aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.builds/freebsd.yml44
-rw-r--r--.builds/openbsd.yml40
-rwxr-xr-x.gitattributes1
-rw-r--r--.github/ISSUE_TEMPLATE/lsp_bug_report.md46
-rw-r--r--.gitignore5
-rw-r--r--.luacheckrc7
-rw-r--r--.luacov22
-rw-r--r--.travis.yml88
-rw-r--r--CMakeLists.txt87
-rw-r--r--CONTRIBUTING.md36
-rw-r--r--Makefile34
-rw-r--r--README.md20
-rw-r--r--appveyor.yml6
-rwxr-xr-xci/before_install.sh11
-rwxr-xr-xci/before_script.sh11
-rw-r--r--ci/build.ps1134
-rw-r--r--ci/common/build.sh15
-rwxr-xr-xci/common/submit_coverage.sh11
-rwxr-xr-xci/install.sh11
-rwxr-xr-xci/run_lint.sh4
-rwxr-xr-xci/run_tests.sh4
-rwxr-xr-xci/snap/after_success.sh14
-rwxr-xr-xci/snap/deploy.sh21
-rwxr-xr-xci/snap/install.sh10
-rwxr-xr-xci/snap/script.sh8
-rw-r--r--ci/snap/travis_snapcraft.cfgbin0 -> 2448 bytes
-rw-r--r--cmake/FindLibIntl.cmake6
-rw-r--r--cmake/GetCompileFlags.cmake7
-rw-r--r--cmake/GetGitRevisionDescription.cmake180
-rw-r--r--cmake/GetGitRevisionDescription.cmake.in38
-rw-r--r--codecov.yml3
-rw-r--r--config/CMakeLists.txt2
-rw-r--r--config/pathdef.c.in1
-rw-r--r--config/versiondef.h.in4
-rw-r--r--contrib/local.mk.example7
-rw-r--r--man/nvim.12
-rw-r--r--runtime/autoload/health/nvim.vim60
-rw-r--r--runtime/autoload/health/provider.vim244
-rw-r--r--runtime/autoload/man.vim216
-rw-r--r--runtime/autoload/netrw.vim49
-rw-r--r--runtime/autoload/provider.vim5
-rw-r--r--runtime/autoload/provider/clipboard.vim13
-rw-r--r--runtime/autoload/provider/node.vim3
-rw-r--r--runtime/autoload/provider/perl.vim69
-rw-r--r--runtime/autoload/provider/pythonx.vim13
-rw-r--r--runtime/autoload/remote/define.vim2
-rw-r--r--runtime/autoload/remote/host.vim4
-rw-r--r--runtime/autoload/spellfile.vim7
-rw-r--r--runtime/doc/api.txt2065
-rw-r--r--runtime/doc/autocmd.txt321
-rw-r--r--runtime/doc/change.txt11
-rw-r--r--runtime/doc/cmdline.txt8
-rw-r--r--runtime/doc/deprecated.txt3
-rw-r--r--runtime/doc/develop.txt90
-rw-r--r--runtime/doc/digraph.txt4
-rw-r--r--runtime/doc/editing.txt4
-rw-r--r--runtime/doc/eval.txt454
-rw-r--r--runtime/doc/filetype.txt13
-rw-r--r--runtime/doc/fold.txt3
-rw-r--r--runtime/doc/help.txt5
-rw-r--r--runtime/doc/if_lua.txt666
-rw-r--r--runtime/doc/index.txt18
-rw-r--r--runtime/doc/insert.txt3
-rw-r--r--runtime/doc/intro.txt14
-rw-r--r--runtime/doc/lsp.txt1262
-rw-r--r--runtime/doc/lua.txt1453
-rw-r--r--runtime/doc/map.txt7
-rw-r--r--runtime/doc/message.txt14
-rw-r--r--runtime/doc/mlang.txt4
-rw-r--r--runtime/doc/motion.txt128
-rw-r--r--runtime/doc/msgpack_rpc.txt7
-rw-r--r--runtime/doc/nvim_terminal_emulator.txt24
-rw-r--r--runtime/doc/options.txt212
-rw-r--r--runtime/doc/pattern.txt3
-rw-r--r--runtime/doc/provider.txt31
-rw-r--r--runtime/doc/quickfix.txt30
-rw-r--r--runtime/doc/quickref.txt2
-rw-r--r--runtime/doc/sign.txt10
-rw-r--r--runtime/doc/spell.txt18
-rw-r--r--runtime/doc/starting.txt16
-rw-r--r--runtime/doc/syntax.txt31
-rw-r--r--runtime/doc/tabpage.txt3
-rw-r--r--runtime/doc/tagsrch.txt66
-rw-r--r--runtime/doc/term.txt2
-rw-r--r--runtime/doc/ui.txt27
-rw-r--r--runtime/doc/usr_21.txt15
-rw-r--r--runtime/doc/usr_25.txt9
-rw-r--r--runtime/doc/usr_41.txt8
-rw-r--r--runtime/doc/vim_diff.txt68
-rw-r--r--runtime/doc/visual.txt8
-rw-r--r--runtime/doc/windows.txt11
-rw-r--r--runtime/filetype.vim86
-rw-r--r--runtime/ftplugin/man.vim11
-rw-r--r--runtime/indent/Makefile14
-rw-r--r--runtime/indent/README.txt2
-rw-r--r--runtime/indent/testdir/README.txt97
-rw-r--r--runtime/indent/testdir/runtest.vim133
-rw-r--r--runtime/indent/testdir/vim.in46
-rw-r--r--runtime/indent/testdir/vim.ok46
-rw-r--r--runtime/indent/tex.vim22
-rw-r--r--runtime/keymap/russian-jcukenwintype.vim112
-rw-r--r--runtime/lua/vim/highlight.lua77
-rw-r--r--runtime/lua/vim/inspect.lua7
-rw-r--r--runtime/lua/vim/lsp.lua1013
-rw-r--r--runtime/lua/vim/lsp/buf.lua251
-rw-r--r--runtime/lua/vim/lsp/callbacks.lua293
-rw-r--r--runtime/lua/vim/lsp/log.lua94
-rw-r--r--runtime/lua/vim/lsp/protocol.lua988
-rw-r--r--runtime/lua/vim/lsp/rpc.lua472
-rw-r--r--runtime/lua/vim/lsp/util.lua1360
-rw-r--r--runtime/lua/vim/shared.lua457
-rw-r--r--runtime/lua/vim/treesitter.lua260
-rw-r--r--runtime/lua/vim/tshighlighter.lua116
-rw-r--r--runtime/lua/vim/uri.lua112
-rw-r--r--runtime/makemenu.vim15
-rw-r--r--runtime/nvim.appdata.xml4
-rw-r--r--runtime/optwin.vim5
-rw-r--r--runtime/pack/dist/opt/termdebug/plugin/termdebug.vim288
-rw-r--r--runtime/plugin/man.vim2
-rw-r--r--runtime/scripts.vim10
-rw-r--r--runtime/synmenu.vim421
-rw-r--r--runtime/syntax/man.vim3
-rw-r--r--runtime/syntax/tex.vim37
-rw-r--r--runtime/syntax/tutor.vim12
-rw-r--r--runtime/syntax/vim.vim4
-rw-r--r--runtime/tutor/en/vim-01-beginner.tutor40
-rw-r--r--runtime/tutor/en/vim-01-beginner.tutor.json84
-rw-r--r--runtime/tutor/tutor.tutor2
-rwxr-xr-xscripts/gen_vimdoc.py795
-rw-r--r--scripts/lua2dox.lua131
-rwxr-xr-xscripts/lua2dox_filter11
-rwxr-xr-xscripts/release.sh6
-rwxr-xr-xscripts/shadacat.py2
-rwxr-xr-xscripts/update-ts-runtime.sh39
-rwxr-xr-xscripts/update_version_stamp.lua55
-rwxr-xr-xscripts/vim-patch.sh226
-rw-r--r--snap/snapcraft.yaml43
-rwxr-xr-xsrc/clint.py10
-rw-r--r--src/nvim/CMakeLists.txt99
-rw-r--r--src/nvim/README.md10
-rw-r--r--src/nvim/api/buffer.c635
-rw-r--r--src/nvim/api/private/helpers.c84
-rw-r--r--src/nvim/api/private/helpers.h20
-rw-r--r--src/nvim/api/ui.c58
-rw-r--r--src/nvim/api/ui_events.in.h4
-rw-r--r--src/nvim/api/vim.c270
-rw-r--r--src/nvim/ascii.h26
-rw-r--r--src/nvim/aucmd.c41
-rw-r--r--src/nvim/auevents.lua11
-rw-r--r--src/nvim/buffer.c697
-rw-r--r--src/nvim/buffer_defs.h594
-rw-r--r--src/nvim/buffer_updates.c56
-rw-r--r--src/nvim/bufhl_defs.h41
-rw-r--r--src/nvim/change.c47
-rw-r--r--src/nvim/channel.c55
-rw-r--r--src/nvim/channel.h2
-rw-r--r--src/nvim/charset.c4
-rw-r--r--src/nvim/context.c2
-rw-r--r--src/nvim/cursor.c11
-rw-r--r--src/nvim/cursor_shape.h8
-rw-r--r--src/nvim/diff.c61
-rw-r--r--src/nvim/diff.h7
-rw-r--r--src/nvim/digraph.c237
-rw-r--r--src/nvim/edit.c1533
-rw-r--r--src/nvim/edit.h3
-rw-r--r--src/nvim/eval.c15702
-rw-r--r--src/nvim/eval.h115
-rw-r--r--src/nvim/eval.lua21
-rw-r--r--src/nvim/eval/decode.c12
-rw-r--r--src/nvim/eval/encode.c9
-rw-r--r--src/nvim/eval/encode.h1
-rw-r--r--src/nvim/eval/executor.c4
-rw-r--r--src/nvim/eval/funcs.c11270
-rw-r--r--src/nvim/eval/funcs.h24
-rw-r--r--src/nvim/eval/typval.c189
-rw-r--r--src/nvim/eval/typval.h65
-rw-r--r--src/nvim/eval/typval_encode.c.h21
-rw-r--r--src/nvim/eval/userfunc.c3480
-rw-r--r--src/nvim/eval/userfunc.h42
-rw-r--r--src/nvim/event/libuv_process.c9
-rw-r--r--src/nvim/event/loop.c40
-rw-r--r--src/nvim/event/process.h3
-rw-r--r--src/nvim/event/stream.c13
-rw-r--r--src/nvim/ex_cmds.c235
-rw-r--r--src/nvim/ex_cmds.lua67
-rw-r--r--src/nvim/ex_cmds2.c132
-rw-r--r--src/nvim/ex_cmds_defs.h156
-rw-r--r--src/nvim/ex_docmd.c2276
-rw-r--r--src/nvim/ex_docmd.h14
-rw-r--r--src/nvim/ex_eval.c39
-rw-r--r--src/nvim/ex_eval.h44
-rw-r--r--src/nvim/ex_getln.c293
-rw-r--r--src/nvim/ex_getln.h5
-rw-r--r--src/nvim/ex_session.c1047
-rw-r--r--src/nvim/ex_session.h13
-rw-r--r--src/nvim/extmark.c928
-rw-r--r--src/nvim/extmark.h93
-rw-r--r--src/nvim/extmark_defs.h37
-rw-r--r--src/nvim/file_search.c4
-rw-r--r--src/nvim/fileio.c189
-rw-r--r--src/nvim/fold.c353
-rw-r--r--src/nvim/func_attr.h2
-rw-r--r--src/nvim/generators/c_grammar.lua1
-rw-r--r--src/nvim/generators/gen_api_dispatch.lua21
-rw-r--r--src/nvim/generators/gen_char_blob.lua70
-rwxr-xr-xsrc/nvim/generators/gen_declarations.lua4
-rw-r--r--src/nvim/generators/gen_eval.lua2
-rw-r--r--src/nvim/getchar.c91
-rw-r--r--src/nvim/getchar.h4
-rw-r--r--src/nvim/globals.h573
-rw-r--r--src/nvim/grid_defs.h4
-rw-r--r--src/nvim/hardcopy.c631
-rw-r--r--src/nvim/hashtab.h2
-rw-r--r--src/nvim/highlight.c42
-rw-r--r--src/nvim/highlight_defs.h3
-rw-r--r--src/nvim/if_cscope.c372
-rw-r--r--src/nvim/if_cscope_defs.h31
-rw-r--r--src/nvim/indent.c48
-rw-r--r--src/nvim/indent.h9
-rw-r--r--src/nvim/indent_c.c12
-rw-r--r--src/nvim/keymap.c5
-rw-r--r--src/nvim/keymap.h24
-rw-r--r--src/nvim/lib/kbtree.h6
-rw-r--r--src/nvim/log.c1
-rw-r--r--src/nvim/lua/converter.c104
-rw-r--r--src/nvim/lua/converter.h9
-rw-r--r--src/nvim/lua/executor.c779
-rw-r--r--src/nvim/lua/executor.h4
-rw-r--r--src/nvim/lua/treesitter.c1006
-rw-r--r--src/nvim/lua/treesitter.h14
-rw-r--r--src/nvim/lua/vim.lua341
-rw-r--r--src/nvim/macros.h44
-rw-r--r--src/nvim/main.c289
-rw-r--r--src/nvim/main.h34
-rw-r--r--src/nvim/map.c11
-rw-r--r--src/nvim/map.h16
-rw-r--r--src/nvim/mark.c67
-rw-r--r--src/nvim/mark.h1
-rw-r--r--src/nvim/marktree.c1197
-rw-r--r--src/nvim/marktree.h76
-rw-r--r--src/nvim/mbyte.c126
-rw-r--r--src/nvim/memline.c126
-rw-r--r--src/nvim/memory.c2
-rw-r--r--src/nvim/menu.c348
-rw-r--r--src/nvim/menu.h34
-rw-r--r--src/nvim/message.c51
-rw-r--r--src/nvim/misc1.c80
-rw-r--r--src/nvim/mouse.c85
-rw-r--r--src/nvim/mouse.h1
-rw-r--r--src/nvim/move.c197
-rw-r--r--src/nvim/msgpack_rpc/channel.c2
-rw-r--r--src/nvim/msgpack_rpc/channel.h2
-rw-r--r--src/nvim/msgpack_rpc/server.c2
-rw-r--r--src/nvim/normal.c476
-rw-r--r--src/nvim/ops.c412
-rw-r--r--src/nvim/option.c754
-rw-r--r--src/nvim/option_defs.h32
-rw-r--r--src/nvim/options.lua62
-rw-r--r--src/nvim/os/env.c207
-rw-r--r--src/nvim/os/fs.c69
-rw-r--r--src/nvim/os/input.c9
-rw-r--r--src/nvim/os/os_win_console.c74
-rw-r--r--src/nvim/os/os_win_console.h12
-rw-r--r--src/nvim/os/pty_conpty_win.c199
-rw-r--r--src/nvim/os/pty_conpty_win.h22
-rw-r--r--src/nvim/os/pty_process_win.c136
-rw-r--r--src/nvim/os/pty_process_win.h15
-rw-r--r--src/nvim/os/shell.c501
-rw-r--r--src/nvim/os/signal.c34
-rw-r--r--src/nvim/os/time.c55
-rw-r--r--src/nvim/os/tty.c5
-rw-r--r--src/nvim/os/users.c83
-rw-r--r--src/nvim/os_unix.c531
-rw-r--r--src/nvim/path.c59
-rw-r--r--src/nvim/path.h11
-rw-r--r--src/nvim/po/af.po2
-rw-r--r--src/nvim/po/check.vim11
-rw-r--r--src/nvim/po/fi.po2
-rw-r--r--src/nvim/po/uk.po4
-rw-r--r--src/nvim/popupmnu.c186
-rw-r--r--src/nvim/pos.h4
-rw-r--r--src/nvim/quickfix.c4666
-rw-r--r--src/nvim/regexp.c884
-rw-r--r--src/nvim/regexp_defs.h27
-rw-r--r--src/nvim/regexp_nfa.c1164
-rw-r--r--src/nvim/screen.c597
-rw-r--r--src/nvim/screen.h6
-rw-r--r--src/nvim/search.c226
-rw-r--r--src/nvim/search.h9
-rw-r--r--src/nvim/shada.c50
-rw-r--r--src/nvim/sign.c210
-rw-r--r--src/nvim/spell.c429
-rw-r--r--src/nvim/spell_defs.h13
-rw-r--r--src/nvim/spellfile.c207
-rw-r--r--src/nvim/state.c2
-rw-r--r--src/nvim/strings.c14
-rw-r--r--src/nvim/syntax.c106
-rw-r--r--src/nvim/tag.c1326
-rw-r--r--src/nvim/tag.h27
-rw-r--r--src/nvim/terminal.c52
-rw-r--r--src/nvim/testdir/Makefile28
-rw-r--r--src/nvim/testdir/check.vim11
-rwxr-xr-xsrc/nvim/testdir/runnvim.sh5
-rw-r--r--src/nvim/testdir/runtest.vim37
-rw-r--r--src/nvim/testdir/screendump.vim2
-rw-r--r--src/nvim/testdir/setup.vim1
-rw-r--r--src/nvim/testdir/shared.vim116
-rw-r--r--src/nvim/testdir/summarize.vim9
-rw-r--r--src/nvim/testdir/term_util.vim11
-rw-r--r--src/nvim/testdir/test42.inbin2438 -> 2373 bytes
-rw-r--r--src/nvim/testdir/test48.in82
-rw-r--r--src/nvim/testdir/test48.ok23
-rw-r--r--src/nvim/testdir/test49.vim6
-rw-r--r--src/nvim/testdir/test64.in654
-rw-r--r--src/nvim/testdir/test64.ok1107
-rw-r--r--src/nvim/testdir/test_alot.vim3
-rw-r--r--src/nvim/testdir/test_assert.vim46
-rw-r--r--src/nvim/testdir/test_autocmd.vim199
-rw-r--r--src/nvim/testdir/test_backup.vim58
-rw-r--r--src/nvim/testdir/test_breakindent.vim126
-rw-r--r--src/nvim/testdir/test_bufline.vim2
-rw-r--r--src/nvim/testdir/test_bufwintabinfo.vim17
-rw-r--r--src/nvim/testdir/test_cindent.vim69
-rw-r--r--src/nvim/testdir/test_clientserver.vim13
-rw-r--r--src/nvim/testdir/test_cmdline.vim207
-rw-r--r--src/nvim/testdir/test_compiler.vim19
-rw-r--r--src/nvim/testdir/test_const.vim20
-rw-r--r--src/nvim/testdir/test_cursor_func.vim16
-rw-r--r--src/nvim/testdir/test_debugger.vim58
-rw-r--r--src/nvim/testdir/test_diffmode.vim88
-rw-r--r--src/nvim/testdir/test_digraph.vim25
-rw-r--r--src/nvim/testdir/test_display.vim56
-rw-r--r--src/nvim/testdir/test_edit.vim17
-rw-r--r--src/nvim/testdir/test_escaped_glob.vim2
-rw-r--r--src/nvim/testdir/test_excmd.vim32
-rw-r--r--src/nvim/testdir/test_exit.vim88
-rw-r--r--src/nvim/testdir/test_expr.vim5
-rw-r--r--src/nvim/testdir/test_filetype.vim76
-rw-r--r--src/nvim/testdir/test_flatten.vim81
-rw-r--r--src/nvim/testdir/test_fnamemodify.vim75
-rw-r--r--src/nvim/testdir/test_fold.vim61
-rw-r--r--src/nvim/testdir/test_functions.vim73
-rw-r--r--src/nvim/testdir/test_ga.vim1
-rw-r--r--src/nvim/testdir/test_gf.vim33
-rw-r--r--src/nvim/testdir/test_gn.vim27
-rw-r--r--src/nvim/testdir/test_goto.vim406
-rw-r--r--src/nvim/testdir/test_hardcopy.vim169
-rw-r--r--src/nvim/testdir/test_help.vim6
-rw-r--r--src/nvim/testdir/test_highlight.vim11
-rw-r--r--src/nvim/testdir/test_increment.vim36
-rw-r--r--src/nvim/testdir/test_ins_complete.vim97
-rw-r--r--src/nvim/testdir/test_join.vim406
-rw-r--r--src/nvim/testdir/test_let.vim162
-rw-r--r--src/nvim/testdir/test_listchars.vim20
-rw-r--r--src/nvim/testdir/test_listlbr.vim31
-rw-r--r--src/nvim/testdir/test_maparg.vim15
-rw-r--r--src/nvim/testdir/test_mapping.vim76
-rw-r--r--src/nvim/testdir/test_marks.vim69
-rw-r--r--src/nvim/testdir/test_match.vim97
-rw-r--r--src/nvim/testdir/test_messages.vim34
-rw-r--r--src/nvim/testdir/test_mksession.vim33
-rw-r--r--src/nvim/testdir/test_mksession_utf8.vim57
-rw-r--r--src/nvim/testdir/test_modeline.vim117
-rw-r--r--src/nvim/testdir/test_normal.vim491
-rw-r--r--src/nvim/testdir/test_number.vim11
-rw-r--r--src/nvim/testdir/test_options.vim86
-rw-r--r--src/nvim/testdir/test_plus_arg_edit.vim28
-rw-r--r--src/nvim/testdir/test_popup.vim132
-rw-r--r--src/nvim/testdir/test_profile.vim294
-rw-r--r--src/nvim/testdir/test_python2.vim10
-rw-r--r--src/nvim/testdir/test_python3.vim28
-rw-r--r--src/nvim/testdir/test_pyx2.vim8
-rw-r--r--src/nvim/testdir/test_pyx3.vim8
-rw-r--r--src/nvim/testdir/test_quickfix.vim1299
-rw-r--r--src/nvim/testdir/test_quotestar.vim23
-rw-r--r--src/nvim/testdir/test_recover.vim6
-rw-r--r--src/nvim/testdir/test_regexp_latin.vim668
-rw-r--r--src/nvim/testdir/test_regexp_utf8.vim342
-rw-r--r--src/nvim/testdir/test_registers.vim104
-rw-r--r--src/nvim/testdir/test_restricted.vim103
-rw-r--r--src/nvim/testdir/test_search.vim41
-rw-r--r--src/nvim/testdir/test_signs.vim413
-rw-r--r--src/nvim/testdir/test_spell.vim100
-rw-r--r--src/nvim/testdir/test_startup.vim165
-rw-r--r--src/nvim/testdir/test_startup_utf8.vim2
-rw-r--r--src/nvim/testdir/test_statusline.vim67
-rw-r--r--src/nvim/testdir/test_substitute.vim31
-rw-r--r--src/nvim/testdir/test_suspend.vim3
-rw-r--r--src/nvim/testdir/test_swap.vim84
-rw-r--r--src/nvim/testdir/test_syntax.vim94
-rw-r--r--src/nvim/testdir/test_tabline.vim25
-rw-r--r--src/nvim/testdir/test_tabpage.vim4
-rw-r--r--src/nvim/testdir/test_tagfunc.vim84
-rw-r--r--src/nvim/testdir/test_tagjump.vim138
-rw-r--r--src/nvim/testdir/test_taglist.vim17
-rw-r--r--src/nvim/testdir/test_textformat.vim423
-rw-r--r--src/nvim/testdir/test_textobjects.vim21
-rw-r--r--src/nvim/testdir/test_timers.vim97
-rw-r--r--src/nvim/testdir/test_undo.vim253
-rw-r--r--src/nvim/testdir/test_user_func.vim4
-rw-r--r--src/nvim/testdir/test_utf8.vim43
-rw-r--r--src/nvim/testdir/test_vimscript.vim72
-rw-r--r--src/nvim/testdir/test_virtualedit.vim144
-rw-r--r--src/nvim/testdir/test_window_cmd.vim135
-rw-r--r--src/nvim/testdir/test_writefile.vim7
-rw-r--r--src/nvim/testdir/view_util.vim14
-rw-r--r--src/nvim/tui/input.c60
-rw-r--r--src/nvim/tui/input.h7
-rw-r--r--src/nvim/tui/terminfo.c12
-rw-r--r--src/nvim/tui/tui.c301
-rw-r--r--src/nvim/ui.c21
-rw-r--r--src/nvim/ui.h7
-rw-r--r--src/nvim/ui_compositor.c16
-rw-r--r--src/nvim/undo.c119
-rw-r--r--src/nvim/undo_defs.h16
-rw-r--r--src/nvim/version.c156
-rw-r--r--src/nvim/vim.h6
-rw-r--r--src/nvim/viml/parser/expressions.c3
-rw-r--r--src/nvim/viml/parser/expressions.h4
-rw-r--r--src/nvim/window.c308
-rw-r--r--src/nvim/xdiff/xdiff.h24
-rw-r--r--src/nvim/xdiff/xdiffi.c44
-rw-r--r--src/nvim/xdiff/xdiffi.h2
-rw-r--r--src/nvim/xdiff/xemit.c12
-rw-r--r--src/nvim/xdiff/xemit.h2
-rw-r--r--src/nvim/xdiff/xhistogram.c12
-rw-r--r--src/nvim/xdiff/xinclude.h8
-rw-r--r--src/nvim/xdiff/xmacros.h2
-rw-r--r--src/nvim/xdiff/xpatience.c30
-rw-r--r--src/nvim/xdiff/xprepare.h2
-rw-r--r--src/nvim/xdiff/xtypes.h2
-rw-r--r--src/nvim/xdiff/xutils.c10
-rw-r--r--src/nvim/xdiff/xutils.h2
-rw-r--r--src/tree_sitter/LICENSE21
-rw-r--r--src/tree_sitter/README.md16
-rw-r--r--src/tree_sitter/alloc.h95
-rw-r--r--src/tree_sitter/api.h876
-rw-r--r--src/tree_sitter/array.h158
-rw-r--r--src/tree_sitter/atomic.h42
-rw-r--r--src/tree_sitter/bits.h29
-rw-r--r--src/tree_sitter/clock.h141
-rw-r--r--src/tree_sitter/error_costs.h11
-rw-r--r--src/tree_sitter/get_changed_ranges.c482
-rw-r--r--src/tree_sitter/get_changed_ranges.h36
-rw-r--r--src/tree_sitter/language.c149
-rw-r--r--src/tree_sitter/language.h143
-rw-r--r--src/tree_sitter/length.h44
-rw-r--r--src/tree_sitter/lexer.c391
-rw-r--r--src/tree_sitter/lexer.h48
-rw-r--r--src/tree_sitter/lib.c17
-rw-r--r--src/tree_sitter/node.c677
-rw-r--r--src/tree_sitter/parser.c1879
-rw-r--r--src/tree_sitter/parser.h235
-rw-r--r--src/tree_sitter/point.h54
-rw-r--r--src/tree_sitter/query.c2035
-rw-r--r--src/tree_sitter/reduce_action.h34
-rw-r--r--src/tree_sitter/reusable_node.h88
-rw-r--r--src/tree_sitter/stack.c848
-rw-r--r--src/tree_sitter/stack.h135
-rw-r--r--src/tree_sitter/subtree.c982
-rw-r--r--src/tree_sitter/subtree.h285
-rw-r--r--src/tree_sitter/tree.c148
-rw-r--r--src/tree_sitter/tree.h34
-rw-r--r--src/tree_sitter/tree_cursor.c367
-rw-r--r--src/tree_sitter/tree_cursor.h21
-rw-r--r--src/tree_sitter/treesitter_commit_hash.txt1
-rw-r--r--src/tree_sitter/unicode.h50
-rw-r--r--src/tree_sitter/unicode/ICU_SHA1
-rw-r--r--src/tree_sitter/unicode/LICENSE414
-rw-r--r--src/tree_sitter/unicode/README.md29
-rw-r--r--src/tree_sitter/unicode/ptypes.h1
-rw-r--r--src/tree_sitter/unicode/umachine.h448
-rw-r--r--src/tree_sitter/unicode/urename.h1
-rw-r--r--src/tree_sitter/unicode/utf.h1
-rw-r--r--src/tree_sitter/unicode/utf16.h733
-rw-r--r--src/tree_sitter/unicode/utf8.h881
-rw-r--r--test/README.md22
-rw-r--r--test/busted/outputHandlers/TAP.lua2
-rw-r--r--test/busted/outputHandlers/nvim.lua46
-rw-r--r--test/functional/api/extmark_spec.lua1477
-rw-r--r--test/functional/api/highlight_spec.lua35
-rw-r--r--test/functional/api/keymap_spec.lua8
-rw-r--r--test/functional/api/menu_spec.lua4
-rw-r--r--test/functional/api/proc_spec.lua2
-rw-r--r--test/functional/api/rpc_fixture.lua14
-rw-r--r--test/functional/api/server_requests_spec.lua27
-rw-r--r--test/functional/api/tabpage_spec.lua4
-rw-r--r--test/functional/api/vim_spec.lua216
-rw-r--r--test/functional/api/window_spec.lua6
-rw-r--r--test/functional/autocmd/autocmd_spec.lua101
-rw-r--r--test/functional/autocmd/cmdline_spec.lua2
-rw-r--r--test/functional/autocmd/tabclose_spec.lua18
-rw-r--r--test/functional/autocmd/tabnewentered_spec.lua563
-rw-r--r--test/functional/autocmd/textyankpost_spec.lua48
-rw-r--r--test/functional/autoread/focus_spec.lua58
-rw-r--r--test/functional/cmdline/ctrl_r_spec.lua2
-rw-r--r--test/functional/cmdline/history_spec.lua4
-rw-r--r--test/functional/core/fileio_spec.lua25
-rw-r--r--test/functional/core/job_spec.lua84
-rw-r--r--test/functional/core/main_spec.lua2
-rw-r--r--test/functional/core/startup_spec.lua60
-rw-r--r--test/functional/eval/api_functions_spec.lua3
-rw-r--r--test/functional/eval/buf_functions_spec.lua6
-rw-r--r--test/functional/eval/ctx_functions_spec.lua2
-rw-r--r--test/functional/eval/environ_spec.lua1
-rw-r--r--test/functional/eval/fnamemodify_spec.lua119
-rw-r--r--test/functional/eval/input_spec.lua2
-rw-r--r--test/functional/eval/let_spec.lua19
-rw-r--r--test/functional/eval/map_functions_spec.lua2
-rw-r--r--test/functional/eval/null_spec.lua6
-rw-r--r--test/functional/eval/sort_spec.lua3
-rw-r--r--test/functional/eval/system_spec.lua21
-rw-r--r--test/functional/eval/timer_spec.lua21
-rw-r--r--test/functional/eval/uniq_spec.lua2
-rw-r--r--test/functional/eval/wait_spec.lua7
-rw-r--r--test/functional/ex_cmds/dict_notifications_spec.lua14
-rw-r--r--test/functional/ex_cmds/drop_spec.lua8
-rw-r--r--test/functional/ex_cmds/echo_spec.lua167
-rw-r--r--test/functional/ex_cmds/highlight_spec.lua4
-rw-r--r--test/functional/ex_cmds/ls_spec.lua12
-rw-r--r--test/functional/ex_cmds/mksession_spec.lua45
-rw-r--r--test/functional/ex_cmds/profile_spec.lua76
-rw-r--r--test/functional/ex_cmds/script_spec.lua4
-rw-r--r--test/functional/ex_cmds/sign_spec.lua4
-rw-r--r--test/functional/ex_cmds/write_spec.lua7
-rw-r--r--test/functional/example_spec.lua38
-rw-r--r--test/functional/fixtures/CMakeLists.txt10
-rw-r--r--test/functional/fixtures/api_level_6.mpackbin25194 -> 25287 bytes
-rw-r--r--test/functional/fixtures/fake-lsp-server.lua450
-rw-r--r--test/functional/fixtures/printenv-test.c6
-rw-r--r--test/functional/helpers.lua62
-rw-r--r--test/functional/insert/insert_spec.lua6
-rw-r--r--test/functional/legacy/021_control_wi_spec.lua2
-rw-r--r--test/functional/legacy/022_line_ending_spec.lua25
-rw-r--r--test/functional/legacy/041_writing_and_reading_hundred_kbyte_spec.lua43
-rw-r--r--test/functional/legacy/045_folding_spec.lua3
-rw-r--r--test/functional/legacy/063_match_and_matchadd_spec.lua20
-rw-r--r--test/functional/legacy/075_maparg_spec.lua6
-rw-r--r--test/functional/legacy/077_mf_hash_grow_spec.lua52
-rw-r--r--test/functional/legacy/084_curswant_spec.lua49
-rw-r--r--test/functional/legacy/095_regexp_multibyte_spec.lua2
-rw-r--r--test/functional/legacy/097_glob_path_spec.lua2
-rw-r--r--test/functional/legacy/098_scrollbind_spec.lua48
-rw-r--r--test/functional/legacy/104_let_assignment_spec.lua54
-rw-r--r--test/functional/legacy/assert_spec.lua62
-rw-r--r--test/functional/legacy/breakindent_spec.lua214
-rw-r--r--test/functional/legacy/delete_spec.lua2
-rw-r--r--test/functional/legacy/eval_spec.lua4
-rw-r--r--test/functional/legacy/expand_spec.lua39
-rw-r--r--test/functional/legacy/memory_usage_spec.lua169
-rw-r--r--test/functional/legacy/prompt_buffer_spec.lua153
-rw-r--r--test/functional/legacy/search_spec.lua34
-rw-r--r--test/functional/lua/api_spec.lua32
-rw-r--r--test/functional/lua/buffer_updates_spec.lua17
-rw-r--r--test/functional/lua/commands_spec.lua99
-rw-r--r--test/functional/lua/luaeval_spec.lua308
-rw-r--r--test/functional/lua/overrides_spec.lua34
-rw-r--r--test/functional/lua/treesitter_spec.lua512
-rw-r--r--test/functional/lua/uri_spec.lua150
-rw-r--r--test/functional/lua/utility_functions_spec.lua291
-rw-r--r--test/functional/lua/vim_spec.lua1235
-rw-r--r--test/functional/normal/jump_spec.lua91
-rw-r--r--test/functional/normal/put_spec.lua10
-rw-r--r--test/functional/normal/tabpage_spec.lua38
-rw-r--r--test/functional/options/chars_spec.lua48
-rw-r--r--test/functional/options/defaults_spec.lua42
-rw-r--r--test/functional/options/keymap_spec.lua2
-rw-r--r--test/functional/options/num_options_spec.lua20
-rw-r--r--test/functional/options/shortmess_spec.lua8
-rw-r--r--test/functional/plugin/health_spec.lua20
-rw-r--r--test/functional/plugin/lsp_spec.lua1620
-rw-r--r--test/functional/plugin/man_spec.lua54
-rw-r--r--test/functional/preload.lua10
-rw-r--r--test/functional/provider/clipboard_spec.lua48
-rw-r--r--test/functional/provider/define_spec.lua15
-rw-r--r--test/functional/provider/nodejs_spec.lua5
-rw-r--r--test/functional/provider/perl_spec.lua80
-rw-r--r--test/functional/provider/python3_spec.lua5
-rw-r--r--test/functional/provider/python_spec.lua5
-rw-r--r--test/functional/provider/ruby_spec.lua5
-rw-r--r--test/functional/shada/errors_spec.lua21
-rw-r--r--test/functional/shada/history_spec.lua10
-rw-r--r--test/functional/shada/variables_spec.lua35
-rw-r--r--test/functional/terminal/buffer_spec.lua46
-rw-r--r--test/functional/terminal/edit_spec.lua6
-rw-r--r--test/functional/terminal/ex_terminal_spec.lua2
-rw-r--r--test/functional/terminal/helpers.lua2
-rw-r--r--test/functional/terminal/highlight_spec.lua57
-rw-r--r--test/functional/terminal/mouse_spec.lua38
-rw-r--r--test/functional/terminal/scrollback_spec.lua32
-rw-r--r--test/functional/terminal/tui_spec.lua285
-rw-r--r--test/functional/terminal/window_split_tab_spec.lua10
-rw-r--r--test/functional/ui/bufhl_spec.lua370
-rw-r--r--test/functional/ui/cmdline_highlight_spec.lua8
-rw-r--r--test/functional/ui/cmdline_spec.lua13
-rw-r--r--test/functional/ui/cursor_spec.lua23
-rw-r--r--test/functional/ui/diff_spec.lua151
-rw-r--r--test/functional/ui/embed_spec.lua3
-rw-r--r--test/functional/ui/float_spec.lua101
-rw-r--r--test/functional/ui/fold_spec.lua115
-rw-r--r--test/functional/ui/highlight_spec.lua141
-rw-r--r--test/functional/ui/hlstate_spec.lua12
-rw-r--r--test/functional/ui/inccommand_spec.lua223
-rw-r--r--test/functional/ui/messages_spec.lua304
-rw-r--r--test/functional/ui/mode_spec.lua53
-rw-r--r--test/functional/ui/mouse_spec.lua30
-rw-r--r--test/functional/ui/multibyte_spec.lua42
-rw-r--r--test/functional/ui/multigrid_spec.lua191
-rw-r--r--test/functional/ui/options_spec.lua47
-rw-r--r--test/functional/ui/output_spec.lua28
-rw-r--r--test/functional/ui/popupmenu_spec.lua286
-rw-r--r--test/functional/ui/screen.lua243
-rw-r--r--test/functional/ui/screen_basic_spec.lua3
-rw-r--r--test/functional/ui/searchhl_spec.lua2
-rw-r--r--test/functional/ui/sign_spec.lua4
-rw-r--r--test/functional/ui/spell_spec.lua35
-rw-r--r--test/functional/ui/syntax_conceal_spec.lua97
-rw-r--r--test/functional/ui/tabline_spec.lua12
-rw-r--r--test/functional/ui/wildmode_spec.lua138
-rw-r--r--test/helpers.lua96
-rw-r--r--test/unit/eval/helpers.lua14
-rw-r--r--test/unit/eval/typval_spec.lua94
-rw-r--r--test/unit/helpers.lua8
-rw-r--r--test/unit/marktree_spec.lua190
-rw-r--r--test/unit/mbyte_spec.lua5
-rw-r--r--test/unit/os/env_spec.lua4
-rw-r--r--test/unit/os/fs_spec.lua2
-rw-r--r--test/unit/path_spec.lua6
-rw-r--r--test/unit/preprocess.lua4
-rw-r--r--test/unit/tui_spec.lua136
-rw-r--r--third-party/CMakeLists.txt68
-rw-r--r--third-party/cmake/BuildGettext.cmake4
-rw-r--r--third-party/cmake/BuildLibtermkey.cmake1
-rw-r--r--third-party/cmake/BuildLibvterm.cmake2
-rw-r--r--third-party/cmake/BuildLuajit.cmake22
-rw-r--r--third-party/cmake/BuildLuarocks.cmake34
-rw-r--r--third-party/cmake/BuildLuv.cmake31
-rw-r--r--third-party/cmake/BuildMsgpack.cmake2
-rw-r--r--third-party/cmake/BuildTreesitterParsers.cmake27
-rw-r--r--third-party/cmake/BuildUnibilium.cmake1
-rw-r--r--third-party/cmake/DownloadAndExtractFile.cmake3
-rw-r--r--third-party/cmake/GettextCMakeLists.txt278
-rw-r--r--third-party/cmake/TreesitterParserCMakeLists.txt19
-rw-r--r--third-party/patches/gettext-Fix-building-with-MSVC.patch50
-rw-r--r--third-party/patches/gettext-Fix-compilation-on-a-system-without-alloca.patch28
645 files changed, 84198 insertions, 35451 deletions
diff --git a/.builds/freebsd.yml b/.builds/freebsd.yml
new file mode 100644
index 0000000000..36f44f236b
--- /dev/null
+++ b/.builds/freebsd.yml
@@ -0,0 +1,44 @@
+image: freebsd/12.x
+
+packages:
+- cmake
+- gmake
+- ninja
+- libtool
+- sha
+- automake
+- pkgconf
+- unzip
+- wget
+- gettext
+- python
+- libffi
+
+sources:
+- https://github.com/neovim/neovim
+
+environment:
+ SOURCEHUT: 1
+ LANG: en_US.UTF-8
+ CMAKE_EXTRA_FLAGS: -DTRAVIS_CI_BUILD=ON -DMIN_LOG_LEVEL=3
+
+tasks:
+- build-deps: |
+ cd neovim
+ gmake deps
+- build: |
+ cd neovim
+ gmake CMAKE_BUILD_TYPE=Release CMAKE_EXTRA_FLAGS="${CMAKE_EXTRA_FLAGS}" nvim
+- functionaltest: |
+ cd neovim
+ gmake functionaltest
+- unittest: |
+ cd neovim
+ gmake unittest
+
+# Unfortunately, oldtest is tanking hard on sourcehut's FreeBSD instance
+# and not producing any logs as a result. So don't do this task for now.
+# Ref: https://github.com/neovim/neovim/pull/11477#discussion_r352095005.
+# - test-oldtest: |
+# cd neovim
+# gmake oldtest
diff --git a/.builds/openbsd.yml b/.builds/openbsd.yml
index a8f6992e3f..5fa6556066 100644
--- a/.builds/openbsd.yml
+++ b/.builds/openbsd.yml
@@ -1,41 +1,43 @@
# sourcehut CI: https://builds.sr.ht/~jmk/neovim
-image: openbsd/6.5
+image: openbsd/6.7
packages:
- autoconf-2.69p2
- automake-1.15.1
- cmake
-- gettext-0.19.8.1p3
-- gettext-tools-0.19.8.1
+- gettext-runtime-0.20.1p1
+- gettext-tools-0.20.1p3
- gmake
- libtool
-- ninja-1.8.2p0
-- unzip-6.0p11
+- ninja-1.10.0
+- unzip-6.0p13
sources:
- https://github.com/neovim/neovim
+environment:
+ SOURCEHUT: 1
+ LC_CTYPE: en_US.UTF-8
+ CMAKE_EXTRA_FLAGS: -DTRAVIS_CI_BUILD=ON -DMIN_LOG_LEVEL=3
+
tasks:
-- build: |
+- build-deps: |
export AUTOCONF_VERSION=2.69
export AUTOMAKE_VERSION=1.15
- cd neovim
- mkdir .deps
- cd .deps
+ mkdir neovim/.deps
+ cd neovim/.deps
cmake -G Ninja ../third-party/
cmake --build . --config Debug
- cd ..
- mkdir build
- cd build
- cmake -G Ninja -DMIN_LOG_LEVEL=3 ..
+- build: |
+ mkdir neovim/build
+ cd neovim/build
+ cmake -G Ninja $CMAKE_EXTRA_FLAGS ..
cmake --build . --config Debug
./bin/nvim --version
-- test: |
- export LC_CTYPE=en_US.UTF-8
- # functional tests
+- functionaltest: |
cd neovim/build
- # cmake --build . --config Debug --target functionaltest
- # oldtests
- cd ..
+ cmake --build . --config Debug --target functionaltest
+- oldtest: |
+ cd neovim
gmake oldtest
diff --git a/.gitattributes b/.gitattributes
index 15a5c58091..cb5934a2a1 100755
--- a/.gitattributes
+++ b/.gitattributes
@@ -1 +1,2 @@
*.h linguist-language=C
+src/nvim/testdir/test42.in diff
diff --git a/.github/ISSUE_TEMPLATE/lsp_bug_report.md b/.github/ISSUE_TEMPLATE/lsp_bug_report.md
new file mode 100644
index 0000000000..0e5155c7ac
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/lsp_bug_report.md
@@ -0,0 +1,46 @@
+---
+name: Language server client bug report
+about: Report a built-in lsp problem in Nvim
+title: ''
+labels: bug, lsp
+
+---
+
+<!-- Before reporting: search existing issues and check the FAQ. -->
+
+- `nvim --version`:
+- language server name/version:
+- Operating system/version:
+
+<details>
+<summary>nvim -c ":checkhealth nvim nvim_lsp"</summary>
+
+<!-- Paste the results from `nvim -c ":checkhealth nvim nvim_lsp"` here. -->
+
+</details>
+
+<details>
+<summary>lsp.log</summary>
+
+<!--
+Please paste the lsp log before and after the problem.
+
+You can set log level like this.
+`:lua vim.lsp.set_log_level("debug")`
+
+You can find the location of the log with the following command.
+`:lua print(vim.lsp.get_log_path())`
+-->
+
+</details>
+
+### Steps to reproduce using `nvim -u NORC`
+
+```
+nvim -u NORC
+```
+
+### Actual behaviour
+
+### Expected behaviour
+
diff --git a/.gitignore b/.gitignore
index 7db3d96e2b..699d493b59 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,7 +10,9 @@ compile_commands.json
/dist/
/.deps/
/tmp/
+/.clangd/
+.DS_Store
*.mo
.*.sw?
*~
@@ -42,6 +44,7 @@ tags
/src/nvim/testdir/valgrind.*
/src/nvim/testdir/.gdbinit
/runtime/indent/testdir/*.out
++runtime/indent/testdir/*.fail
# Generated by src/nvim/testdir/runnvim.sh.
/src/nvim/testdir/*.tlog
@@ -58,6 +61,8 @@ local.mk
/runtime/doc/*.html
/runtime/doc/tags.ref
/runtime/doc/errors.log
+# Don't include the mpack files.
+/runtime/doc/*.mpack
# CLion
/.idea/
diff --git a/.luacheckrc b/.luacheckrc
index b945835bba..a628daed80 100644
--- a/.luacheckrc
+++ b/.luacheckrc
@@ -1,7 +1,14 @@
-- vim: ft=lua tw=80
+stds.nvim = {
+ read_globals = { "jit" }
+}
+std = "lua51+nvim"
+
-- Ignore W211 (unused variable) with preload files.
files["**/preload.lua"] = {ignore = { "211" }}
+-- Allow vim module to modify itself, but only here.
+files["src/nvim/lua/vim.lua"] = {ignore = { "122/vim" }}
-- Don't report unused self arguments of methods.
self = false
diff --git a/.luacov b/.luacov
new file mode 100644
index 0000000000..f8eb28e3f7
--- /dev/null
+++ b/.luacov
@@ -0,0 +1,22 @@
+-- Configuration file for LuaCov
+
+local source = require("lfs").currentdir()
+
+local function pesc(s)
+ assert(type(s) == 'string', s)
+ return s:gsub('[%(%)%.%%%+%-%*%?%[%]%^%$]', '%%%1')
+end
+
+return {
+ include = {
+ -- Absolute paths (starting with source dir, not hidden (i.e. .deps)).
+ pesc(source) .. "[/\\][^.].+",
+ -- Relative (non-hidden) paths.
+ '^[^/\\.]',
+ },
+ modules = {
+ ['vim'] = 'runtime/lua/vim/shared.lua'
+ },
+}
+
+-- vim: ft=lua tw=80 sw=2 et
diff --git a/.travis.yml b/.travis.yml
index 325b5e7b56..b920f70f45 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -4,6 +4,19 @@ language: c
env:
global:
+ # Encrypted environment variables, see
+ # http://docs.travis-ci.com/user/encryption-keys/
+ #
+ # SNAP_SECRET_KEY: generated by:
+ # travis encrypt SNAP_SECRET_KEY=xx --add
+ # https://github.com/neovim/neovim/pull/11428
+ # snapcraft key expires after 1 year. Steps to refresh it:
+ # 1. snapcraft enable-ci travis --refresh
+ # 2. mv .snapcraft/travis_snapcraft.cfg ci/snap/travis_snapcraft.cfg
+ # 3. Copy after_success command to ci/snap/deploy.sh from .travis.yml
+ # 4. Undo changes to .travis.yml
+ - secure: hd0qn2u8ABbJg5Bx4pBRcUQbKYFmcSHoecyHIPTCnGJT+NI41Bvm/IkN/N5DhBF+LbD3Q2nmR/dzI5H/dqS7RxMFvEx1DuFLendFHHX3MYf0AuKpXYY3gwgMTmqx8p/v6srlU7RBGWNGzHCWqksAem+EIWCe3I7WvfdKo1/DV/Y=
+
# Set "false" to force rebuild of third-party dependencies.
- CACHE_ENABLE=true
# Build directory for Neovim.
@@ -24,10 +37,6 @@ env:
-DDEPS_PREFIX=$DEPS_BUILD_DIR/usr
-DMIN_LOG_LEVEL=3"
- DEPS_CMAKE_FLAGS="-DUSE_BUNDLED_GPERF=OFF"
- # Additional CMake flags for 32-bit builds.
- - CMAKE_FLAGS_32BIT="-DCMAKE_SYSTEM_LIBRARY_PATH=/lib32:/usr/lib32:/usr/local/lib32
- -DCMAKE_IGNORE_PATH=/lib:/usr/lib:/usr/local/lib
- -DCMAKE_TOOLCHAIN_FILE=$TRAVIS_BUILD_DIR/cmake/i386-linux-gnu.toolchain.cmake"
# Environment variables for Clang sanitizers.
- ASAN_OPTIONS="detect_leaks=1:check_initialization_order=1:log_path=$LOG_DIR/asan"
- TSAN_OPTIONS="log_path=$LOG_DIR/tsan"
@@ -62,12 +71,12 @@ addons:
- build-essential
- clang
- cmake
+ - cpanminus
- cscope
- gcc-multilib
- gdb
- gperf
- language-pack-tr
- - libc6-dev-i386
- libtool-bin
- locales
- ninja-build
@@ -76,10 +85,14 @@ addons:
- valgrind
- xclip
homebrew:
- update: false
+ update: true
+ casks:
+ - powershell
packages:
- ccache
+ - cpanminus
- ninja
+ - perl
jobs:
include:
@@ -99,14 +112,19 @@ jobs:
- GCOV=gcov-9
- CMAKE_FLAGS="$CMAKE_FLAGS -DUSE_GCOV=ON"
- GCOV_ERROR_FILE="/tmp/libgcov-errors.log"
+ - USE_LUACOV=1
+ - BUSTED_ARGS="--coverage"
- *common-job-env
addons:
apt:
sources:
- sourceline: 'ppa:ubuntu-toolchain-r/test'
+ - sourceline: 'deb [arch=amd64] https://packages.microsoft.com/ubuntu/16.04/prod xenial main'
+ key_url: 'https://packages.microsoft.com/keys/microsoft.asc'
packages:
- *common-apt-packages
- gcc-9
+ - powershell
- if: branch = master AND commit_message !~ /\[skip.lint\]/
name: lint
os: linux
@@ -136,15 +154,73 @@ jobs:
compiler: gcc
env:
- BUILD_32BIT=ON
+ - CMAKE_FLAGS="$CMAKE_FLAGS -m32 -DCMAKE_TOOLCHAIN_FILE=$TRAVIS_BUILD_DIR/cmake/i386-linux-gnu.toolchain.cmake"
+ - DEPS_CMAKE_FLAGS="$DEPS_CMAKE_FLAGS -m32 -DCMAKE_TOOLCHAIN_FILE=$TRAVIS_BUILD_DIR/cmake/i386-linux-gnu.toolchain.cmake"
# Minimum required CMake.
- CMAKE_URL=https://cmake.org/files/v2.8/cmake-2.8.12-Linux-i386.sh
- *common-job-env
+ - name: big-endian
+ os: linux
+ arch: s390x
+ compiler: gcc
+ env:
+ - FUNCTIONALTEST=functionaltest-lua
+ - CMAKE_FLAGS="$CMAKE_FLAGS -DPREFER_LUA=ON"
+ - DEPS_CMAKE_FLAGS="$DEPS_CMAKE_FLAGS -DUSE_BUNDLED_LUAJIT=OFF"
+ - *common-job-env
+ addons:
+ apt:
+ packages:
+ - *common-apt-packages
+ - gettext
+ - python-pip
+ - python3-pip
+ - python-setuptools
+ - python3-setuptools
+ - python-dev
+ - python3-dev
- name: clang-tsan
os: linux
compiler: clang
env:
- CLANG_SANITIZER=TSAN
- *common-job-env
+ - if: type != pull_request
+ name: snap
+ os: linux
+ env:
+ - LC_ALL: C.UTF-8
+ - LANG: C.UTF-8
+ - SNAPCRAFT_ENABLE_SILENT_REPORT: y
+ - SNAPCRAFT_ENABLE_DEVELOPER_DEBUG: y
+ addons:
+ snaps:
+ - name: snapcraft
+ channel: stable
+ classic: true
+ - name: http
+ - name: transfer
+ - name: lxd
+ channel: stable
+ # Override default before_install, before_cache.
+ before_install: /bin/true
+ before_cache: /bin/true
+ install: ci/snap/install.sh
+ before_script: echo "Building snap..."
+ script: ci/snap/script.sh
+ after_success: ci/snap/after_success.sh
+ deploy:
+ skip_cleanup: true
+ provider: script
+ script: ci/snap/deploy.sh
+ on:
+ branch: master
+ allow_failures:
+ - env:
+ - LC_ALL: C.UTF-8
+ - LANG: C.UTF-8
+ - SNAPCRAFT_ENABLE_SILENT_REPORT: y
+ - SNAPCRAFT_ENABLE_DEVELOPER_DEBUG: y
fast_finish: true
before_install: ci/before_install.sh
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 9c3c20d23f..a4e49ccfc5 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -6,6 +6,13 @@
cmake_minimum_required(VERSION 2.8.12)
project(nvim C)
+if(POLICY CMP0065)
+ cmake_policy(SET CMP0065 NEW)
+endif()
+if(POLICY CMP0060)
+ cmake_policy(SET CMP0060 NEW)
+endif()
+
# Point CMake at any custom modules we may ship
list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake")
@@ -113,47 +120,32 @@ set_property(CACHE CMAKE_BUILD_TYPE PROPERTY
# 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 4)
+set(NVIM_VERSION_MINOR 5)
set(NVIM_VERSION_PATCH 0)
-set(NVIM_VERSION_PRERELEASE "") # for package maintainers
+set(NVIM_VERSION_PRERELEASE "-dev") # for package maintainers
# API level
-set(NVIM_API_LEVEL 6) # Bump this after any API change.
+set(NVIM_API_LEVEL 7) # Bump this after any API change.
set(NVIM_API_LEVEL_COMPAT 0) # Adjust this after a _breaking_ API change.
-set(NVIM_API_PRERELEASE false)
-
-file(TO_CMAKE_PATH ${CMAKE_CURRENT_LIST_DIR}/.git FORCED_GIT_DIR)
-include(GetGitRevisionDescription)
-get_git_head_revision(GIT_REFSPEC NVIM_VERSION_COMMIT)
-if(NVIM_VERSION_COMMIT) # is a git repo
- git_describe(NVIM_VERSION_MEDIUM)
- # `git describe` annotates the most recent tagged release; for pre-release
- # builds we must replace that with the unreleased version.
- string(REGEX REPLACE "^v[0-9]+\\.[0-9]+\\.[0-9]+"
- "v${NVIM_VERSION_MAJOR}.${NVIM_VERSION_MINOR}.${NVIM_VERSION_PATCH}"
- NVIM_VERSION_MEDIUM
- ${NVIM_VERSION_MEDIUM})
-endif()
+set(NVIM_API_PRERELEASE true)
set(NVIM_VERSION_BUILD_TYPE "${CMAKE_BUILD_TYPE}")
# NVIM_VERSION_CFLAGS set further below.
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
-# Minimize logging for release-type builds.
-if(NOT CMAKE_C_FLAGS_RELEASE MATCHES DMIN_LOG_LEVEL)
- set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -DMIN_LOG_LEVEL=3")
-endif()
-if(NOT CMAKE_C_FLAGS_MINSIZEREL MATCHES DMIN_LOG_LEVEL)
- set(CMAKE_C_FLAGS_MINSIZEREL "${CMAKE_C_FLAGS_MINSIZEREL} -DMIN_LOG_LEVEL=3")
-endif()
-if(NOT CMAKE_C_FLAGS_RELWITHDEBINFO MATCHES DMIN_LOG_LEVEL)
- set(CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO} -DMIN_LOG_LEVEL=3")
-endif()
-
# Log level (MIN_LOG_LEVEL in log.h)
if("${MIN_LOG_LEVEL}" MATCHES "^$")
- message(STATUS "MIN_LOG_LEVEL not specified, default is 1 (INFO)")
+ # 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})
@@ -309,11 +301,18 @@ if(UNIX)
if(HAS_FSTACK_PROTECTOR_STRONG_FLAG)
add_compile_options(-fstack-protector-strong)
+ link_libraries(-fstack-protector-strong)
elseif(HAS_FSTACK_PROTECTOR_FLAG)
add_compile_options(-fstack-protector --param ssp-buffer-size=4)
+ link_libraries(-fstack-protector --param ssp-buffer-size=4)
endif()
endif()
+check_c_compiler_flag(-fno-common HAVE_FNO_COMMON)
+if (HAVE_FNO_COMMON)
+ add_compile_options(-fno-common)
+endif()
+
check_c_compiler_flag(-fdiagnostics-color=auto HAS_DIAG_COLOR_FLAG)
if(HAS_DIAG_COLOR_FLAG)
if(CMAKE_GENERATOR MATCHES "Ninja")
@@ -323,10 +322,10 @@ if(HAS_DIAG_COLOR_FLAG)
endif()
endif()
-option(TRAVIS_CI_BUILD "Travis/QuickBuild CI, extra flags will be set" OFF)
+option(TRAVIS_CI_BUILD "Travis/sourcehut CI, extra flags will be set" OFF)
if(TRAVIS_CI_BUILD)
- message(STATUS "Travis/QuickBuild CI build enabled")
+ message(STATUS "Travis/sourcehut CI build enabled")
add_compile_options(-Werror)
if(DEFINED ENV{BUILD_32BIT})
# Get some test coverage for unsigned char
@@ -454,17 +453,17 @@ if((CLANG_ASAN_UBSAN OR CLANG_MSAN OR CLANG_TSAN) AND NOT CMAKE_C_COMPILER_ID MA
message(FATAL_ERROR "Sanitizers are only supported for Clang")
endif()
+if(ENABLE_LIBICONV)
+ find_package(Iconv REQUIRED)
+ include_directories(SYSTEM ${Iconv_INCLUDE_DIRS})
+endif()
+
if(ENABLE_LIBINTL)
# LibIntl (not Intl) selects our FindLibIntl.cmake script. #8464
find_package(LibIntl REQUIRED)
include_directories(SYSTEM ${LibIntl_INCLUDE_DIRS})
endif()
-if(ENABLE_LIBICONV)
- find_package(Iconv REQUIRED)
- include_directories(SYSTEM ${Iconv_INCLUDE_DIRS})
-endif()
-
# Determine platform's threading library. Set CMAKE_THREAD_PREFER_PTHREAD
# explicitly to indicate a strong preference for pthread.
set(CMAKE_THREAD_PREFER_PTHREAD ON)
@@ -486,18 +485,19 @@ include(LuaHelpers)
set(LUA_DEPENDENCIES lpeg mpack bit)
if(NOT LUA_PRG)
foreach(CURRENT_LUA_PRG luajit lua5.1 lua5.2 lua)
- # If LUA_PRG is set find_program() will not search
- unset(LUA_PRG CACHE)
+ unset(_CHECK_LUA_PRG CACHE)
unset(LUA_PRG_WORKS)
- find_program(LUA_PRG ${CURRENT_LUA_PRG})
+ find_program(_CHECK_LUA_PRG ${CURRENT_LUA_PRG})
- if(LUA_PRG)
- check_lua_deps(${LUA_PRG} "${LUA_DEPENDENCIES}" LUA_PRG_WORKS)
+ 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)
endif()
@@ -560,10 +560,7 @@ if(BUSTED_PRG)
endif()
set(UNITTEST_PREREQS nvim-test unittest-headers)
- set(FUNCTIONALTEST_PREREQS nvim printargs-test shell-test streams-test ${GENERATED_HELP_TAGS})
- if(NOT WIN32)
- list(APPEND FUNCTIONALTEST_PREREQS tty-test)
- endif()
+ set(FUNCTIONALTEST_PREREQS nvim printenv-test printargs-test shell-test streams-test tty-test ${GENERATED_HELP_TAGS})
set(BENCHMARK_PREREQS nvim tty-test)
# Useful for automated build systems, if they want to manually run the tests.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 607f1aae83..c7d8398bf0 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -8,7 +8,7 @@ If you want to help but don't know where to start, here are some
low-risk/isolated tasks:
- [Merge a Vim patch].
-- Try a [complexity:low] issue.
+- Try a [good first issue](../../labels/good%20first%20issue) or [complexity:low] issue.
- Fix bugs found by [Clang](#clang-scan-build), [PVS](#pvs-studio) or
[Coverity](#coverity).
@@ -65,10 +65,11 @@ Pull requests (PRs)
Pull requests have three stages: `[WIP]` (Work In Progress), `[RFC]` (Request
For Comment) and `[RDY]` (Ready).
-- `[RFC]` is assumed by default, i.e. you are requesting a review.
-- Add `[WIP]` to the PR title if you are _not_ requesting feedback and the work
- is still in flux.
-- Add `[RDY]` if you are _done_ and only waiting on merge.
+1. `[RFC]` is assumed by default, **do not** put "RFC" in the PR title (it adds
+ noise to merge commit messages).
+2. Add `[WIP]` to the PR title if you are _not_ requesting feedback and the work
+ is still in flux.
+3. Add `[RDY]` to the PR title if you are _done_ and only waiting on merge.
### Commit messages
@@ -85,7 +86,7 @@ the VCS/git logs more valuable.
### Automated builds (CI)
-Each pull request must pass the automated builds on [Travis CI], [QuickBuild]
+Each pull request must pass the automated builds on [Travis CI], [sourcehut]
and [AppVeyor].
- CI builds are compiled with [`-Werror`][gcc-warnings], so compiler warnings
@@ -100,14 +101,19 @@ and [AppVeyor].
- 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.)
-- [How to investigate QuickBuild failures](https://github.com/neovim/neovim/pull/4718#issuecomment-217631350)
- - QuickBuild uses this invocation:
- ```
- mkdir -p build/${params.get("buildType")} \
- && cd build/${params.get("buildType")} \
- && cmake -G "Unix Makefiles" -DBUSTED_OUTPUT_TYPE=TAP -DCMAKE_BUILD_TYPE=${params.get("buildType")}
- -DTRAVIS_CI_BUILD=ON ../.. && ${node.getAttribute("make", "make")}
- VERBOSE=1 nvim unittest-prereqs functionaltest-prereqs
+- CI for freebsd and openbsd runs on [sourcehut].
+ - To get a backtrace on freebsd (after connecting via ssh):
+ ```sh
+ sudo pkg install tmux # If you want tmux.
+ lldb build/bin/nvim -c nvim.core
+
+ # To get a full backtrace:
+ # 1. Rebuild with debug info.
+ rm -rf nvim.core build
+ gmake CMAKE_BUILD_TYPE=RelWithDebInfo CMAKE_EXTRA_FLAGS="-DTRAVIS_CI_BUILD=ON -DMIN_LOG_LEVEL=3" nvim
+ # 2. Run the failing test to generate a new core file.
+ TEST_FILE=test/functional/foo.lua gmake functionaltest
+ lldb build/bin/nvim -c nvim.core
```
### Clang scan-build
@@ -223,7 +229,7 @@ as context, use the `-W` argument as well.
[review-checklist]: https://github.com/neovim/neovim/wiki/Code-review-checklist
[3174]: https://github.com/neovim/neovim/issues/3174
[Travis CI]: https://travis-ci.org/neovim/neovim
-[QuickBuild]: http://neovim-qb.szakmeister.net/dashboard
+[sourcehut]: https://builds.sr.ht/~jmk
[AppVeyor]: https://ci.appveyor.com/project/neovim/neovim
[Merge a Vim patch]: https://github.com/neovim/neovim/wiki/Merging-patches-from-upstream-Vim
[Clang report]: https://neovim.io/doc/reports/clang/
diff --git a/Makefile b/Makefile
index 264ae8a470..39f42739ff 100644
--- a/Makefile
+++ b/Makefile
@@ -58,11 +58,15 @@ endif
BUILD_CMD = $(BUILD_TOOL)
-ifneq ($(VERBOSE),)
- # Only need to handle Ninja here. Make will inherit the VERBOSE variable.
- ifeq ($(BUILD_TYPE),Ninja)
+# Only need to handle Ninja here. Make will inherit the VERBOSE variable, and the -j and -n flags.
+ifeq ($(BUILD_TYPE),Ninja)
+ ifneq ($(VERBOSE),)
BUILD_CMD += -v
endif
+ BUILD_CMD += $(shell printf '%s' '$(MAKEFLAGS)' | grep -o -- '-j[0-9]\+')
+ ifeq (n,$(findstring n,$(firstword -$(MAKEFLAGS))))
+ BUILD_CMD += -n
+ endif
endif
DEPS_CMAKE_FLAGS ?=
@@ -119,8 +123,13 @@ oldtest: | nvim build/runtime/doc/tags
ifeq ($(strip $(TEST_FILE)),)
+$(SINGLE_MAKE) -C src/nvim/testdir NVIM_PRG="$(realpath build/bin/nvim)" $(MAKEOVERRIDES)
else
- +$(SINGLE_MAKE) -C src/nvim/testdir NVIM_PRG="$(realpath build/bin/nvim)" NEW_TESTS=$(TEST_FILE) SCRIPTS= $(MAKEOVERRIDES)
+ @# Handle TEST_FILE=test_foo{,.res,.vim}.
+ +$(SINGLE_MAKE) -C src/nvim/testdir NVIM_PRG="$(realpath build/bin/nvim)" 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="$(realpath build/bin/nvim)" SCRIPTS= $(MAKEOVERRIDES) $(patsubst src/nvim/testdir/%.vim,%,$@)
build/runtime/doc/tags helptags: | nvim
+$(BUILD_CMD) -C build runtime/doc/tags
@@ -138,6 +147,14 @@ functionaltest-lua: | nvim
lualint: | build/.ran-cmake deps
$(BUILD_CMD) -C build lualint
+shlint:
+ @shellcheck --version | head -n 2
+ shellcheck scripts/vim-patch.sh
+
+_opt_shlint:
+ @command -v shellcheck && { $(MAKE) shlint; exit $$?; } \
+ || echo "SKIP: shlint (shellcheck not found)"
+
pylint:
flake8 contrib/ scripts/ src/ test/
@@ -158,6 +175,7 @@ clean:
+test -d build && $(BUILD_CMD) -C build clean || true
$(MAKE) -C src/nvim/testdir clean
$(MAKE) -C runtime/doc clean
+ $(MAKE) -C runtime/indent clean
distclean:
rm -rf $(DEPS_BUILD_DIR) build
@@ -187,16 +205,16 @@ appimage:
appimage-%:
bash scripts/genappimage.sh $*
-lint: check-single-includes clint lualint _opt_pylint
+lint: check-single-includes clint lualint _opt_pylint _opt_shlint
# Generic pattern rules, allowing for `make build/bin/nvim` etc.
# Does not work with "Unix Makefiles".
ifeq ($(BUILD_TYPE),Ninja)
-build/%:
+build/%: phony_force
$(BUILD_CMD) -C build $(patsubst build/%,%,$@)
-$(DEPS_BUILD_DIR)/%:
+$(DEPS_BUILD_DIR)/%: phony_force
$(BUILD_CMD) -C $(DEPS_BUILD_DIR) $(patsubst $(DEPS_BUILD_DIR)/%,%,$@)
endif
-.PHONY: test lualint pylint functionaltest unittest lint clint clean distclean nvim libnvim cmake deps install appimage checkprefix
+.PHONY: test lualint pylint shlint functionaltest unittest lint clint clean distclean nvim libnvim cmake deps install appimage checkprefix
diff --git a/README.md b/README.md
index 1bdf33d6bb..f9a9a7fe8b 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,7 @@
[![Neovim](https://raw.githubusercontent.com/neovim/neovim.github.io/master/logos/neovim-logo-300x87.png)](https://neovim.io)
-[Wiki](https://github.com/neovim/neovim/wiki) |
[Documentation](https://neovim.io/doc) |
-[Chat/Discussion](https://gitter.im/neovim/neovim) |
+[Chat](https://gitter.im/neovim/neovim) |
[Twitter](https://twitter.com/Neovim)
[![Travis build status](https://travis-ci.org/neovim/neovim.svg?branch=master)](https://travis-ci.org/neovim/neovim)
@@ -15,6 +14,7 @@
[![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/)
+[![nvim](https://snapcraft.io//nvim/badge.svg)](https://snapcraft.io/nvim)
Neovim is a project that seeks to aggressively refactor Vim in order to:
@@ -67,7 +67,7 @@ To skip bundled (`third-party/*`) dependencies:
1. Install the dependencies using a package manager.
```
- sudo apt install gperf luajit luarocks libuv1-dev libluajit-5.1-dev libunibilium-dev libmsgpack-dev libtermkey-dev libvterm-dev
+ sudo apt install gperf luajit luarocks libuv1-dev libluajit-5.1-dev libunibilium-dev libmsgpack-dev libtermkey-dev libvterm-dev libutf8proc-dev
sudo luarocks build mpack
sudo luarocks build lpeg
sudo luarocks build inspect
@@ -113,17 +113,9 @@ Project layout
License
-------
-Neovim is licensed under the terms of the Apache 2.0 license, except for
-parts that were contributed under the Vim license.
-
-- Contributions committed before [b17d96][license-commit] remain under the Vim
- license.
-
-- Contributions committed after [b17d96][license-commit] are licensed under
- Apache 2.0 unless those contributions were copied from Vim (identified in
- the commit logs by the `vim-patch` token).
-
-See `LICENSE` for details.
+Neovim contributions since [b17d96][license-commit] are licensed under the
+Apache 2.0 license, except for contributions copied from Vim (identified by the
+`vim-patch` token). See LICENSE for details.
Vim is Charityware. You can use and copy it as much as you like, but you are
encouraged to make a donation for needy children in Uganda. Please see the
diff --git a/appveyor.yml b/appveyor.yml
index 756c7fea2e..01ca16f930 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -22,9 +22,6 @@ init:
#- ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
matrix:
fast_finish: true
-install: []
-before_build:
-- ps: Install-Product node 8
build_script:
- powershell ci\build.ps1
after_build:
@@ -40,6 +37,3 @@ cache:
artifacts:
- path: build/Neovim.zip
- path: build/bin/nvim.exe
-branches:
- only:
- - master
diff --git a/ci/before_install.sh b/ci/before_install.sh
index 5cb6894b8c..1cf60edf73 100755
--- a/ci/before_install.sh
+++ b/ci/before_install.sh
@@ -3,10 +3,6 @@
set -e
set -o pipefail
-if [[ "${CI_TARGET}" == lint ]]; then
- exit
-fi
-
echo 'Python info:'
(
set -x
@@ -26,7 +22,7 @@ if [[ "${TRAVIS_OS_NAME}" != osx ]] && command -v pyenv; then
echo 'Setting Python versions via pyenv'
# Prefer Python 2 over 3 (more conservative).
- pyenv global 2.7.15:3.7
+ pyenv global 2.7.15:3.7.1
echo 'Updated Python info:'
(
@@ -47,12 +43,11 @@ if [[ "${TRAVIS_OS_NAME}" == osx ]] || [ ! -f ~/.nvm/nvm.sh ]; then
fi
source ~/.nvm/nvm.sh
-nvm install --lts
-nvm use --lts
+nvm install 10
if [[ -n "$CMAKE_URL" ]]; then
echo "Installing custom CMake: $CMAKE_URL"
- curl --retry 5 --silent --fail -o /tmp/cmake-installer.sh "$CMAKE_URL"
+ curl --retry 5 --silent --show-error --fail -o /tmp/cmake-installer.sh "$CMAKE_URL"
mkdir -p "$HOME/.local/bin" /opt/cmake-custom
bash /tmp/cmake-installer.sh --prefix=/opt/cmake-custom --skip-license
ln -sfn /opt/cmake-custom/bin/cmake "$HOME/.local/bin/cmake"
diff --git a/ci/before_script.sh b/ci/before_script.sh
index 605ecdbf66..1759dbe942 100755
--- a/ci/before_script.sh
+++ b/ci/before_script.sh
@@ -10,6 +10,12 @@ fi
CI_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${CI_DIR}/common/build.sh"
+# Enable ipv6 on Travis. ref: a39c8b7ce30d
+if ! test "${TRAVIS_OS_NAME}" = osx ; then
+ echo "before_script.sh: enable ipv6"
+ sudo sysctl -w net.ipv6.conf.lo.disable_ipv6=0
+fi
+
# Test some of the configuration variables.
if [[ -n "${GCOV}" ]] && [[ ! $(type -P "${GCOV}") ]]; then
echo "\$GCOV: '${GCOV}' is not executable."
@@ -35,5 +41,10 @@ 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/build.ps1 b/ci/build.ps1
index d533d7b4e0..36570be7ae 100644
--- a/ci/build.ps1
+++ b/ci/build.ps1
@@ -1,10 +1,12 @@
-$ErrorActionPreference = 'stop'
-Set-PSDebug -Strict -Trace 1
+param([switch]$NoTests)
+Set-StrictMode -Version Latest
+$ErrorActionPreference = 'Stop'
+$ProgressPreference = 'SilentlyContinue'
$isPullRequest = ($env:APPVEYOR_PULL_REQUEST_HEAD_COMMIT -ne $null)
$env:CONFIGURATION -match '^(?<compiler>\w+)_(?<bits>32|64)(?:-(?<option>\w+))?$'
$compiler = $Matches.compiler
-$compileOption = $Matches.option
+$compileOption = if ($Matches -contains 'option') {$Matches.option} else {''}
$bits = $Matches.bits
$cmakeBuildType = $(if ($env:CMAKE_BUILD_TYPE -ne $null) {$env:CMAKE_BUILD_TYPE} else {'RelWithDebInfo'});
$buildDir = [System.IO.Path]::GetFullPath("$(pwd)")
@@ -23,11 +25,33 @@ $uploadToCodeCov = $false
function exitIfFailed() {
if ($LastExitCode -ne 0) {
- Set-PSDebug -Off
exit $LastExitCode
}
}
+# https://github.com/lukesampson/scoop#installation
+$scoop = (New-Object System.Net.WebClient).DownloadString('https://get.scoop.sh')
+& {
+ Set-StrictMode -Off
+ Invoke-Expression $scoop
+}
+
+scoop install perl
+perl --version
+cpanm.bat --version
+
+if (-not $NoTests) {
+ scoop install nodejs-lts
+ node --version
+ npm.cmd --version
+
+ cpanm.bat -n Neovim::Ext
+ if ($LastExitCode -ne 0) {
+ Get-Content -Path "$env:USERPROFILE\.cpanm\build.log"
+ }
+ perl -W -e 'use Neovim::Ext; print $Neovim::Ext::VERSION'; exitIfFailed
+}
+
if (-Not (Test-Path -PathType container $env:DEPS_BUILD_DIR)) {
write-host "cache dir not found: $($env:DEPS_BUILD_DIR)"
mkdir $env:DEPS_BUILD_DIR
@@ -46,13 +70,17 @@ if ($compiler -eq 'MINGW') {
$nvimCmakeVars['USE_GCOV'] = 'ON'
$uploadToCodecov = $true
$env:GCOV = "C:\msys64\mingw$bits\bin\gcov"
+
+ # Setup/build Lua coverage.
+ $env:USE_LUACOV = 1
+ $env:BUSTED_ARGS = "--coverage"
}
# These are native MinGW builds, but they use the toolchain inside
# MSYS2, this allows using all the dependencies and tools available
# in MSYS2, but we cannot build inside the MSYS2 shell.
$cmakeGenerator = 'Ninja'
$cmakeGeneratorArgs = '-v'
- $mingwPackages = @('ninja', 'cmake', 'perl', 'diffutils').ForEach({
+ $mingwPackages = @('ninja', 'cmake', 'diffutils').ForEach({
"mingw-w64-$arch-$_"
})
@@ -76,23 +104,30 @@ elseif ($compiler -eq 'MSVC') {
}
}
-# Setup python (use AppVeyor system python)
-C:\Python27\python.exe -m pip install pynvim ; exitIfFailed
-C:\Python35\python.exe -m pip install pynvim ; exitIfFailed
-# Disambiguate python3
-move c:\Python35\python.exe c:\Python35\python3.exe
-$env:PATH = "C:\Python35;C:\Python27;$env:PATH"
-# Sanity check
-python -c "import pynvim; print(str(pynvim))" ; exitIfFailed
-python3 -c "import pynvim; print(str(pynvim))" ; exitIfFailed
-
-$env:PATH = "C:\Ruby24\bin;$env:PATH"
-gem.cmd install neovim
-Get-Command -CommandType Application neovim-ruby-host.bat
+if (-not $NoTests) {
+ # Setup python (use AppVeyor system python)
+ C:\Python27\python.exe -m pip install pynvim ; exitIfFailed
+ C:\Python35\python.exe -m pip install pynvim ; exitIfFailed
+ # Disambiguate python3
+ move c:\Python35\python.exe c:\Python35\python3.exe
+ $env:PATH = "C:\Python35;C:\Python27;$env:PATH"
+ # Sanity check
+ python -c "import pynvim; print(str(pynvim))" ; exitIfFailed
+ python3 -c "import pynvim; print(str(pynvim))" ; exitIfFailed
+
+ $env:PATH = "C:\Ruby24\bin;$env:PATH"
+ gem.cmd install neovim
+ Get-Command -CommandType Application neovim-ruby-host.bat
+
+ npm.cmd install -g neovim
+ Get-Command -CommandType Application neovim-node-host.cmd
+ npm.cmd link neovim
+}
-npm.cmd install -g neovim
-Get-Command -CommandType Application neovim-node-host.cmd
-npm.cmd link neovim
+if ($compiler -eq 'MSVC') {
+ # Required for LuaRocks (https://github.com/luarocks/luarocks/issues/1039#issuecomment-507296940).
+ $env:VCINSTALLDIR = "C:/Program Files (x86)/Microsoft Visual Studio/2017/Community/VC/Tools/MSVC/14.16.27023/"
+}
function convertToCmakeArgs($vars) {
return $vars.GetEnumerator() | foreach { "-D$($_.Key)=$($_.Value)" }
@@ -113,37 +148,42 @@ cmake --build . --config $cmakeBuildType -- $cmakeGeneratorArgs ; exitIfFailed
# Ensure that the "win32" feature is set.
.\bin\nvim -u NONE --headless -c 'exe !has(\"win32\").\"cq\"' ; exitIfFailed
-# Functional tests
-# The $LastExitCode from MSBuild can't be trusted
-$failed = $false
-# Temporarily turn off tracing to reduce log file output
-Set-PSDebug -Off
-cmake --build . --config $cmakeBuildType --target functionaltest -- $cmakeGeneratorArgs 2>&1 |
- foreach { $failed = $failed -or
- $_ -match 'functional tests failed with error'; $_ }
-if ($failed) {
- if ($uploadToCodecov) {
- bash -l /c/projects/neovim/ci/common/submit_coverage.sh functionaltest
- }
- exit $LastExitCode
+if ($env:USE_LUACOV -eq 1) {
+ & $env:DEPS_PREFIX\luarocks\luarocks.bat install cluacov
}
-Set-PSDebug -Strict -Trace 1
+if (-not $NoTests) {
+ # Functional tests
+ # The $LastExitCode from MSBuild can't be trusted
+ $failed = $false
-if ($uploadToCodecov) {
- bash -l /c/projects/neovim/ci/common/submit_coverage.sh functionaltest
-}
+ # Run only this test file:
+ # $env:TEST_FILE = "test\functional\foo.lua"
+ cmake --build . --config $cmakeBuildType --target functionaltest -- $cmakeGeneratorArgs 2>&1 |
+ foreach { $failed = $failed -or
+ $_ -match 'functional tests failed with error'; $_ }
+
+ if ($uploadToCodecov) {
+ if ($env:USE_LUACOV -eq 1) {
+ & $env:DEPS_PREFIX\bin\luacov.bat
+ }
+ bash -l /c/projects/neovim/ci/common/submit_coverage.sh functionaltest
+ }
+ if ($failed) {
+ exit $LastExitCode
+ }
-# Old tests
-# 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\mingw$bits\bin\mingw32-make.exe" -C $(Convert-Path ..\src\nvim\testdir) VERBOSE=1 ; exitIfFailed
-$env:PATH = $OldPath
+ # Old tests
+ # 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\mingw$bits\bin\mingw32-make.exe" -C $(Convert-Path ..\src\nvim\testdir) VERBOSE=1 ; exitIfFailed
+ $env:PATH = $OldPath
-if ($uploadToCodecov) {
- bash -l /c/projects/neovim/ci/common/submit_coverage.sh oldtest
+ if ($uploadToCodecov) {
+ bash -l /c/projects/neovim/ci/common/submit_coverage.sh oldtest
+ }
}
# Build artifacts
diff --git a/ci/common/build.sh b/ci/common/build.sh
index 8e9b2f8ebb..0024f2cbd5 100644
--- a/ci/common/build.sh
+++ b/ci/common/build.sh
@@ -18,9 +18,6 @@ build_make() {
}
build_deps() {
- if test "${BUILD_32BIT}" = ON ; then
- DEPS_CMAKE_FLAGS="${DEPS_CMAKE_FLAGS} ${CMAKE_FLAGS_32BIT}"
- fi
if test "${FUNCTIONALTEST}" = "functionaltest-lua" \
|| test "${CLANG_SANITIZER}" = "ASAN_UBSAN" ; then
DEPS_CMAKE_FLAGS="${DEPS_CMAKE_FLAGS} -DUSE_BUNDLED_LUA=ON"
@@ -53,9 +50,6 @@ prepare_build() {
if test -n "${CLANG_SANITIZER}" ; then
CMAKE_FLAGS="${CMAKE_FLAGS} -DCLANG_${CLANG_SANITIZER}=ON"
fi
- if test "${BUILD_32BIT}" = ON ; then
- CMAKE_FLAGS="${CMAKE_FLAGS} ${CMAKE_FLAGS_32BIT}"
- fi
mkdir -p "${BUILD_DIR}"
cd "${BUILD_DIR}"
@@ -92,12 +86,3 @@ build_nvim() {
cd "${TRAVIS_BUILD_DIR}"
}
-
-macos_rvm_dance() {
- # neovim-ruby gem requires a ruby newer than the macOS default.
- source ~/.rvm/scripts/rvm
- rvm get stable --auto-dotfiles
- rvm reload
- rvm use 2.2.5
- rvm use
-}
diff --git a/ci/common/submit_coverage.sh b/ci/common/submit_coverage.sh
index 4e92975d22..9c7887de0b 100755
--- a/ci/common/submit_coverage.sh
+++ b/ci/common/submit_coverage.sh
@@ -43,3 +43,14 @@ 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/install.sh b/ci/install.sh
index cda9a11f08..a4dfc87a1b 100755
--- a/ci/install.sh
+++ b/ci/install.sh
@@ -4,7 +4,7 @@ set -e
set -o pipefail
if [[ "${CI_TARGET}" == lint ]]; then
- python -m pip -q install --user --upgrade flake8
+ python3 -m pip -q install --user --upgrade flake8
exit
fi
@@ -14,13 +14,18 @@ fi
# Use default CC to avoid compilation problems when installing Python modules.
echo "Install neovim module for Python 3."
-CC=cc python3 -m pip -q install --upgrade pynvim
+CC=cc python3 -m pip -q install --user --upgrade pynvim
echo "Install neovim module for Python 2."
-CC=cc python2 -m pip -q install --upgrade pynvim
+CC=cc python2 -m pip -q install --user --upgrade pynvim
echo "Install neovim RubyGem."
gem install --no-document --version ">= 0.8.0" neovim
echo "Install neovim npm package"
+source ~/.nvm/nvm.sh
+nvm use 10
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'
diff --git a/ci/run_lint.sh b/ci/run_lint.sh
index 88af163e80..8373a3cb36 100755
--- a/ci/run_lint.sh
+++ b/ci/run_lint.sh
@@ -20,6 +20,10 @@ enter_suite 'pylint'
run_test 'make pylint' pylint
exit_suite --continue
+enter_suite 'shlint'
+run_test 'make shlint' shlint
+exit_suite --continue
+
enter_suite single-includes
CLICOLOR_FORCE=1 run_test_wd \
--allow-hang \
diff --git a/ci/run_tests.sh b/ci/run_tests.sh
index c175910da5..d91ac5589e 100755
--- a/ci/run_tests.sh
+++ b/ci/run_tests.sh
@@ -17,6 +17,10 @@ build_nvim
exit_suite --continue
+source ~/.nvm/nvm.sh
+nvm use 10
+
+
enter_suite tests
if test "$CLANG_SANITIZER" != "TSAN" ; then
diff --git a/ci/snap/after_success.sh b/ci/snap/after_success.sh
new file mode 100755
index 0000000000..e66721a5e2
--- /dev/null
+++ b/ci/snap/after_success.sh
@@ -0,0 +1,14 @@
+#!/usr/bin/env bash
+
+set -e
+set -o pipefail
+
+
+RESULT_SNAP=$(find ./ -name "*.snap")
+
+sudo snap install "$RESULT_SNAP" --dangerous --classic
+
+/snap/bin/nvim --version
+
+SHA256=$(sha256sum "$RESULT_SNAP")
+echo "SHA256: ${SHA256} ."
diff --git a/ci/snap/deploy.sh b/ci/snap/deploy.sh
new file mode 100755
index 0000000000..5fbd52d775
--- /dev/null
+++ b/ci/snap/deploy.sh
@@ -0,0 +1,21 @@
+#!/usr/bin/env bash
+
+set -e
+set -o pipefail
+
+# not a tagged release, abort
+# [[ "$TRAVIS_TAG" != "$TRAVIS_BRANCH" ]] && exit 0
+
+mkdir -p .snapcraft
+# shellcheck disable=SC2154
+openssl aes-256-cbc -K "$encrypted_ece1c4844832_key" -iv "$encrypted_ece1c4844832_iv" \
+ -in ci/snap/travis_snapcraft.cfg -out .snapcraft/snapcraft.cfg -d
+
+SNAP=$(find ./ -name "*.snap")
+
+# TODO(justinmk): This always does `edge` until we enable tagged builds.
+if [[ "$SNAP" =~ "dirty" || "$SNAP" =~ "nightly" ]]; then
+ snapcraft push "$SNAP" --release edge
+else
+ snapcraft push "$SNAP" --release candidate
+fi
diff --git a/ci/snap/install.sh b/ci/snap/install.sh
new file mode 100755
index 0000000000..23e0bc5eb8
--- /dev/null
+++ b/ci/snap/install.sh
@@ -0,0 +1,10 @@
+#!/usr/bin/env bash
+
+set -e
+set -o pipefail
+
+sudo apt update
+sudo /snap/bin/lxd.migrate -yes
+sudo /snap/bin/lxd waitready
+sudo /snap/bin/lxd init --auto
+
diff --git a/ci/snap/script.sh b/ci/snap/script.sh
new file mode 100755
index 0000000000..647cda4874
--- /dev/null
+++ b/ci/snap/script.sh
@@ -0,0 +1,8 @@
+#!/usr/bin/env bash
+
+set -e
+set -o pipefail
+
+mkdir -p "$TRAVIS_BUILD_DIR/snaps-cache"
+sudo snapcraft --use-lxd
+
diff --git a/ci/snap/travis_snapcraft.cfg b/ci/snap/travis_snapcraft.cfg
new file mode 100644
index 0000000000..3e6a60c30d
--- /dev/null
+++ b/ci/snap/travis_snapcraft.cfg
Binary files differ
diff --git a/cmake/FindLibIntl.cmake b/cmake/FindLibIntl.cmake
index 5663098147..09eafb786a 100644
--- a/cmake/FindLibIntl.cmake
+++ b/cmake/FindLibIntl.cmake
@@ -38,6 +38,9 @@ endif()
if (LibIntl_LIBRARY)
list(APPEND CMAKE_REQUIRED_LIBRARIES "${LibIntl_LIBRARY}")
endif()
+if (MSVC)
+ list(APPEND CMAKE_REQUIRED_LIBRARIES ${ICONV_LIBRARY})
+endif()
check_c_source_compiles("
#include <libintl.h>
@@ -48,6 +51,9 @@ int main(int argc, char** argv) {
bind_textdomain_codeset(\"foo\", \"bar\");
textdomain(\"foo\");
}" HAVE_WORKING_LIBINTL)
+if (MSVC)
+ list(REMOVE_ITEM CMAKE_REQUIRED_LIBRARIES ${ICONV_LIBRARY})
+endif()
if (LibIntl_INCLUDE_DIR)
list(REMOVE_ITEM CMAKE_REQUIRED_INCLUDES "${LibIntl_INCLUDE_DIR}")
endif()
diff --git a/cmake/GetCompileFlags.cmake b/cmake/GetCompileFlags.cmake
index 2238744a66..49b57f6f75 100644
--- a/cmake/GetCompileFlags.cmake
+++ b/cmake/GetCompileFlags.cmake
@@ -3,6 +3,13 @@ function(get_compile_flags _compile_flags)
set(compile_flags "<CMAKE_C_COMPILER> <CFLAGS> <BUILD_TYPE_CFLAGS> <COMPILE_OPTIONS><COMPILE_DEFINITIONS> <INCLUDES>")
# Get C compiler.
+ if(CMAKE_C_COMPILER_ARG1)
+ string(REPLACE
+ "<CMAKE_C_COMPILER>"
+ "<CMAKE_C_COMPILER> ${CMAKE_C_COMPILER_ARG1}"
+ compile_flags
+ "${compile_flags}")
+ endif()
string(REPLACE
"<CMAKE_C_COMPILER>"
"${CMAKE_C_COMPILER}"
diff --git a/cmake/GetGitRevisionDescription.cmake b/cmake/GetGitRevisionDescription.cmake
deleted file mode 100644
index 5044c682e4..0000000000
--- a/cmake/GetGitRevisionDescription.cmake
+++ /dev/null
@@ -1,180 +0,0 @@
-# https://github.com/rpavlik/cmake-modules
-#
-# - Returns a version string from Git
-#
-# These functions force a re-configure on each git commit so that you can
-# trust the values of the variables in your build system.
-#
-# get_git_head_revision(<refspecvar> <hashvar> [<additional arguments to git describe> ...])
-#
-# Returns the refspec and sha hash of the current head revision
-#
-# git_describe(<var> [<additional arguments to git describe> ...])
-#
-# Returns the results of git describe on the source tree, and adjusting
-# the output so that it tests false if an error occurs.
-#
-# git_get_exact_tag(<var> [<additional arguments to git describe> ...])
-#
-# Returns the results of git describe --exact-match on the source tree,
-# and adjusting the output so that it tests false if there was no exact
-# matching tag.
-#
-# Requires CMake 2.6 or newer (uses the 'function' command)
-#
-# Original Author:
-# 2009-2010 Ryan Pavlik <rpavlik@iastate.edu> <abiryan@ryand.net>
-# http://academic.cleardefinition.com
-# Iowa State University HCI Graduate Program/VRAC
-#
-# Copyright Iowa State University 2009-2010.
-# Distributed under the Boost Software License, Version 1.0.
-# (See accompanying file LICENSE_1_0.txt or copy at
-# http://www.boost.org/LICENSE_1_0.txt)
-
-if(__get_git_revision_description)
- return()
-endif()
-set(__get_git_revision_description YES)
-
-# We must run the following at "include" time, not at function call time,
-# to find the path to this module rather than the path to a calling list file
-get_filename_component(_gitdescmoddir ${CMAKE_CURRENT_LIST_FILE} PATH)
-
-function(get_git_dir _gitdir)
- # check FORCED_GIT_DIR first
- if(FORCED_GIT_DIR)
- set(${_gitdir} ${FORCED_GIT_DIR} PARENT_SCOPE)
- return()
- endif()
-
- # check GIT_DIR in environment
- set(GIT_DIR $ENV{GIT_DIR})
- if(NOT GIT_DIR)
- set(GIT_PARENT_DIR ${CMAKE_CURRENT_SOURCE_DIR})
- set(GIT_DIR ${GIT_PARENT_DIR}/.git)
- endif()
- # .git dir not found, search parent directories
- while(NOT EXISTS ${GIT_DIR})
- set(GIT_PREVIOUS_PARENT ${GIT_PARENT_DIR})
- get_filename_component(GIT_PARENT_DIR ${GIT_PARENT_DIR} PATH)
- if(GIT_PARENT_DIR STREQUAL GIT_PREVIOUS_PARENT)
- return()
- endif()
- set(GIT_DIR ${GIT_PARENT_DIR}/.git)
- endwhile()
- # check if this is a submodule
- if(NOT IS_DIRECTORY ${GIT_DIR})
- file(READ ${GIT_DIR} submodule)
- string(REGEX REPLACE "gitdir: (.*)\n$" "\\1" GIT_DIR_RELATIVE ${submodule})
- get_filename_component(SUBMODULE_DIR ${GIT_DIR} PATH)
- get_filename_component(GIT_DIR ${SUBMODULE_DIR}/${GIT_DIR_RELATIVE} ABSOLUTE)
- endif()
- set(${_gitdir} ${GIT_DIR} PARENT_SCOPE)
-endfunction()
-
-function(get_git_head_revision _refspecvar _hashvar)
- get_git_dir(GIT_DIR)
- if(NOT GIT_DIR)
- return()
- endif()
-
- set(GIT_DATA ${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/git-data)
- if(NOT EXISTS ${GIT_DATA})
- file(MAKE_DIRECTORY ${GIT_DATA})
- endif()
-
- if(NOT EXISTS ${GIT_DIR}/HEAD)
- return()
- endif()
- set(HEAD_FILE ${GIT_DATA}/HEAD)
- configure_file(${GIT_DIR}/HEAD ${HEAD_FILE} COPYONLY)
-
- configure_file(${_gitdescmoddir}/GetGitRevisionDescription.cmake.in
- ${GIT_DATA}/grabRef.cmake
- @ONLY)
- include(${GIT_DATA}/grabRef.cmake)
-
- set(${_refspecvar} ${HEAD_REF} PARENT_SCOPE)
- set(${_hashvar} ${HEAD_HASH} PARENT_SCOPE)
-endfunction()
-
-function(git_describe _var)
- get_git_dir(GIT_DIR)
- if(NOT GIT_DIR)
- return()
- endif()
-
- if(NOT GIT_FOUND)
- find_package(Git QUIET)
- endif()
- if(NOT GIT_FOUND)
- set(${_var} "GIT-NOTFOUND" PARENT_SCOPE)
- return()
- endif()
-
- get_git_head_revision(refspec hash)
- if(NOT hash)
- set(${_var} "HEAD-HASH-NOTFOUND" PARENT_SCOPE)
- return()
- endif()
-
- execute_process(COMMAND
- ${GIT_EXECUTABLE}
- describe
- ${hash}
- ${ARGN}
- WORKING_DIRECTORY
- ${GIT_DIR}
- RESULT_VARIABLE
- res
- OUTPUT_VARIABLE
- out
- ERROR_QUIET
- OUTPUT_STRIP_TRAILING_WHITESPACE)
- if(NOT res EQUAL 0)
- set(out "${out}-${res}-NOTFOUND")
- endif()
-
- set(${_var} ${out} PARENT_SCOPE)
-endfunction()
-
-function(git_timestamp _var)
- get_git_dir(GIT_DIR)
- if(NOT GIT_DIR)
- return()
- endif()
-
- if(NOT GIT_FOUND)
- find_package(Git QUIET)
- endif()
- if(NOT GIT_FOUND)
- return()
- endif()
-
- get_git_head_revision(refspec hash)
- if(NOT hash)
- set(${_var} "HEAD-HASH-NOTFOUND" PARENT_SCOPE)
- return()
- endif()
-
- execute_process(COMMAND ${GIT_EXECUTABLE} log -1 --format="%ci" ${hash} ${ARGN}
- WORKING_DIRECTORY ${GIT_DIR}
- RESULT_VARIABLE res
- OUTPUT_VARIABLE out
- ERROR_QUIET
- OUTPUT_STRIP_TRAILING_WHITESPACE)
- if(res EQUAL 0)
- string(REGEX REPLACE "[-\" :]" "" out ${out})
- string(SUBSTRING ${out} 0 12 out)
- else()
- set(out "${out}-${res}-NOTFOUND")
- endif()
-
- set(${_var} ${out} PARENT_SCOPE)
-endfunction()
-
-function(git_get_exact_tag _var)
- git_describe(out --exact-match ${ARGN})
- set(${_var} ${out} PARENT_SCOPE)
-endfunction()
diff --git a/cmake/GetGitRevisionDescription.cmake.in b/cmake/GetGitRevisionDescription.cmake.in
deleted file mode 100644
index 8a085b2671..0000000000
--- a/cmake/GetGitRevisionDescription.cmake.in
+++ /dev/null
@@ -1,38 +0,0 @@
-#
-# Internal file for GetGitRevisionDescription.cmake
-#
-# Requires CMake 2.6 or newer (uses the 'function' command)
-#
-# Original Author:
-# 2009-2010 Ryan Pavlik <rpavlik@iastate.edu> <abiryan@ryand.net>
-# http://academic.cleardefinition.com
-# Iowa State University HCI Graduate Program/VRAC
-#
-# Copyright Iowa State University 2009-2010.
-# Distributed under the Boost Software License, Version 1.0.
-# (See accompanying file LICENSE_1_0.txt or copy at
-# http://www.boost.org/LICENSE_1_0.txt)
-
-set(HEAD_HASH)
-
-file(READ "@HEAD_FILE@" HEAD_CONTENTS LIMIT 1024)
-
-string(STRIP "${HEAD_CONTENTS}" HEAD_CONTENTS)
-if(HEAD_CONTENTS MATCHES "ref")
- # named branch
- string(REPLACE "ref: " "" HEAD_REF "${HEAD_CONTENTS}")
- if(EXISTS "@GIT_DIR@/${HEAD_REF}")
- configure_file("@GIT_DIR@/${HEAD_REF}" "@GIT_DATA@/head-ref" COPYONLY)
- elseif(EXISTS "@GIT_DIR@/logs/${HEAD_REF}")
- configure_file("@GIT_DIR@/logs/${HEAD_REF}" "@GIT_DATA@/head-ref" COPYONLY)
- set(HEAD_HASH "${HEAD_REF}")
- endif()
-else()
- # detached HEAD
- configure_file("@GIT_DIR@/HEAD" "@GIT_DATA@/head-ref" COPYONLY)
-endif()
-
-if(NOT HEAD_HASH)
- file(READ "@GIT_DATA@/head-ref" HEAD_HASH LIMIT 1024)
- string(STRIP "${HEAD_HASH}" HEAD_HASH)
-endif()
diff --git a/codecov.yml b/codecov.yml
index a83fd916ee..0f867db668 100644
--- a/codecov.yml
+++ b/codecov.yml
@@ -25,3 +25,6 @@ coverage:
changes: no
comment: off
+
+ignore:
+ - "src/tree_sitter"
diff --git a/config/CMakeLists.txt b/config/CMakeLists.txt
index 0ca41d5dfd..6c9e06d59d 100644
--- a/config/CMakeLists.txt
+++ b/config/CMakeLists.txt
@@ -12,7 +12,7 @@ check_type_size("size_t" SIZEOF_SIZE_T)
check_type_size("long long" SIZEOF_LONG_LONG)
check_type_size("void *" SIZEOF_VOID_PTR)
-if (CMAKE_HOST_SYSTEM_VERSION MATCHES ".*-Microsoft")
+if (CMAKE_HOST_SYSTEM_VERSION MATCHES ".*-(Microsoft|microsoft-standard)")
# Windows Subsystem for Linux
set(HAVE_WSL 1)
endif()
diff --git a/config/pathdef.c.in b/config/pathdef.c.in
index 41950f5ac5..6a8a2b205a 100644
--- a/config/pathdef.c.in
+++ b/config/pathdef.c.in
@@ -3,5 +3,6 @@
#include "${PROJECT_SOURCE_DIR}/src/nvim/vim.h"
char *default_vim_dir = "${CMAKE_INSTALL_FULL_DATAROOTDIR}/nvim";
char *default_vimruntime_dir = "";
+char *default_lib_dir = "${CMAKE_INSTALL_FULL_LIBDIR}/nvim";
char_u *compiled_user = (char_u *)"${USERNAME}";
char_u *compiled_sys = (char_u *)"${HOSTNAME}";
diff --git a/config/versiondef.h.in b/config/versiondef.h.in
index b9565735b3..22cad87249 100644
--- a/config/versiondef.h.in
+++ b/config/versiondef.h.in
@@ -5,7 +5,11 @@
#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@
diff --git a/contrib/local.mk.example b/contrib/local.mk.example
index 5a31ded59b..778e848d60 100644
--- a/contrib/local.mk.example
+++ b/contrib/local.mk.example
@@ -25,6 +25,12 @@
#
# CMAKE_BUILD_TYPE := Debug
+# With non-Debug builds interprocedural optimization (IPO) (which includes
+# link-time optimization (LTO)) is enabled by default, which causes the link
+# step to take a significant amout of time, which is relevant when building
+# 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
@@ -42,6 +48,7 @@
# 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
#
# Or disable all bundled dependencies at once.
#
diff --git a/man/nvim.1 b/man/nvim.1
index bc11739747..9e7da629f7 100644
--- a/man/nvim.1
+++ b/man/nvim.1
@@ -378,7 +378,7 @@ See also
System-global
.Nm
configuration file.
-.It Pa /usr/local/share/nvim
+.It Pa $VIM
System-global
.Nm
runtime directory.
diff --git a/runtime/autoload/health/nvim.vim b/runtime/autoload/health/nvim.vim
index c25f5ee64f..f18801ea69 100644
--- a/runtime/autoload/health/nvim.vim
+++ b/runtime/autoload/health/nvim.vim
@@ -129,6 +129,25 @@ function! s:check_performance() abort
endif
endfunction
+function! s:get_tmux_option(option) abort
+ let cmd = 'tmux show-option -qvg '.a:option " try global scope
+ let out = system(cmd)
+ let val = substitute(out, '\v(\s|\r|\n)', '', 'g')
+ if v:shell_error
+ call health#report_error('command failed: '.cmd."\n".out)
+ return 'error'
+ elseif empty(val)
+ let cmd = 'tmux show-option -qvgs '.a:option " try session scope
+ let out = system(cmd)
+ let val = substitute(out, '\v(\s|\r|\n)', '', 'g')
+ if v:shell_error
+ call health#report_error('command failed: '.cmd."\n".out)
+ return 'error'
+ endif
+ endif
+ return val
+endfunction
+
function! s:check_tmux() abort
if empty($TMUX) || !executable('tmux')
return
@@ -136,20 +155,31 @@ function! s:check_tmux() abort
call health#report_start('tmux')
" check escape-time
- let suggestions = ["Set escape-time in ~/.tmux.conf:\nset-option -sg escape-time 10",
+ let suggestions = ["set escape-time in ~/.tmux.conf:\nset-option -sg escape-time 10",
\ s:suggest_faq]
- let cmd = 'tmux show-option -qvgs escape-time'
- let out = system(cmd)
- let tmux_esc_time = substitute(out, '\v(\s|\r|\n)', '', 'g')
- if v:shell_error
- call health#report_error('command failed: '.cmd."\n".out)
- elseif empty(tmux_esc_time)
- call health#report_error('escape-time is not set', suggestions)
- elseif tmux_esc_time > 300
- call health#report_error(
- \ 'escape-time ('.tmux_esc_time.') is higher than 300ms', suggestions)
- else
- call health#report_ok('escape-time: '.tmux_esc_time.'ms')
+ let tmux_esc_time = s:get_tmux_option('escape-time')
+ if tmux_esc_time !=# 'error'
+ if empty(tmux_esc_time)
+ call health#report_error('`escape-time` is not set', suggestions)
+ elseif tmux_esc_time > 300
+ call health#report_error(
+ \ '`escape-time` ('.tmux_esc_time.') is higher than 300ms', suggestions)
+ else
+ call health#report_ok('escape-time: '.tmux_esc_time)
+ endif
+ endif
+
+ " check focus-events
+ let suggestions = ["(tmux 1.9+ only) Set `focus-events` in ~/.tmux.conf:\nset-option -g focus-events on"]
+ let tmux_focus_events = s:get_tmux_option('focus-events')
+ call health#report_info('Checking stuff')
+ if tmux_focus_events !=# 'error'
+ if empty(tmux_focus_events) || tmux_focus_events !=# 'on'
+ call health#report_warn(
+ \ "`focus-events` is not enabled. |'autoread'| may not work.", suggestions)
+ else
+ call health#report_ok('focus-events: '.tmux_focus_events)
+ endif
endif
" check default-terminal and $TERM
@@ -203,9 +233,9 @@ function! s:check_terminal() abort
call health#report_error('command failed: '.cmd."\n".out)
else
call health#report_info('key_backspace (kbs) terminfo entry: '
- \ .(empty(kbs_entry) ? '? (not found)' : kbs_entry))
+ \ .(empty(kbs_entry) ? '? (not found)' : kbs_entry))
call health#report_info('key_dc (kdch1) terminfo entry: '
- \ .(empty(kbs_entry) ? '? (not found)' : kdch1_entry))
+ \ .(empty(kbs_entry) ? '? (not found)' : kdch1_entry))
endif
for env_var in ['XTERM_VERSION', 'VTE_VERSION', 'TERM_PROGRAM', 'COLORTERM', 'SSH_TTY']
if exists('$'.env_var)
diff --git a/runtime/autoload/health/provider.vim b/runtime/autoload/health/provider.vim
index 87d82150b6..0482cb7f3c 100644
--- a/runtime/autoload/health/provider.vim
+++ b/runtime/autoload/health/provider.vim
@@ -38,9 +38,10 @@ endfunction
" Handler for s:system() function.
function! s:system_handler(jobid, data, event) dict abort
if a:event ==# 'stderr'
- let self.stderr .= join(a:data, '')
- if !self.ignore_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, '')
@@ -64,7 +65,7 @@ function! s:system(cmd, ...) abort
let stdin = a:0 ? a:1 : ''
let ignore_error = a:0 > 2 ? a:3 : 0
let opts = {
- \ 'ignore_stderr': a:0 > 1 ? a:2 : 0,
+ \ 'add_stderr_to_output': a:0 > 1 ? a:2 : 0,
\ 'output': '',
\ 'stderr': '',
\ 'on_stdout': function('s:system_handler'),
@@ -89,8 +90,15 @@ function! s:system(cmd, ...) abort
call health#report_error(printf('Command timed out: %s', s:shellify(a:cmd)))
call jobstop(jobid)
elseif s:shell_error != 0 && !ignore_error
- call health#report_error(printf("Command error (job=%d, exit code %d): `%s` (in %s)\nOutput: %s\nStderr: %s",
- \ jobid, s:shell_error, s:shellify(a:cmd), string(getcwd()), opts.output, opts.stderr))
+ 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
@@ -155,7 +163,7 @@ function! s:check_clipboard() abort
endif
endfunction
-" Get the latest Neovim Python client (pynvim) version from PyPI.
+" 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')
@@ -172,7 +180,7 @@ endfunction
" Get version information using the specified interpreter. The interpreter is
" used directly in case breaking changes were introduced since the last time
-" Neovim's Python client was updated.
+" Nvim's Python client was updated.
"
" Returns: [
" {python executable version},
@@ -194,7 +202,8 @@ function! s:version_info(python) abort
let nvim_path = s:trim(s:system([
\ a:python, '-c',
- \ 'import sys; sys.path.remove(""); ' .
+ \ 'import sys; ' .
+ \ 'sys.path = list(filter(lambda x: x != "", sys.path)); ' .
\ 'import neovim; print(neovim.__file__)']))
if s:shell_error || empty(nvim_path)
return [python_version, 'unable to load neovim Python module', pypi_version,
@@ -215,7 +224,7 @@ function! s:version_info(python) abort
\ 'print("{}.{}.{}{}".format(v.major, v.minor, v.patch, v.prerelease))'],
\ '', 1, 1)
if empty(nvim_version)
- let nvim_version = 'unable to find neovim Python module 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)
@@ -257,6 +266,22 @@ function! s:check_bin(bin) abort
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(version) abort
call health#report_start('Python ' . a:version . ' provider (optional)')
@@ -264,11 +289,10 @@ function! s:check_python(version) abort
let python_exe = ''
let venv = exists('$VIRTUAL_ENV') ? resolve($VIRTUAL_ENV) : ''
let host_prog_var = pyname.'_host_prog'
- let loaded_var = 'g:loaded_'.pyname.'_provider'
let python_multiple = []
- if exists(loaded_var) && !exists('*provider#'.pyname.'#Call')
- call health#report_info('Disabled ('.loaded_var.'='.eval(loaded_var).'). This might be due to some previous error.')
+ if s:disabled_via_loaded_var(pyname)
+ return
endif
let [pyenv, pyenv_root] = s:check_for_pyenv()
@@ -286,7 +310,7 @@ function! s:check_python(version) abort
let python_exe = pyname
endif
- " No Python executable could `import neovim`.
+ " No Python executable could `import neovim`, or host_prog_var was used.
if !empty(pythonx_errors)
call health#report_error('Python provider error:', pythonx_errors)
@@ -339,7 +363,7 @@ function! s:check_python(version) abort
\ && !empty(pyenv_root) && resolve(python_exe) !~# '^'.pyenv_root.'/'
call health#report_warn('pyenv is not set up optimally.', [
\ printf('Create a virtualenv specifically '
- \ . 'for Neovim using pyenv, and set `g:%s`. This will avoid '
+ \ . '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)
\ ])
@@ -353,7 +377,7 @@ function! s:check_python(version) abort
if resolve(python_exe) !~# '^'.venv_root.'/'
call health#report_warn('Your virtualenv is not set up optimally.', [
\ printf('Create a virtualenv specifically '
- \ . 'for Neovim and use `g:%s`. This will avoid '
+ \ . 'for Nvim and use `g:%s`. This will avoid '
\ . 'the need to install the pynvim module in each '
\ . 'virtualenv.', host_prog_var)
\ ])
@@ -368,18 +392,6 @@ function! s:check_python(version) abort
let python_exe = ''
endif
- " Check if $VIRTUAL_ENV is valid.
- if exists('$VIRTUAL_ENV') && !empty(python_exe)
- if $VIRTUAL_ENV ==# matchstr(python_exe, '^\V'.$VIRTUAL_ENV)
- call health#report_info('$VIRTUAL_ENV matches executable')
- else
- call health#report_warn(
- \ '$VIRTUAL_ENV exists but appears to be inactive. '
- \ . 'This could lead to unexpected results.',
- \ [ 'If you are using Zsh, see: http://vi.stackexchange.com/a/7654' ])
- endif
- endif
-
" Diagnostic output
call health#report_info('Executable: ' . (empty(python_exe) ? 'Not found' : python_exe))
if len(python_multiple)
@@ -473,12 +485,83 @@ function! s:check_for_pyenv() abort
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(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/7654'
+ 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)')
- let loaded_var = 'g:loaded_ruby_provider'
- if exists(loaded_var) && !exists('*provider#ruby#Call')
- call health#report_info('Disabled. '.loaded_var.'='.eval(loaded_var))
+ if s:disabled_via_loaded_var('ruby')
return
endif
@@ -501,7 +584,7 @@ function! s:check_ruby() abort
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_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,
@@ -509,7 +592,7 @@ function! s:check_ruby() abort
\ 'Are you behind a firewall or proxy?'])
return
endif
- let latest_gem = get(split(latest_gem, 'neovim (\|, \|)$' ), 1, 'not found')
+ 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)
@@ -532,9 +615,7 @@ endfunction
function! s:check_node() abort
call health#report_start('Node.js provider (optional)')
- let loaded_var = 'g:loaded_node_provider'
- if exists(loaded_var) && !exists('*provider#node#Call')
- call health#report_info('Disabled. '.loaded_var.'='.eval(loaded_var))
+ if s:disabled_via_loaded_var('node')
return
endif
@@ -546,8 +627,8 @@ function! s:check_node() abort
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('Neovim node.js host does not support '.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
@@ -562,7 +643,7 @@ function! s:check_node() abort
\ 'Run in shell (if you use yarn): yarn global add neovim'])
return
endif
- call health#report_info('Neovim node.js host: '. host)
+ call health#report_info('Nvim node.js host: '. host)
let manager = executable('npm') ? 'npm' : 'yarn'
let latest_npm_cmd = has('win32') ?
@@ -575,14 +656,12 @@ function! s:check_node() abort
\ 'Are you behind a firewall or proxy?'])
return
endif
- if !empty(latest_npm)
- 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')
- 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)
@@ -603,10 +682,83 @@ function! s:check_node() abort
endif
endfunction
+function! s:check_perl() abort
+ call health#report_start('Perl provider (optional)')
+
+ if s:disabled_via_loaded_var('perl')
+ return
+ endif
+
+ if !executable('perl') || !executable('cpanm')
+ call health#report_warn(
+ \ '`perl` and `cpanm` must be in $PATH.',
+ \ ['Install Perl and cpanminus and verify that `perl` and `cpanm` commands work.'])
+ return
+ endif
+ let perl_v = get(split(s:system(['perl', '-W', '-e', 'print $^V']), "\n"), 0, '')
+ call health#report_info('Perl: '. perl_v)
+ if s:shell_error
+ call health#report_warn('Nvim perl host does not support '.perl_v)
+ " Skip further checks, they are nonsense if perl is too old.
+ return
+ endif
+
+ let host = provider#perl#Detect()
+ if empty(host)
+ call health#report_warn('Missing "Neovim::Ext" cpan module.',
+ \ ['Run in shell: cpanm Neovim::Ext'])
+ return
+ endif
+ call health#report_info('Nvim perl host: '. host)
+
+ let latest_cpan_cmd = 'cpanm --info -q Neovim::Ext'
+ let latest_cpan = s:system(latest_cpan_cmd)
+ if s:shell_error || empty(latest_cpan)
+ call health#report_error('Failed to run: '. 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 = [host, '-W', '-MNeovim::Ext', '-e', 'print $Neovim::Ext::VERSION']
+ let current_cpan = s:system(current_cpan_cmd)
+ if s:shell_error
+ call health#report_error('Failed to run: '. string(current_cpan_cmd),
+ \ ['Report this issue with the output of: ', string(current_cpan_cmd)])
+ 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 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(2)
call s:check_python(3)
+ call s:check_virtualenv()
call s:check_ruby()
call s:check_node()
+ call s:check_perl()
endfunction
diff --git a/runtime/autoload/man.vim b/runtime/autoload/man.vim
index 153f1afed8..dab88fde23 100644
--- a/runtime/autoload/man.vim
+++ b/runtime/autoload/man.vim
@@ -1,4 +1,4 @@
-" Maintainer: Anmol Sethi <anmol@aubble.com>
+" Maintainer: Anmol Sethi <hi@nhooyr.io>
if exists('s:loaded_man')
finish
@@ -7,22 +7,10 @@ let s:loaded_man = 1
let s:find_arg = '-w'
let s:localfile_arg = v:true " Always use -l if possible. #6683
-let s:section_arg = '-s'
+let s:section_arg = '-S'
-function! s:init_section_flag()
- call system(['env', 'MANPAGER=cat', 'man', s:section_arg, '1', 'man'])
- if v:shell_error
- let s:section_arg = '-S'
- endif
-endfunction
-
-function! s:init() abort
- call s:init_section_flag()
- " TODO(nhooyr): Does `man -l` on SunOS list searched directories?
+function! man#init() abort
try
- if !has('win32') && $OSTYPE !~? 'cygwin\|linux' && system('uname -s') =~? 'SunOS' && system('uname -r') =~# '^5'
- let s:find_arg = '-l'
- endif
" Check for -l support.
call s:get_page(s:get_path('', 'man'))
catch /E145:/
@@ -52,51 +40,40 @@ function! man#open_page(count, count1, mods, ...) abort
let ref = a:2.'('.a:1.')'
endif
try
- let [sect, name] = man#extract_sect_and_name_ref(ref)
+ let [sect, name] = s:extract_sect_and_name_ref(ref)
if a:count ==# a:count1
" v:count defaults to 0 which is a valid section, and v:count1 defaults to
" 1, also a valid section. If they are equal, count explicitly set.
let sect = string(a:count)
endif
- let [sect, name, path] = s:verify_exists(sect, name)
+ let path = s:verify_exists(sect, name)
+ let [sect, name] = s:extract_sect_and_name_path(path)
catch
call s:error(v:exception)
return
endtry
- call s:push_tag()
- let bufname = 'man://'.name.(empty(sect)?'':'('.sect.')')
-
+ let [l:buf, l:save_tfu] = [bufnr(), &tagfunc]
try
- set eventignore+=BufReadCmd
+ set tagfunc=man#goto_tag
+ let l:target = l:name . '(' . l:sect . ')'
if a:mods !~# 'tab' && s:find_man()
- execute 'silent keepalt edit' fnameescape(bufname)
+ execute 'silent keepalt tag' l:target
else
- execute 'silent keepalt' a:mods 'split' fnameescape(bufname)
+ execute 'silent keepalt' a:mods 'stag' l:target
endif
finally
- set eventignore-=BufReadCmd
- endtry
-
- try
- let page = s:get_page(path)
- catch
- if a:mods =~# 'tab' || !s:find_man()
- " a new window was opened
- close
- endif
- call s:error(v:exception)
- return
+ call setbufvar(l:buf, '&tagfunc', l:save_tfu)
endtry
let b:man_sect = sect
- call s:put_page(page)
endfunction
function! man#read_page(ref) abort
try
- let [sect, name] = man#extract_sect_and_name_ref(a:ref)
- let [sect, name, path] = s:verify_exists(sect, name)
+ let [sect, name] = s:extract_sect_and_name_ref(a:ref)
+ let path = s:verify_exists(sect, name)
+ let [sect, name] = s:extract_sect_and_name_path(path)
let page = s:get_page(path)
catch
call s:error(v:exception)
@@ -152,7 +129,7 @@ function! s:get_page(path) abort
" Disable hard-wrap by using a big $MANWIDTH (max 1000 on some systems #9065).
" Soft-wrap: ftplugin/man.vim sets wrap/breakindent/….
" Hard-wrap: driven by `man`.
- let manwidth = !get(g:,'man_hardwrap') ? 999 : (empty($MANWIDTH) ? winwidth(0) : $MANWIDTH)
+ let manwidth = !get(g:, 'man_hardwrap', 1) ? 999 : (empty($MANWIDTH) ? winwidth(0) : $MANWIDTH)
" Force MANPAGER=cat to ensure Vim is not recursively invoked (by man-db).
" http://comments.gmane.org/gmane.editors.vim.devel/29085
" Set MAN_KEEP_FORMATTING so Debian man doesn't discard backspaces.
@@ -163,6 +140,9 @@ endfunction
function! s:put_page(page) abort
setlocal modifiable
setlocal noreadonly
+ setlocal noswapfile
+ " git-ls-files(1) is all one keyword/tag-target
+ setlocal iskeyword+=(,)
silent keepjumps %delete _
silent put =a:page
while getline(1) =~# '^\s*$'
@@ -204,7 +184,7 @@ endfunction
" attempt to extract the name and sect out of 'name(sect)'
" otherwise just return the largest string of valid characters in ref
-function! man#extract_sect_and_name_ref(ref) abort
+function! s:extract_sect_and_name_ref(ref) abort
if a:ref[0] ==# '-' " try ':Man -pandoc' with this disabled.
throw 'manpage name cannot start with ''-'''
endif
@@ -214,7 +194,7 @@ function! man#extract_sect_and_name_ref(ref) abort
if empty(name)
throw 'manpage reference cannot contain only parentheses'
endif
- return [get(b:, 'man_default_sects', ''), name]
+ return ['', name]
endif
let left = split(ref, '(')
" see ':Man 3X curses' on why tolower.
@@ -237,42 +217,62 @@ function! s:get_path(sect, name) abort
return substitute(get(split(s:system(['man', s:find_arg, s:section_arg, a:sect, a:name])), 0, ''), '\n\+$', '', '')
endfunction
+" s:verify_exists attempts to find the path to a manpage
+" based on the passed section and name.
+"
+" 1. If the passed section is empty, b:man_default_sects is used.
+" 2. If manpage could not be found with the given sect and name,
+" then another attempt is made with b:man_default_sects.
+" 3. If it still could not be found, then we try again without a section.
+" 4. If still not found but $MANSECT is set, then we try again with $MANSECT
+" unset.
+"
+" This function is careful to avoid duplicating a search if a previous
+" step has already done it. i.e if we use b:man_default_sects in step 1,
+" then we don't do it again in step 2.
function! s:verify_exists(sect, name) abort
+ let sect = a:sect
+ if empty(sect)
+ let sect = get(b:, 'man_default_sects', '')
+ endif
+
try
- let path = s:get_path(a:sect, a:name)
+ return s:get_path(sect, a:name)
catch /^command error (/
+ endtry
+
+ if !empty(get(b:, 'man_default_sects', '')) && sect !=# b:man_default_sects
try
- let path = s:get_path(get(b:, 'man_default_sects', ''), a:name)
+ return s:get_path(b:man_default_sects, a:name)
catch /^command error (/
- let path = s:get_path('', a:name)
endtry
- endtry
- " Extract the section from the path, because sometimes the actual section is
- " 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'.
- return s:extract_sect_and_name_path(path) + [path]
-endfunction
-
-let s:tag_stack = []
+ endif
-function! s:push_tag() abort
- let s:tag_stack += [{
- \ 'buf': bufnr('%'),
- \ 'lnum': line('.'),
- \ 'col': col('.'),
- \ }]
-endfunction
+ if !empty(sect)
+ try
+ return s:get_path('', a:name)
+ catch /^command error (/
+ endtry
+ endif
-function! man#pop_tag() abort
- if !empty(s:tag_stack)
- let tag = remove(s:tag_stack, -1)
- execute 'silent' tag['buf'].'buffer'
- call cursor(tag['lnum'], tag['col'])
+ if !empty($MANSECT)
+ try
+ let MANSECT = $MANSECT
+ unset $MANSECT
+ return s:get_path('', a:name)
+ catch /^command error (/
+ finally
+ let $MANSECT = MANSECT
+ endtry
endif
+
+ throw 'no manual entry for ' . a:name
endfunction
-" extracts the name and sect out of 'path/name.sect'
+" Extracts the name/section from the 'path/name.sect', because sometimes the actual section is
+" 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'.
function! s:extract_sect_and_name_path(path) abort
let tail = fnamemodify(a:path, ':t')
if a:path =~# '\.\%([glx]z\|bz2\|lzma\|Z\)$' " valid extensions
@@ -284,20 +284,16 @@ function! s:extract_sect_and_name_path(path) abort
endfunction
function! s:find_man() abort
- if &filetype ==# 'man'
- return 1
- elseif winnr('$') ==# 1
- return 0
- endif
- let thiswin = winnr()
- while 1
- wincmd w
- if &filetype ==# 'man'
+ let l:win = 1
+ while l:win <= winnr('$')
+ let l:buf = winbufnr(l:win)
+ if getbufvar(l:buf, '&filetype', '') ==# 'man'
+ execute l:win.'wincmd w'
return 1
- elseif thiswin ==# winnr()
- return 0
endif
+ let l:win += 1
endwhile
+ return 0
endfunction
function! s:error(msg) abort
@@ -307,7 +303,7 @@ function! s:error(msg) abort
echohl None
endfunction
-" see man#extract_sect_and_name_ref on why tolower(sect)
+" see s:extract_sect_and_name_ref on why tolower(sect)
function! man#complete(arg_lead, cmd_line, cursor_pos) abort
let args = split(a:cmd_line)
let cmd_offset = index(args, 'Man')
@@ -360,14 +356,35 @@ function! man#complete(arg_lead, cmd_line, cursor_pos) abort
return s:complete(sect, sect, name)
endfunction
-function! s:complete(sect, psect, name) abort
+function! s:get_paths(sect, name, do_fallback) abort
+ " callers must try-catch this, as some `man` implementations don't support `s:find_arg`
try
let mandirs = join(split(s:system(['man', s:find_arg]), ':\|\n'), ',')
+ let paths = globpath(mandirs, 'man?/'.a:name.'*.'.a:sect.'*', 0, 1)
+ try
+ " Prioritize the result from verify_exists as it obeys b:man_default_sects.
+ let first = s:verify_exists(a:sect, a:name)
+ let paths = filter(paths, 'v:val !=# first')
+ let paths = [first] + paths
+ catch
+ endtry
+ return paths
catch
- call s:error(v:exception)
- return
+ if !a:do_fallback
+ throw v:exception
+ endif
+
+ " Fallback to a single path, with the page we're trying to find.
+ try
+ return [s:verify_exists(a:sect, a:name)]
+ catch
+ return []
+ endtry
endtry
- let pages = globpath(mandirs,'man?/'.a:name.'*.'.a:sect.'*', 0, 1)
+endfunction
+
+function! s:complete(sect, psect, name) abort
+ let pages = s:get_paths(a:sect, a:name, v:false)
" We remove duplicates in case the same manpage in different languages was found.
return uniq(sort(map(pages, 's:format_candidate(v:val, a:psect)'), 'i'))
endfunction
@@ -387,6 +404,10 @@ function! s:format_candidate(path, psect) abort
endfunction
function! man#init_pager() abort
+ " https://github.com/neovim/neovim/issues/6828
+ let og_modifiable = &modifiable
+ setlocal modifiable
+
if getline(1) =~# '^\s*$'
silent keepjumps 1delete _
else
@@ -397,13 +418,40 @@ function! man#init_pager() abort
" know the correct casing, cf. `man glDrawArraysInstanced`).
let ref = substitute(matchstr(getline(1), '^[^)]\+)'), ' ', '_', 'g')
try
- let b:man_sect = man#extract_sect_and_name_ref(ref)[0]
+ let b:man_sect = s:extract_sect_and_name_ref(ref)[0]
catch
let b:man_sect = ''
endtry
if -1 == match(bufname('%'), 'man:\/\/') " Avoid duplicate buffers, E95.
execute 'silent file man://'.tolower(fnameescape(ref))
endif
+
+ let &l:modifiable = og_modifiable
+endfunction
+
+function! man#goto_tag(pattern, flags, info) abort
+ let [l:sect, l:name] = s:extract_sect_and_name_ref(a:pattern)
+
+ let l:paths = s:get_paths(l:sect, l:name, v:true)
+ let l:structured = []
+
+ for l:path in l:paths
+ let l:n = s:extract_sect_and_name_path(l:path)[1]
+ let l:structured += [{ 'name': l:n, 'path': l:path }]
+ endfor
+
+ if &cscopetag
+ " return only a single entry so we work well with :cstag (#11675)
+ let l:structured = l:structured[:0]
+ endif
+
+ return map(l:structured, {
+ \ _, entry -> {
+ \ 'name': entry.name,
+ \ 'filename': 'man://' . entry.path,
+ \ 'cmd': '1'
+ \ }
+ \ })
endfunction
-call s:init()
+call man#init()
diff --git a/runtime/autoload/netrw.vim b/runtime/autoload/netrw.vim
index a5b47e06d5..b69ad7187a 100644
--- a/runtime/autoload/netrw.vim
+++ b/runtime/autoload/netrw.vim
@@ -688,10 +688,6 @@ fun! netrw#Explore(indx,dosplit,style,...)
endif
" save registers
- if has("clipboard")
- sil! let keepregstar = @*
- sil! let keepregplus = @+
- endif
sil! let keepregslash= @/
" if dosplit
@@ -915,10 +911,6 @@ fun! netrw#Explore(indx,dosplit,style,...)
" call Decho("..case Nexplore with starpat=".starpat.": (indx=".indx.")",'~'.expand("<slnum>"))
if !exists("w:netrw_explore_list") " sanity check
NetrwKeepj call netrw#ErrorMsg(s:WARNING,"using Nexplore or <s-down> improperly; see help for netrw-starstar",40)
- if has("clipboard")
- sil! let @* = keepregstar
- sil! let @+ = keepregplus
- endif
sil! let @/ = keepregslash
" call Dret("netrw#Explore")
return
@@ -940,10 +932,6 @@ fun! netrw#Explore(indx,dosplit,style,...)
" call Decho("case Pexplore with starpat=".starpat.": (indx=".indx.")",'~'.expand("<slnum>"))
if !exists("w:netrw_explore_list") " sanity check
NetrwKeepj call netrw#ErrorMsg(s:WARNING,"using Pexplore or <s-up> improperly; see help for netrw-starstar",41)
- if has("clipboard")
- sil! let @* = keepregstar
- sil! let @+ = keepregplus
- endif
sil! let @/ = keepregslash
" call Dret("netrw#Explore")
return
@@ -995,10 +983,6 @@ fun! netrw#Explore(indx,dosplit,style,...)
catch /^Vim\%((\a\+)\)\=:E480/
keepalt call netrw#ErrorMsg(s:WARNING,'no files matched pattern<'.pattern.'>',45)
if &hls | let keepregslash= s:ExplorePatHls(pattern) | endif
- if has("clipboard")
- sil! let @* = keepregstar
- sil! let @+ = keepregplus
- endif
sil! let @/ = keepregslash
" call Dret("netrw#Explore : no files matched pattern")
return
@@ -1031,10 +1015,6 @@ fun! netrw#Explore(indx,dosplit,style,...)
if w:netrw_explore_listlen == 0 || (w:netrw_explore_listlen == 1 && w:netrw_explore_list[0] =~ '\*\*\/')
keepalt NetrwKeepj call netrw#ErrorMsg(s:WARNING,"no files matched",42)
- if has("clipboard")
- sil! let @* = keepregstar
- sil! let @+ = keepregplus
- endif
sil! let @/ = keepregslash
" call Dret("netrw#Explore : no files matched")
return
@@ -1079,10 +1059,6 @@ fun! netrw#Explore(indx,dosplit,style,...)
if !exists("g:netrw_quiet")
keepalt NetrwKeepj call netrw#ErrorMsg(s:WARNING,"your vim needs the +path_extra feature for Exploring with **!",44)
endif
- if has("clipboard")
- sil! let @* = keepregstar
- sil! let @+ = keepregplus
- endif
sil! let @/ = keepregslash
" call Dret("netrw#Explore : missing +path_extra")
return
@@ -1152,10 +1128,6 @@ fun! netrw#Explore(indx,dosplit,style,...)
" there's no danger of a late FocusGained event on initialization.
" Consequently, set s:netrw_events to 2.
let s:netrw_events= 2
- if has("clipboard")
- sil! let @* = keepregstar
- sil! let @+ = keepregplus
- endif
sil! let @/ = keepregslash
" call Dret("netrw#Explore : @/<".@/.">")
endfun
@@ -1676,10 +1648,6 @@ fun! s:NetrwOptionsSave(vt)
if g:netrw_keepdir
let {a:vt}netrw_dirkeep = getcwd()
endif
- if has("clipboard")
- sil! let {a:vt}netrw_starkeep = @*
- sil! let {a:vt}netrw_pluskeep = @+
- endif
sil! let {a:vt}netrw_slashkeep= @/
" 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>"))
@@ -1828,10 +1796,6 @@ fun! s:NetrwOptionsRestore(vt)
unlet {a:vt}netrw_dirkeep
endif
endif
- if has("clipboard")
- call s:NetrwRestoreSetting(a:vt."netrw_starkeep","@*")
- call s:NetrwRestoreSetting(a:vt."netrw_pluskeep","@+")
- endif
call s:NetrwRestoreSetting(a:vt."netrw_slashkeep","@/")
" call Decho("g:netrw_keepdir=".g:netrw_keepdir.": getcwd<".getcwd()."> acd=".&acd,'~'.expand("<slnum>"))
@@ -5496,6 +5460,11 @@ fun! netrw#CheckIfRemote(...)
else
let curfile= expand("%")
endif
+
+ " Ignore terminal buffers
+ if &buftype ==# 'terminal'
+ return 0
+ endif
" call Decho("curfile<".curfile.">")
if curfile =~ '^\a\{3,}://'
" call Dret("netrw#CheckIfRemote 1")
@@ -9559,10 +9528,6 @@ fun! s:NetrwWideListing()
let newcolstart = w:netrw_bannercnt + fpc
let newcolend = newcolstart + fpc - 1
" call Decho("bannercnt=".w:netrw_bannercnt." fpl=".w:netrw_fpl." fpc=".fpc." newcol[".newcolstart.",".newcolend."]",'~'.expand("<slnum>"))
- if has("clipboard")
- sil! let keepregstar = @*
- sil! let keepregplus = @+
- endif
while line("$") >= newcolstart
if newcolend > line("$") | let newcolend= line("$") | endif
let newcolqty= newcolend - newcolstart
@@ -9575,10 +9540,6 @@ fun! s:NetrwWideListing()
exe "sil! NetrwKeepj ".newcolstart.','.newcolend.'d _'
exe 'sil! NetrwKeepj '.w:netrw_bannercnt
endwhile
- if has("clipboard")
- sil! let @*= keepregstar
- sil! let @+= keepregplus
- endif
exe "sil! NetrwKeepj ".w:netrw_bannercnt.',$s/\s\+$//e'
NetrwKeepj call histdel("/",-1)
exe 'nno <buffer> <silent> w :call search(''^.\\|\s\s\zs\S'',''W'')'."\<cr>"
diff --git a/runtime/autoload/provider.vim b/runtime/autoload/provider.vim
index dc24e801d0..803c1a0b1c 100644
--- a/runtime/autoload/provider.vim
+++ b/runtime/autoload/provider.vim
@@ -3,8 +3,11 @@
" Start the provider and perform a 'poll' request
"
" Returns a valid channel on success
-function! provider#Poll(argv, orig_name, log_env) abort
+function! provider#Poll(argv, orig_name, log_env, ...) abort
let job = {'rpc': v:true, 'stderr_buffered': v:true}
+ if a:0
+ let job = extend(job, a:1)
+ endif
try
let channel_id = jobstart(a:argv, job)
if channel_id > 0 && rpcrequest(channel_id, 'poll') ==# 'ok'
diff --git a/runtime/autoload/provider/clipboard.vim b/runtime/autoload/provider/clipboard.vim
index e33dc31f6d..a96a0a61b7 100644
--- a/runtime/autoload/provider/clipboard.vim
+++ b/runtime/autoload/provider/clipboard.vim
@@ -113,8 +113,13 @@ function! provider#clipboard#Executable() abort
let s:paste['*'] = s:paste['+']
return 'doitclient'
elseif executable('win32yank.exe')
- let s:copy['+'] = 'win32yank.exe -i --crlf'
- let s:paste['+'] = 'win32yank.exe -o --lf'
+ if has('wsl') && getftype(exepath('win32yank.exe')) == 'link'
+ let win32yank = resolve(exepath('win32yank.exe'))
+ else
+ let win32yank = 'win32yank.exe'
+ endif
+ let s:copy['+'] = win32yank.' -i --crlf'
+ let s:paste['+'] = win32yank.' -o --lf'
let s:copy['*'] = s:copy['+']
let s:paste['*'] = s:paste['+']
return 'win32yank'
@@ -172,6 +177,10 @@ function! s:clipboard.set(lines, regtype, reg) abort
if jobid > 0
call jobsend(jobid, a:lines)
call jobclose(jobid, 'stdin')
+ " xclip does not close stdout when receiving input via stdin
+ if argv[0] ==# 'xclip'
+ call jobclose(jobid, 'stdout')
+ endif
let selection.owner = jobid
let ret = 1
else
diff --git a/runtime/autoload/provider/node.vim b/runtime/autoload/provider/node.vim
index b2a3b3ee08..c5d5e87729 100644
--- a/runtime/autoload/provider/node.vim
+++ b/runtime/autoload/provider/node.vim
@@ -51,6 +51,9 @@ function! provider#node#Detect() abort
if exists('g:node_host_prog')
return expand(g:node_host_prog)
endif
+ if !executable('node')
+ return ''
+ endif
if !s:is_minimum_version(v:null, 6, 0)
return ''
endif
diff --git a/runtime/autoload/provider/perl.vim b/runtime/autoload/provider/perl.vim
new file mode 100644
index 0000000000..36ca2bbf14
--- /dev/null
+++ b/runtime/autoload/provider/perl.vim
@@ -0,0 +1,69 @@
+if exists('s:loaded_perl_provider')
+ finish
+endif
+
+let s:loaded_perl_provider = 1
+
+function! provider#perl#Detect() abort
+ " use g:perl_host_prof if set or check if perl is on the path
+ let prog = exepath(get(g:, 'perl_host_prog', 'perl'))
+ if empty(prog)
+ return ''
+ endif
+
+ " if perl is available, make sure the required module is available
+ call system([prog, '-W', '-MNeovim::Ext', '-e', ''])
+ return v:shell_error ? '' : prog
+endfunction
+
+function! provider#perl#Prog() abort
+ return s:prog
+endfunction
+
+function! provider#perl#Require(host) abort
+ if s:err != ''
+ echoerr s:err
+ return
+ endif
+
+ let prog = provider#perl#Prog()
+ let args = [s:prog, '-e', 'use Neovim::Ext; start_host();']
+
+ " Collect registered perl plugins into args
+ let perl_plugins = remote#host#PluginsForHost(a:host.name)
+ for plugin in perl_plugins
+ call add(args, plugin.path)
+ endfor
+
+ return provider#Poll(args, a:host.orig_name, '$NVIM_PERL_LOG_FILE')
+endfunction
+
+function! provider#perl#Call(method, args) abort
+ if s:err != ''
+ echoerr s:err
+ return
+ endif
+
+ if !exists('s:host')
+ try
+ let s:host = remote#host#Require('perl')
+ catch
+ let s:err = v:exception
+ echohl WarningMsg
+ echomsg v:exception
+ echohl None
+ return
+ endtry
+ endif
+ return call('rpcrequest', insert(insert(a:args, 'perl_'.a:method), s:host))
+endfunction
+
+let s:err = ''
+let s:prog = provider#perl#Detect()
+let g:loaded_perl_provider = empty(s:prog) ? 1 : 2
+
+if g:loaded_perl_provider != 2
+ let s:err = 'Cannot find perl or the required perl module'
+endif
+
+call remote#host#RegisterPlugin('perl-provider', 'perl', [])
diff --git a/runtime/autoload/provider/pythonx.vim b/runtime/autoload/provider/pythonx.vim
index 59b1c27b72..e89d519790 100644
--- a/runtime/autoload/provider/pythonx.vim
+++ b/runtime/autoload/provider/pythonx.vim
@@ -10,7 +10,8 @@ function! provider#pythonx#Require(host) abort
" Python host arguments
let prog = (ver == '2' ? provider#python#Prog() : provider#python3#Prog())
- let args = [prog, '-c', 'import sys; sys.path.remove(""); import neovim; neovim.start_host()']
+ let args = [prog, '-c', 'import sys; sys.path = list(filter(lambda x: x != "", sys.path)); import neovim; neovim.start_host()']
+
" Collect registered Python plugins into args
let python_plugins = remote#host#PluginsForHost(a:host.name)
@@ -18,7 +19,7 @@ function! provider#pythonx#Require(host) abort
call add(args, plugin.path)
endfor
- return provider#Poll(args, a:host.orig_name, '$NVIM_PYTHON_LOG_FILE')
+ return provider#Poll(args, a:host.orig_name, '$NVIM_PYTHON_LOG_FILE', {'overlapped': v:true})
endfunction
function! s:get_python_executable_from_host_var(major_version) abort
@@ -28,8 +29,8 @@ endfunction
function! s:get_python_candidates(major_version) abort
return {
\ 2: ['python2', 'python2.7', 'python2.6', 'python'],
- \ 3: ['python3', 'python3.7', 'python3.6', 'python3.5', 'python3.4', 'python3.3',
- \ 'python']
+ \ 3: ['python3', 'python3.9', 'python3.8', 'python3.7', 'python3.6', 'python3.5',
+ \ 'python3.4', 'python3.3', 'python']
\ }[a:major_version]
endfunction
@@ -43,7 +44,7 @@ function! provider#pythonx#DetectByModule(module, major_version) abort
let python_exe = s:get_python_executable_from_host_var(a:major_version)
if !empty(python_exe)
- return [python_exe, '']
+ return [exepath(expand(python_exe)), '']
endif
let candidates = s:get_python_candidates(a:major_version)
@@ -66,7 +67,7 @@ endfunction
function! s:import_module(prog, module) abort
let prog_version = system([a:prog, '-c' , printf(
\ 'import sys; ' .
- \ 'sys.path.remove(""); ' .
+ \ 'sys.path = list(filter(lambda x: x != "", sys.path)); ' .
\ 'sys.stdout.write(str(sys.version_info[0]) + "." + str(sys.version_info[1])); ' .
\ 'import pkgutil; ' .
\ 'exit(2*int(pkgutil.get_loader("%s") is None))',
diff --git a/runtime/autoload/remote/define.vim b/runtime/autoload/remote/define.vim
index 2688a62a82..2aec96e365 100644
--- a/runtime/autoload/remote/define.vim
+++ b/runtime/autoload/remote/define.vim
@@ -24,7 +24,7 @@ function! remote#define#CommandOnHost(host, method, sync, name, opts)
endif
if has_key(a:opts, 'nargs')
- call add(forward_args, ' <args>')
+ call add(forward_args, ' " . <q-args> . "')
endif
exe s:GetCommandPrefix(a:name, a:opts)
diff --git a/runtime/autoload/remote/host.vim b/runtime/autoload/remote/host.vim
index 1cf328e08d..c34ff4bee7 100644
--- a/runtime/autoload/remote/host.vim
+++ b/runtime/autoload/remote/host.vim
@@ -203,3 +203,7 @@ call remote#host#Register('ruby', '*.rb',
" nodejs
call remote#host#Register('node', '*',
\ function('provider#node#Require'))
+
+" perl
+call remote#host#Register('perl', '*',
+ \ function('provider#perl#Require'))
diff --git a/runtime/autoload/spellfile.vim b/runtime/autoload/spellfile.vim
index c0ef51cdfe..d098902305 100644
--- a/runtime/autoload/spellfile.vim
+++ b/runtime/autoload/spellfile.vim
@@ -13,6 +13,13 @@ let s:spellfile_URL = '' " Start with nothing so that s:donedict is reset.
" This function is used for the spellfile plugin.
function! spellfile#LoadFile(lang)
+ " Check for sandbox/modeline. #11359
+ try
+ :!
+ catch /\<E12\>/
+ throw 'Cannot download spellfile in sandbox/modeline. Try ":set spell" from the cmdline.'
+ endtry
+
" If the netrw plugin isn't loaded we silently skip everything.
if !exists(":Nread")
if &verbose
diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt
index 98dd330b48..ea3a8242ae 100644
--- a/runtime/doc/api.txt
+++ b/runtime/doc/api.txt
@@ -19,6 +19,7 @@ API Usage *api-rpc* *RPC* *rpc*
*msgpack-rpc*
RPC is the typical way to control Nvim programmatically. Nvim implements the
MessagePack-RPC protocol:
+ https://github.com/msgpack-rpc/msgpack-rpc/blob/master/spec.md
https://github.com/msgpack/msgpack/blob/0b8f5ac/spec.md
Many clients use the API: user interfaces (GUIs), remote plugins, scripts like
@@ -182,21 +183,17 @@ External programs (clients) can use the metadata to discover the API, using
any of these approaches:
1. Connect to a running Nvim instance and call |nvim_get_api_info()| via
- msgpack-rpc. This is best for clients written in dynamic languages which
+ msgpack-RPC. This is best for clients written in dynamic languages which
can define functions at runtime.
- 2. Start Nvim with the |--api-info| option. Useful for clients written in
- statically-compiled languages.
-
- 3. Use the |api_info()| Vimscript function.
-
-Example: To get a human-readable list of API functions: >
- :new|put =map(filter(api_info().functions, '!has_key(v:val,''deprecated_since'')'), 'v:val.name')
-<
-Example: To get a formatted dump of the API using python (requires the
-"pyyaml" and "msgpack-python" modules): >
- nvim --api-info | python -c 'import msgpack, sys, yaml; print yaml.dump(msgpack.unpackb(sys.stdin.read()))'
+ 2. Start Nvim with |--api-info|. Useful for statically-compiled clients.
+ Example (requires Python "pyyaml" and "msgpack-python" modules): >
+ nvim --api-info | python -c 'import msgpack, sys, yaml; print yaml.dump(msgpack.unpackb(sys.stdin.read()))'
<
+ 3. Use the |api_info()| Vimscript function. >
+ :lua print(vim.inspect(vim.fn.api_info()))
+< Example using |filter()| to exclude non-deprecated API functions: >
+ :new|put =map(filter(api_info().functions, '!has_key(v:val,''deprecated_since'')'), 'v:val.name')
==============================================================================
API contract *api-contract*
@@ -439,141 +436,170 @@ Example: create a float with scratch buffer: >
>
==============================================================================
+Extended marks *api-extended-marks*
+
+Extended marks (extmarks) represent buffer annotations that track text changes
+in the buffer. They could be used to represent cursors, folds, misspelled
+words, and anything else that needs to track a logical location in the buffer
+over time.
+
+Example:
+
+We will set an extmark at the first row and third column. |api-indexing| is
+zero-indexed, so we use row=0 and column=2. Passing id=0 creates a new mark
+and returns the id: >
+
+ let g:mark_ns = nvim_create_namespace('myplugin')
+ let g:mark_id = nvim_buf_set_extmark(0, g:mark_ns, 0, 0, 2, {})
+
+We can get a mark by its id: >
+
+ echo nvim_buf_get_extmark_by_id(0, g:mark_ns, g:mark_id)
+ => [0, 2]
+
+We can get all marks in a buffer for our namespace (or by a range): >
+
+ echo nvim_buf_get_extmarks(0, g:mark_ns, 0, -1, {})
+ => [[1, 0, 2]]
+
+Deleting all text surrounding an extmark does not remove the extmark. To
+remove an extmark use |nvim_buf_del_extmark()|.
+
+Namespaces allow your plugin to manage only its own extmarks, ignoring those
+created by another plugin.
+
+Extmark positions changed by an edit will be restored on undo/redo. Creating
+and deleting extmarks is not a buffer change, thus new undo states are not
+created for extmark changes.
+
+==============================================================================
Global Functions *api-global*
-nvim_command({command}) *nvim_command()*
- Executes an ex-command.
+nvim__get_lib_dir() *nvim__get_lib_dir()*
+ TODO: Documentation
- On execution error: fails with VimL error, does not update
- v:errmsg.
+nvim__id({obj}) *nvim__id()*
+ Returns object given as argument.
+
+ This API function is used for testing. One should not rely on
+ its presence in plugins.
Parameters: ~
- {command} Ex-command string
+ {obj} Object to return.
-nvim_get_hl_by_name({name}, {rgb}) *nvim_get_hl_by_name()*
- Gets a highlight definition by name.
+ Return: ~
+ its argument.
+
+nvim__id_array({arr}) *nvim__id_array()*
+ Returns array given as argument.
+
+ This API function is used for testing. One should not rely on
+ its presence in plugins.
Parameters: ~
- {name} Highlight group name
- {rgb} Export RGB colors
+ {arr} Array to return.
Return: ~
- Highlight definition map
+ its argument.
- See also: ~
- nvim_get_hl_by_id
+nvim__id_dictionary({dct}) *nvim__id_dictionary()*
+ Returns dictionary given as argument.
-nvim_get_hl_by_id({hl_id}, {rgb}) *nvim_get_hl_by_id()*
- Gets a highlight definition by id. |hlID()|
+ This API function is used for testing. One should not rely on
+ its presence in plugins.
Parameters: ~
- {hl_id} Highlight id as returned by |hlID()|
- {rgb} Export RGB colors
+ {dct} Dictionary to return.
Return: ~
- Highlight definition map
-
- See also: ~
- nvim_get_hl_by_name
+ its argument.
-nvim_feedkeys({keys}, {mode}, {escape_csi}) *nvim_feedkeys()*
- Sends input-keys to Nvim, subject to various quirks controlled
- by `mode` flags. This is a blocking call, unlike
- |nvim_input()|.
+nvim__id_float({flt}) *nvim__id_float()*
+ Returns floating-point value given as argument.
- On execution error: does not fail, but updates v:errmsg.
+ This API function is used for testing. One should not rely on
+ its presence in plugins.
Parameters: ~
- {keys} to be typed
- {mode} behavior flags, see |feedkeys()|
- {escape_csi} If true, escape K_SPECIAL/CSI bytes in
- `keys`
+ {flt} Value to return.
- See also: ~
- feedkeys()
- vim_strsave_escape_csi
+ Return: ~
+ its argument.
-nvim_input({keys}) *nvim_input()*
- 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).
+nvim__inspect_cell({grid}, {row}, {col}) *nvim__inspect_cell()*
+ TODO: Documentation
- On execution error: does not fail, but updates v:errmsg.
+ *nvim__put_attr()*
+nvim__put_attr({id}, {start_row}, {start_col}, {end_row}, {end_col})
+ Set attrs in nvim__buf_set_lua_hl callbacks
- 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 "<LeftMouse><col,row>" is deprecated since
- |api-level| 6.
+ TODO(bfredl): This is rather pedestrian. The final interface
+ should probably be derived from a reformed bufhl/virttext
+ interface with full support for multi-line ranges etc
- Attributes: ~
- {fast}
+nvim__stats() *nvim__stats()*
+ Gets internal stats.
+
+ Return: ~
+ Map of various internal stats.
+
+nvim_call_atomic({calls}) *nvim_call_atomic()*
+ Calls many API methods atomically.
+
+ This has two main usages:
+ 1. To perform several requests from an async context
+ atomically, i.e. without interleaving redraws, RPC requests
+ from other clients, or user interactions (however API
+ methods may trigger autocommands or event processing which
+ have such side-effects, e.g. |:sleep| may wake timers).
+ 2. To minimize RPC overhead (roundtrips) of a sequence of many
+ requests.
Parameters: ~
- {keys} to be typed
+ {calls} an array of calls, where each call is described
+ by an array with two elements: the request name,
+ and an array of arguments.
Return: ~
- Number of bytes actually written (can be fewer than
- requested if the buffer becomes full).
+ Array of two elements. The first is an array of return
+ values. The second is NIL if all calls succeeded. If a
+ call resulted in an error, it is a three-element array
+ with the zero-based index of the call which resulted in an
+ error, the error type and the error message. If an error
+ occurred, the values from all preceding calls will still
+ be returned.
- *nvim_input_mouse()*
-nvim_input_mouse({button}, {action}, {modifier}, {grid}, {row}, {col})
- Send mouse event from GUI.
+ *nvim_call_dict_function()*
+nvim_call_dict_function({dict}, {fn}, {args})
+ Calls a VimL |Dictionary-function| with the given arguments.
- Non-blocking: does not wait on any result, but queues the
- event to be processed soon by the event loop.
+ On execution error: fails with VimL error, does not update
+ v:errmsg.
- 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
- |nvim_input()| has the same limitiation.
+ Parameters: ~
+ {dict} Dictionary, or String evaluating to a VimL |self|
+ dict
+ {fn} Name of the function defined on the VimL dict
+ {args} Function arguments packed in an Array
- Attributes: ~
- {fast}
+ Return: ~
+ Result of the function call
- Parameters: ~
- {button} Mouse button: one of "left", "right",
- "middle", "wheel".
- {action} For ordinary buttons, one of "press", "drag",
- "release". For the wheel, one of "up", "down",
- "left", "right".
- {modifier} 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.
- {grid} Grid number if the client uses |ui-multigrid|,
- else 0.
- {row} Mouse row-position (zero-based, like redraw
- events)
- {col} Mouse column-position (zero-based, like redraw
- events)
+nvim_call_function({fn}, {args}) *nvim_call_function()*
+ Calls a VimL function with the given arguments.
- *nvim_replace_termcodes()*
-nvim_replace_termcodes({str}, {from_part}, {do_lt}, {special})
- Replaces terminal codes and |keycodes| (<CR>, <Esc>, ...) in a
- string with the internal representation.
+ On execution error: fails with VimL error, does not update
+ v:errmsg.
Parameters: ~
- {str} String to be converted.
- {from_part} Legacy Vim parameter. Usually true.
- {do_lt} Also translate <lt>. Ignored if `special` is
- false.
- {special} Replace |keycodes|, e.g. <CR> becomes a "\n"
- char.
+ {fn} Function to call
+ {args} Function arguments packed in an Array
- See also: ~
- replace_termcodes
- cpoptions
+ Return: ~
+ Result of the function call
-nvim_command_output({command}) *nvim_command_output()*
- Executes an ex-command and returns its (non-error) output.
- Shell |:!| output is not captured.
+nvim_command({command}) *nvim_command()*
+ Executes an ex-command.
On execution error: fails with VimL error, does not update
v:errmsg.
@@ -581,9 +607,79 @@ nvim_command_output({command}) *nvim_command_output()*
Parameters: ~
{command} Ex-command string
+ See also: ~
+ |nvim_exec()|
+
+nvim_create_buf({listed}, {scratch}) *nvim_create_buf()*
+ Creates a new, empty, unnamed buffer.
+
+ Parameters: ~
+ {listed} Sets 'buflisted'
+ {scratch} Creates a "throwaway" |scratch-buffer| for
+ temporary work (always 'nomodified'). Also sets
+ 'nomodeline' on the buffer.
+
+ Return: ~
+ Buffer handle, or 0 on error
+
+ See also: ~
+ buf_open_scratch
+
+nvim_create_namespace({name}) *nvim_create_namespace()*
+ Creates a new namespace, or gets an existing one.
+
+ Namespaces are used for buffer highlights and virtual text,
+ see |nvim_buf_add_highlight()| and
+ |nvim_buf_set_virtual_text()|.
+
+ 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.
+
+ Parameters: ~
+ {name} Namespace name or empty string
+
+ Return: ~
+ Namespace id
+
+nvim_del_current_line() *nvim_del_current_line()*
+ Deletes the current line.
+
+nvim_del_keymap({mode}, {lhs}) *nvim_del_keymap()*
+ Unmaps a global |mapping| for the given mode.
+
+ To unmap a buffer-local mapping, use |nvim_buf_del_keymap()|.
+
+ See also: ~
+ |nvim_set_keymap()|
+
+nvim_del_var({name}) *nvim_del_var()*
+ Removes a global (g:) variable.
+
+ Parameters: ~
+ {name} Variable name
+
+nvim_err_write({str}) *nvim_err_write()*
+ Writes a message to the Vim error buffer. Does not append
+ "\n", the message is buffered (won't display) until a linefeed
+ is written.
+
+ Parameters: ~
+ {str} Message
+
+nvim_err_writeln({str}) *nvim_err_writeln()*
+ Writes a message to the Vim error buffer. Appends "\n", so the
+ buffer is flushed (and displayed).
+
+ Parameters: ~
+ {str} Message
+
+ See also: ~
+ nvim_err_write()
+
nvim_eval({expr}) *nvim_eval()*
- Evaluates a VimL expression (:help expression). Dictionaries
- and Lists are recursively expanded.
+ Evaluates a VimL |expression|. Dictionaries and Lists are
+ recursively expanded.
On execution error: fails with VimL error, does not update
v:errmsg.
@@ -594,7 +690,30 @@ nvim_eval({expr}) *nvim_eval()*
Return: ~
Evaluation result or expanded object
-nvim_execute_lua({code}, {args}) *nvim_execute_lua()*
+nvim_exec({src}, {output}) *nvim_exec()*
+ 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, does not update
+ v:errmsg.
+
+ Parameters: ~
+ {src} Vimscript code
+ {output} Capture and return all (non-error, non-shell
+ |:!|) output
+
+ Return: ~
+ Output (non-error, non-shell |:!|) if `output` is true,
+ else empty string.
+
+ See also: ~
+ |execute()|
+ |nvim_command()|
+
+nvim_exec_lua({code}, {args}) *nvim_exec_lua()*
Execute Lua code. Parameters (if any) are available as `...`
inside the chunk. The chunk can return a value.
@@ -608,55 +727,124 @@ nvim_execute_lua({code}, {args}) *nvim_execute_lua()*
Return: ~
Return value of Lua code if present or NIL.
-nvim_call_function({fn}, {args}) *nvim_call_function()*
- Calls a VimL function with the given arguments.
+nvim_feedkeys({keys}, {mode}, {escape_csi}) *nvim_feedkeys()*
+ Sends input-keys to Nvim, subject to various quirks controlled
+ by `mode` flags. This is a blocking call, unlike
+ |nvim_input()|.
- On execution error: fails with VimL error, does not update
- v:errmsg.
+ On execution error: does not fail, but updates v:errmsg.
+
+ If you need to input sequences like <C-o> use nvim_replace_termcodes
+ to replace the termcodes and then pass the resulting string to
+ nvim_feedkeys. You'll also want to enable escape_csi.
+
+ Example: >
+ :let key = nvim_replace_termcodes("<C-o>", v:true, v:false, v:true)
+ :call nvim_feedkeys(key, 'n', v:true)
+<
Parameters: ~
- {fn} Function to call
- {args} Function arguments packed in an Array
+ {keys} to be typed
+ {mode} behavior flags, see |feedkeys()|
+ {escape_csi} If true, escape K_SPECIAL/CSI bytes in
+ `keys`
+
+ See also: ~
+ feedkeys()
+ vim_strsave_escape_csi
+
+nvim_get_api_info() *nvim_get_api_info()*
+ Returns a 2-tuple (Array), where item 0 is the current channel
+ id and item 1 is the |api-metadata| map (Dictionary).
Return: ~
- Result of the function call
+ 2-tuple [{channel-id}, {api-metadata}]
-nvim_call_dict_function({dict}, {fn}, {args}) *nvim_call_dict_function()*
- Calls a VimL |Dictionary-function| with the given arguments.
+ Attributes: ~
+ {fast}
- On execution error: fails with VimL error, does not update
- v:errmsg.
+nvim_get_chan_info({chan}) *nvim_get_chan_info()*
+ Get information about a channel.
+
+ Return: ~
+ Dictionary describing a channel, with these keys:
+ • "stream" the stream underlying the channel
+ • "stdio" stdin and stdout of this Nvim instance
+ • "stderr" stderr of this Nvim instance
+ • "socket" TCP/IP socket or named pipe
+ • "job" job with communication over its stdio
+
+ • "mode" how data received on the channel is interpreted
+ • "bytes" send and receive raw bytes
+ • "terminal" a |terminal| instance interprets ASCII
+ sequences
+ • "rpc" |RPC| communication on the channel is active
+
+ • "pty" Name of pseudoterminal, if one is used (optional).
+ On a POSIX system, this will be a device path like
+ /dev/pts/1. Even if the name is unknown, the key will
+ still be present to indicate a pty is used. This is
+ currently the case when using winpty on windows.
+ • "buffer" buffer with connected |terminal| instance
+ (optional)
+ • "client" information about the client on the other end
+ of the RPC channel, if it has added it using
+ |nvim_set_client_info()|. (optional)
+
+nvim_get_color_by_name({name}) *nvim_get_color_by_name()*
+ Returns the 24-bit RGB value of a |nvim_get_color_map()| color
+ name or "#rrggbb" hexadecimal string.
+
+ Example: >
+ :echo nvim_get_color_by_name("Pink")
+ :echo nvim_get_color_by_name("#cbcbcb")
+<
Parameters: ~
- {dict} Dictionary, or String evaluating to a VimL |self|
- dict
- {fn} Name of the function defined on the VimL dict
- {args} Function arguments packed in an Array
+ {name} Color name or "#rrggbb" string
Return: ~
- Result of the function call
+ 24-bit RGB value, or -1 for invalid argument.
-nvim_strwidth({text}) *nvim_strwidth()*
- Calculates the number of display cells occupied by `text` .
- <Tab> counts as one cell.
+nvim_get_color_map() *nvim_get_color_map()*
+ Returns a map of color names and RGB values.
- Parameters: ~
- {text} Some text
+ Keys are color names (e.g. "Aqua") and values are 24-bit RGB
+ color values (e.g. 65535).
Return: ~
- Number of cells
+ Map of color names and RGB values.
-nvim_list_runtime_paths() *nvim_list_runtime_paths()*
- Gets the paths contained in 'runtimepath'.
+nvim_get_commands({opts}) *nvim_get_commands()*
+ Gets a map of global (non-buffer-local) Ex commands.
+
+ Currently only |user-commands| are supported, not builtin Ex
+ commands.
+
+ Parameters: ~
+ {opts} Optional parameters. Currently only supports
+ {"builtin":false}
Return: ~
- List of paths
+ Map of maps describing commands.
-nvim_set_current_dir({dir}) *nvim_set_current_dir()*
- Changes the global working directory.
+nvim_get_context({opts}) *nvim_get_context()*
+ Gets a map of the current editor state.
Parameters: ~
- {dir} Directory path
+ {opts} Optional parameters.
+ • types: List of |context-types| ("regs", "jumps",
+ "bufs", "gvars", …) to gather, or empty for
+ "all".
+
+ Return: ~
+ map of global |context|.
+
+nvim_get_current_buf() *nvim_get_current_buf()*
+ Gets the current buffer.
+
+ Return: ~
+ Buffer handle
nvim_get_current_line() *nvim_get_current_line()*
Gets the current line.
@@ -664,52 +852,75 @@ nvim_get_current_line() *nvim_get_current_line()*
Return: ~
Current line string
-nvim_set_current_line({line}) *nvim_set_current_line()*
- Sets the current line.
+nvim_get_current_tabpage() *nvim_get_current_tabpage()*
+ Gets the current tabpage.
- Parameters: ~
- {line} Line contents
+ Return: ~
+ Tabpage handle
-nvim_del_current_line() *nvim_del_current_line()*
- Deletes the current line.
+nvim_get_current_win() *nvim_get_current_win()*
+ Gets the current window.
-nvim_get_var({name}) *nvim_get_var()*
- Gets a global (g:) variable.
+ Return: ~
+ Window handle
+
+nvim_get_hl_by_id({hl_id}, {rgb}) *nvim_get_hl_by_id()*
+ Gets a highlight definition by id. |hlID()|
Parameters: ~
- {name} Variable name
+ {hl_id} Highlight id as returned by |hlID()|
+ {rgb} Export RGB colors
Return: ~
- Variable value
+ Highlight definition map
-nvim_set_var({name}, {value}) *nvim_set_var()*
- Sets a global (g:) variable.
+ See also: ~
+ nvim_get_hl_by_name
+
+nvim_get_hl_by_name({name}, {rgb}) *nvim_get_hl_by_name()*
+ Gets a highlight definition by name.
Parameters: ~
- {name} Variable name
- {value} Variable value
+ {name} Highlight group name
+ {rgb} Export RGB colors
-nvim_del_var({name}) *nvim_del_var()*
- Removes a global (g:) variable.
+ Return: ~
+ Highlight definition map
- Parameters: ~
- {name} Variable name
+ See also: ~
+ nvim_get_hl_by_id
-nvim_get_vvar({name}) *nvim_get_vvar()*
- Gets a v: variable.
+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_keymap({mode}) *nvim_get_keymap()*
+ Gets a list of global (non-buffer-local) |mapping|
+ definitions.
Parameters: ~
- {name} Variable name
+ {mode} Mode short-name ("n", "i", "v", ...)
Return: ~
- Variable value
+ Array of maparg()-like dictionaries describing mappings.
+ The "buffer" key is always zero.
-nvim_set_vvar({name}, {value}) *nvim_set_vvar()*
- Sets a v: variable, if it is not readonly.
+nvim_get_mode() *nvim_get_mode()*
+ Gets the current mode. |mode()| "blocking" is true if Nvim is
+ waiting for input.
- Parameters: ~
- {name} Variable name
- {value} Variable value
+ Return: ~
+ Dictionary { "mode": String, "blocking": Boolean }
+
+ Attributes: ~
+ {fast}
+
+nvim_get_namespaces() *nvim_get_namespaces()*
+ Gets existing, non-anonymous namespaces.
+
+ Return: ~
+ dict that maps from names to namespace ids.
nvim_get_option({name}) *nvim_get_option()*
Gets an option value string.
@@ -720,38 +931,113 @@ nvim_get_option({name}) *nvim_get_option()*
Return: ~
Option value (global)
-nvim_set_option({name}, {value}) *nvim_set_option()*
- Sets an option value.
+nvim_get_proc({pid}) *nvim_get_proc()*
+ Gets info describing process `pid` .
+
+ Return: ~
+ Map of process properties, or NIL if process not found.
+
+nvim_get_proc_children({pid}) *nvim_get_proc_children()*
+ Gets the immediate children of process `pid` .
+
+ Return: ~
+ Array of child process ids, empty if process not found.
+
+nvim_get_runtime_file({name}, {all}) *nvim_get_runtime_file()*
+ Find files in runtime directories
+
+ 'name' can contain wildcards. For example
+ nvim_get_runtime_file("colors/*.vim", true) will return all
+ color scheme files.
+
+ It is not an error to not find any files. An empty array is
+ returned then.
Parameters: ~
- {name} Option name
- {value} New option value
+ {name} pattern of files to search for
+ {all} whether to return all matches or only the first
-nvim_out_write({str}) *nvim_out_write()*
- Writes a message to the Vim output buffer. Does not append
- "\n", the message is buffered (won't display) until a linefeed
- is written.
+ Return: ~
+ list of absolute paths to the found files
+
+nvim_get_var({name}) *nvim_get_var()*
+ Gets a global (g:) variable.
Parameters: ~
- {str} Message
+ {name} Variable name
-nvim_err_write({str}) *nvim_err_write()*
- Writes a message to the Vim error buffer. Does not append
- "\n", the message is buffered (won't display) until a linefeed
- is written.
+ Return: ~
+ Variable value
+
+nvim_get_vvar({name}) *nvim_get_vvar()*
+ Gets a v: variable.
Parameters: ~
- {str} Message
+ {name} Variable name
-nvim_err_writeln({str}) *nvim_err_writeln()*
- Writes a message to the Vim error buffer. Appends "\n", so the
- buffer is flushed (and displayed).
+ Return: ~
+ Variable value
+
+nvim_input({keys}) *nvim_input()*
+ 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.
+
+ 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 "<LeftMouse><col,row>" is deprecated since
+ |api-level| 6.
+
+ Attributes: ~
+ {fast}
Parameters: ~
- {str} Message
+ {keys} to be typed
- See also: ~
- nvim_err_write()
+ Return: ~
+ Number of bytes actually written (can be fewer than
+ requested if the buffer becomes full).
+
+ *nvim_input_mouse()*
+nvim_input_mouse({button}, {action}, {modifier}, {grid}, {row}, {col})
+ 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.
+
+ 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
+ |nvim_input()| has the same limitiation.
+
+ Attributes: ~
+ {fast}
+
+ Parameters: ~
+ {button} Mouse button: one of "left", "right",
+ "middle", "wheel".
+ {action} For ordinary buttons, one of "press", "drag",
+ "release". For the wheel, one of "up", "down",
+ "left", "right".
+ {modifier} 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.
+ {grid} Grid number if the client uses |ui-multigrid|,
+ else 0.
+ {row} Mouse row-position (zero-based, like redraw
+ events)
+ {col} Mouse column-position (zero-based, like redraw
+ events)
nvim_list_bufs() *nvim_list_bufs()*
Gets the current list of buffer handles
@@ -762,49 +1048,48 @@ nvim_list_bufs() *nvim_list_bufs()*
Return: ~
List of buffer handles
-nvim_get_current_buf() *nvim_get_current_buf()*
- Gets the current buffer.
+nvim_list_chans() *nvim_list_chans()*
+ Get information about all open channels.
Return: ~
- Buffer handle
+ Array of Dictionaries, each describing a channel with the
+ format specified at |nvim_get_chan_info()|.
-nvim_set_current_buf({buffer}) *nvim_set_current_buf()*
- Sets the current buffer.
+nvim_list_runtime_paths() *nvim_list_runtime_paths()*
+ Gets the paths contained in 'runtimepath'.
- Parameters: ~
- {buffer} Buffer handle
+ Return: ~
+ List of paths
-nvim_list_wins() *nvim_list_wins()*
- Gets the current list of window handles.
+nvim_list_tabpages() *nvim_list_tabpages()*
+ Gets the current list of tabpage handles.
Return: ~
- List of window handles
+ List of tabpage handles
-nvim_get_current_win() *nvim_get_current_win()*
- Gets the current window.
+nvim_list_uis() *nvim_list_uis()*
+ Gets a list of dictionaries representing attached UIs.
Return: ~
- Window handle
+ Array of UI dictionaries, each with these keys:
+ • "height" Requested height of the UI
+ • "width" Requested width of the UI
+ • "rgb" true if the UI uses RGB colors (false implies
+ |cterm-colors|)
+ • "ext_..." Requested UI extensions, see |ui-option|
+ • "chan" Channel id of remote UI (not present for TUI)
-nvim_set_current_win({window}) *nvim_set_current_win()*
- Sets the current window.
+nvim_list_wins() *nvim_list_wins()*
+ Gets the current list of window handles.
- Parameters: ~
- {window} Window handle
+ Return: ~
+ List of window handles
-nvim_create_buf({listed}, {scratch}) *nvim_create_buf()*
- Creates a new, empty, unnamed buffer.
+nvim_load_context({dict}) *nvim_load_context()*
+ Sets the current editor state from the given |context| map.
Parameters: ~
- {listed} Sets 'buflisted'
- {scratch} Creates a "throwaway" |scratch-buffer| for
- temporary work (always 'nomodified')
-
- Return: ~
- Buffer handle, or 0 on error
-
- See also: ~
- buf_open_scratch
+ {dict} |Context| map.
nvim_open_win({buffer}, {enter}, {config}) *nvim_open_win()*
Open a new window.
@@ -849,15 +1134,15 @@ nvim_open_win({buffer}, {enter}, {config}) *nvim_open_win()*
{buffer} Buffer to display, or 0 for current buffer
{enter} Enter the window (make it the current window)
{config} Map defining the window configuration. Keys:
- • `relative` : Sets the window layout to "floating", placed
- at (row,col) coordinates relative to one of:
+ • `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 by default.
+ current window.
• "cursor" Cursor position in current window.
• `win` : |window-ID| for relative="win".
- • `anchor` : Decides which corner of the float to place
+ • `anchor`: Decides which corner of the float to place
at (row,col):
• "NW" northwest (default)
• "NE" northeast
@@ -887,7 +1172,7 @@ nvim_open_win({buffer}, {enter}, {config}) *nvim_open_win()*
an external top-level window. Currently
accepts no other positioning configuration
together with this.
- • `style` : Configure the appearance of the window.
+ • `style`: Configure the appearance of the window.
Currently only takes one non-empty value:
• "minimal" Nvim will display the window with
many UI options disabled. This is useful
@@ -896,54 +1181,119 @@ nvim_open_win({buffer}, {enter}, {config}) *nvim_open_win()*
'number', 'relativenumber', 'cursorline',
'cursorcolumn', 'foldcolumn', 'spell' and
'list' options. 'signcolumn' is changed to
- `auto` . The end-of-buffer region is hidden
- by setting `eob` flag of 'fillchars' to a
- space char, and clearing the |EndOfBuffer|
- region in 'winhighlight'.
+ `auto` and 'colorcolumn' is cleared. The
+ end-of-buffer region is hidden by setting
+ `eob` flag of 'fillchars' to a space char,
+ and clearing the |EndOfBuffer| region in
+ 'winhighlight'.
Return: ~
Window handle, or 0 on error
-nvim_list_tabpages() *nvim_list_tabpages()*
- Gets the current list of tabpage handles.
-
- Return: ~
- List of tabpage handles
-
-nvim_get_current_tabpage() *nvim_get_current_tabpage()*
- Gets the current tabpage.
-
- Return: ~
- Tabpage handle
-
-nvim_set_current_tabpage({tabpage}) *nvim_set_current_tabpage()*
- Sets the current tabpage.
+nvim_out_write({str}) *nvim_out_write()*
+ Writes a message to the Vim output buffer. Does not append
+ "\n", the message is buffered (won't display) until a linefeed
+ is written.
Parameters: ~
- {tabpage} Tabpage handle
-
-nvim_create_namespace({name}) *nvim_create_namespace()*
- Creates a new namespace, or gets an existing one.
+ {str} Message
- Namespaces are used for buffer highlights and virtual text,
- see |nvim_buf_add_highlight()| and
- |nvim_buf_set_virtual_text()|.
+ *nvim_parse_expression()*
+nvim_parse_expression({expr}, {flags}, {highlight})
+ Parse a VimL expression.
- 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.
+ Attributes: ~
+ {fast}
Parameters: ~
- {name} Namespace name or empty string
+ {expr} Expression to parse. Always treated as a
+ single line.
+ {flags} 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".
+ {highlight} 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: ~
- Namespace id
-nvim_get_namespaces() *nvim_get_namespaces()*
- Gets existing, non-anonymous namespaces.
+ • AST: top-level dictionary with these keys:
+ • "error": Dictionary with error, present only if parser
+ saw some error. Contains the following keys:
+ • "message": String, error message in printf format,
+ translated. Must contain exactly one "%.*s".
+ • "arg": String, error message argument.
- Return: ~
- dict that maps from names to namespace ids.
+ • "len": Amount of bytes successfully parsed. With flags
+ equal to "" that should be equal to the length of expr
+ string. (“Sucessfully parsed†here means “participated
+ in AST creationâ€, not “till the first errorâ€.)
+ • "ast": AST, either nil or a dictionary with these
+ keys:
+ • "type": node type, one of the value names from
+ ExprASTNodeType 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.
+ • "len": “length†of the node. This and "start" are
+ there for debugging purposes primary (debugging
+ parser and providing debug information).
+ • "children": a list of nodes described in top/"ast".
+ There always is zero, one or two children, key will
+ not be present if node has no children. Maximum
+ number of children may be found in node_maxchildren
+ array.
+
+ • Local values (present only for certain nodes):
+ • "scope": a single Integer, specifies scope for
+ "Option" and "PlainIdentifier" nodes. For "Option" it
+ is one of ExprOptScope values, for "PlainIdentifier"
+ it is one of ExprVarScope values.
+ • "ident": identifier (without scope, if any), present
+ for "Option", "PlainIdentifier", "PlainKey" and
+ "Environment" nodes.
+ • "name": Integer, register name (one character) or -1.
+ Only present for "Register" nodes.
+ • "cmp_type": String, comparison type, one of the value
+ names from ExprComparisonType, stringified without
+ "kExprCmp" prefix. Only present for "Comparison"
+ nodes.
+ • "ccs_strategy": String, case comparison strategy, one
+ of the value names from ExprCaseCompareStrategy,
+ stringified without "kCCStrategy" prefix. Only present
+ for "Comparison" nodes.
+ • "augmentation": String, augmentation type for
+ "Assignment" nodes. Is either an empty string, "Add",
+ "Subtract" or "Concat" for "=", "+=", "-=" or ".="
+ respectively.
+ • "invert": Boolean, true if result of comparison needs
+ to be inverted. Only present for "Comparison" nodes.
+ • "ivalue": Integer, integer value for "Integer" nodes.
+ • "fvalue": Float, floating-point value for "Float"
+ nodes.
+ • "svalue": String, value for "SingleQuotedString" and
+ "DoubleQuotedString" nodes.
nvim_paste({data}, {crlf}, {phase}) *nvim_paste()*
Pastes at cursor, in any mode.
@@ -984,146 +1334,49 @@ nvim_put({lines}, {type}, {after}, {follow}) *nvim_put()*
{type} Edit behavior: any |getregtype()| result, or:
• "b" |blockwise-visual| mode (may include
width, e.g. "b3")
- • "c" |characterwise| mode
+ • "c" |charwise| mode
• "l" |linewise| mode
• "" guess by contents, see |setreg()|
{after} Insert after cursor (like |p|), or before (like
|P|).
{follow} Place cursor at end of inserted text.
-nvim_subscribe({event}) *nvim_subscribe()*
- Subscribes to event broadcasts.
-
- Parameters: ~
- {event} Event type string
-
-nvim_unsubscribe({event}) *nvim_unsubscribe()*
- Unsubscribes to event broadcasts.
-
- Parameters: ~
- {event} Event type string
-
-nvim_get_color_by_name({name}) *nvim_get_color_by_name()*
- Returns the 24-bit RGB value of a |nvim_get_color_map()| color
- name or "#rrggbb" hexadecimal string.
-
- Example: >
- :echo nvim_get_color_by_name("Pink")
- :echo nvim_get_color_by_name("#cbcbcb")
-<
-
- Parameters: ~
- {name} Color name or "#rrggbb" string
-
- Return: ~
- 24-bit RGB value, or -1 for invalid argument.
-
-nvim_get_color_map() *nvim_get_color_map()*
- 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: ~
- Map of color names and RGB values.
-
-nvim_get_context({opts}) *nvim_get_context()*
- Gets a map of the current editor state.
-
- Parameters: ~
- {opts} Optional parameters.
- • types: List of |context-types| ("regs", "jumps",
- "bufs", "gvars", …) to gather, or empty for
- "all".
-
- Return: ~
- map of global |context|.
-
-nvim_load_context({dict}) *nvim_load_context()*
- Sets the current editor state from the given |context| map.
-
- Parameters: ~
- {dict} |Context| map.
-
-nvim_get_mode() *nvim_get_mode()*
- Gets the current mode. |mode()| "blocking" is true if Nvim is
- waiting for input.
-
- Return: ~
- Dictionary { "mode": String, "blocking": Boolean }
-
- Attributes: ~
- {fast}
-
-nvim_get_keymap({mode}) *nvim_get_keymap()*
- Gets a list of global (non-buffer-local) |mapping|
- definitions.
-
- Parameters: ~
- {mode} Mode short-name ("n", "i", "v", ...)
-
- Return: ~
- Array of maparg()-like dictionaries describing mappings.
- The "buffer" key is always zero.
-
-nvim_set_keymap({mode}, {lhs}, {rhs}, {opts}) *nvim_set_keymap()*
- 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: >
- call nvim_set_keymap('n', ' <NL>', '', {'nowait': v:true})
-<
-
- is equivalent to: >
- nmap <nowait> <Space><NL> <Nop>
-<
+ *nvim_replace_termcodes()*
+nvim_replace_termcodes({str}, {from_part}, {do_lt}, {special})
+ Replaces terminal codes and |keycodes| (<CR>, <Esc>, ...) in a
+ string with the internal representation.
Parameters: ~
- {mode} Mode short-name (map command prefix: "n", "i",
- "v", "x", …) or "!" for |:map!|, or empty string
- for |:map|.
- {lhs} Left-hand-side |{lhs}| of the mapping.
- {rhs} Right-hand-side |{rhs}| of the mapping.
- {opts} Optional parameters map. Accepts all
- |:map-arguments| as keys excluding |<buffer>| but
- including |noremap|. Values are Booleans. Unknown
- key is an error.
-
-nvim_del_keymap({mode}, {lhs}) *nvim_del_keymap()*
- Unmaps a global |mapping| for the given mode.
-
- To unmap a buffer-local mapping, use |nvim_buf_del_keymap()|.
+ {str} String to be converted.
+ {from_part} Legacy Vim parameter. Usually true.
+ {do_lt} Also translate <lt>. Ignored if `special` is
+ false.
+ {special} Replace |keycodes|, e.g. <CR> becomes a "\n"
+ char.
See also: ~
- |nvim_set_keymap()|
+ replace_termcodes
+ cpoptions
-nvim_get_commands({opts}) *nvim_get_commands()*
- Gets a map of global (non-buffer-local) Ex commands.
+ *nvim_select_popupmenu_item()*
+nvim_select_popupmenu_item({item}, {insert}, {finish}, {opts})
+ Selects an item in the completion popupmenu.
- Currently only |user-commands| are supported, not builtin Ex
- commands.
+ If |ins-completion| is not active this API call is silently
+ ignored. Useful for an external UI using |ui-popupmenu| to
+ control the popupmenu with the mouse. Can also be used in a
+ mapping; use <cmd> |:map-cmd| to ensure the mapping doesn't
+ end completion mode.
Parameters: ~
- {opts} Optional parameters. Currently only supports
- {"builtin":false}
-
- Return: ~
- Map of maps describing commands.
-
-nvim_get_api_info() *nvim_get_api_info()*
- Returns a 2-tuple (Array), where item 0 is the current channel
- id and item 1 is the |api-metadata| map (Dictionary).
-
- Return: ~
- 2-tuple [{channel-id}, {api-metadata}]
-
- Attributes: ~
- {fast}
+ {item} Index (zero-based) of the item to select. Value
+ of -1 selects nothing and restores the original
+ text.
+ {insert} Whether the selection should be inserted in the
+ buffer.
+ {finish} Finish the completion and dismiss the popupmenu.
+ Implies `insert` .
+ {opts} Optional parameters. Reserved for future use.
*nvim_set_client_info()*
nvim_set_client_info({name}, {version}, {type}, {methods},
@@ -1190,269 +1443,114 @@ nvim_set_client_info({name}, {version}, {type}, {methods},
small logo or icon. .png or .svg format is
preferred.
-nvim_get_chan_info({chan}) *nvim_get_chan_info()*
- Get information about a channel.
-
- Return: ~
- Dictionary describing a channel, with these keys:
- • "stream" the stream underlying the channel
- • "stdio" stdin and stdout of this Nvim instance
- • "stderr" stderr of this Nvim instance
- • "socket" TCP/IP socket or named pipe
- • "job" job with communication over its stdio
-
- • "mode" how data received on the channel is interpreted
- • "bytes" send and receive raw bytes
- • "terminal" a |terminal| instance interprets ASCII
- sequences
- • "rpc" |RPC| communication on the channel is active
-
- • "pty" Name of pseudoterminal, if one is used (optional).
- On a POSIX system, this will be a device path like
- /dev/pts/1. Even if the name is unknown, the key will
- still be present to indicate a pty is used. This is
- currently the case when using winpty on windows.
- • "buffer" buffer with connected |terminal| instance
- (optional)
- • "client" information about the client on the other end
- of the RPC channel, if it has added it using
- |nvim_set_client_info()|. (optional)
+nvim_set_current_buf({buffer}) *nvim_set_current_buf()*
+ Sets the current buffer.
-nvim_list_chans() *nvim_list_chans()*
- Get information about all open channels.
+ Parameters: ~
+ {buffer} Buffer handle
- Return: ~
- Array of Dictionaries, each describing a channel with the
- format specified at |nvim_get_chan_info()|.
+nvim_set_current_dir({dir}) *nvim_set_current_dir()*
+ Changes the global working directory.
-nvim_call_atomic({calls}) *nvim_call_atomic()*
- Calls many API methods atomically.
+ Parameters: ~
+ {dir} Directory path
- This has two main usages:
- 1. To perform several requests from an async context
- atomically, i.e. without interleaving redraws, RPC requests
- from other clients, or user interactions (however API
- methods may trigger autocommands or event processing which
- have such side-effects, e.g. |:sleep| may wake timers).
- 2. To minimize RPC overhead (roundtrips) of a sequence of many
- requests.
+nvim_set_current_line({line}) *nvim_set_current_line()*
+ Sets the current line.
Parameters: ~
- {calls} an array of calls, where each call is described
- by an array with two elements: the request name,
- and an array of arguments.
+ {line} Line contents
- Return: ~
- Array of two elements. The first is an array of return
- values. The second is NIL if all calls succeeded. If a
- call resulted in an error, it is a three-element array
- with the zero-based index of the call which resulted in an
- error, the error type and the error message. If an error
- occurred, the values from all preceding calls will still
- be returned.
+nvim_set_current_tabpage({tabpage}) *nvim_set_current_tabpage()*
+ Sets the current tabpage.
- *nvim_parse_expression()*
-nvim_parse_expression({expr}, {flags}, {highlight})
- Parse a VimL expression.
+ Parameters: ~
+ {tabpage} Tabpage handle
- Attributes: ~
- {fast}
+nvim_set_current_win({window}) *nvim_set_current_win()*
+ Sets the current window.
Parameters: ~
- {expr} Expression to parse. Always treated as a
- single line.
- {flags} 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".
- {highlight} 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: ~
+ {window} Window handle
- • AST: top-level dictionary with these keys:
- • "error": Dictionary with error, present only if parser
- saw some error. Contains the following keys:
- • "message": String, error message in printf format,
- translated. Must contain exactly one "%.*s".
- • "arg": String, error message argument.
+nvim_set_keymap({mode}, {lhs}, {rhs}, {opts}) *nvim_set_keymap()*
+ Sets a global |mapping| for the given mode.
- • "len": Amount of bytes successfully parsed. With flags
- equal to "" that should be equal to the length of expr
- string. (“Sucessfully parsed†here means “participated
- in AST creationâ€, not “till the first errorâ€.)
- • "ast": AST, either nil or a dictionary with these
- keys:
- • "type": node type, one of the value names from
- ExprASTNodeType 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.
- • "len": “length†of the node. This and "start" are
- there for debugging purposes primary (debugging
- parser and providing debug information).
- • "children": a list of nodes described in top/"ast".
- There always is zero, one or two children, key will
- not be present if node has no children. Maximum
- number of children may be found in node_maxchildren
- array.
+ To set a buffer-local mapping, use |nvim_buf_set_keymap()|.
- • Local values (present only for certain nodes):
- • "scope": a single Integer, specifies scope for
- "Option" and "PlainIdentifier" nodes. For "Option" it
- is one of ExprOptScope values, for "PlainIdentifier"
- it is one of ExprVarScope values.
- • "ident": identifier (without scope, if any), present
- for "Option", "PlainIdentifier", "PlainKey" and
- "Environment" nodes.
- • "name": Integer, register name (one character) or -1.
- Only present for "Register" nodes.
- • "cmp_type": String, comparison type, one of the value
- names from ExprComparisonType, stringified without
- "kExprCmp" prefix. Only present for "Comparison"
- nodes.
- • "ccs_strategy": String, case comparison strategy, one
- of the value names from ExprCaseCompareStrategy,
- stringified without "kCCStrategy" prefix. Only present
- for "Comparison" nodes.
- • "augmentation": String, augmentation type for
- "Assignment" nodes. Is either an empty string, "Add",
- "Subtract" or "Concat" for "=", "+=", "-=" or ".="
- respectively.
- • "invert": Boolean, true if result of comparison needs
- to be inverted. Only present for "Comparison" nodes.
- • "ivalue": Integer, integer value for "Integer" nodes.
- • "fvalue": Float, floating-point value for "Float"
- nodes.
- • "svalue": String, value for "SingleQuotedString" and
- "DoubleQuotedString" nodes.
+ Unlike |:map|, leading/trailing whitespace is accepted as part
+ of the {lhs} or {rhs}. Empty {rhs} is |<Nop>|. |keycodes| are
+ replaced as usual.
-nvim__id({obj}) *nvim__id()*
- Returns object given as argument.
+ Example: >
+ call nvim_set_keymap('n', ' <NL>', '', {'nowait': v:true})
+<
- This API function is used for testing. One should not rely on
- its presence in plugins.
+ is equivalent to: >
+ nmap <nowait> <Space><NL> <Nop>
+<
Parameters: ~
- {obj} Object to return.
-
- Return: ~
- its argument.
-
-nvim__id_array({arr}) *nvim__id_array()*
- Returns array given as argument.
+ {mode} Mode short-name (map command prefix: "n", "i",
+ "v", "x", …) or "!" for |:map!|, or empty string
+ for |:map|.
+ {lhs} Left-hand-side |{lhs}| of the mapping.
+ {rhs} Right-hand-side |{rhs}| of the mapping.
+ {opts} Optional parameters map. Accepts all
+ |:map-arguments| as keys excluding |<buffer>| but
+ including |noremap|. Values are Booleans. Unknown
+ key is an error.
- This API function is used for testing. One should not rely on
- its presence in plugins.
+nvim_set_option({name}, {value}) *nvim_set_option()*
+ Sets an option value.
Parameters: ~
- {arr} Array to return.
-
- Return: ~
- its argument.
-
-nvim__id_dictionary({dct}) *nvim__id_dictionary()*
- Returns dictionary given as argument.
+ {name} Option name
+ {value} New option value
- This API function is used for testing. One should not rely on
- its presence in plugins.
+nvim_set_var({name}, {value}) *nvim_set_var()*
+ Sets a global (g:) variable.
Parameters: ~
- {dct} Dictionary to return.
-
- Return: ~
- its argument.
-
-nvim__id_float({flt}) *nvim__id_float()*
- Returns floating-point value given as argument.
+ {name} Variable name
+ {value} Variable value
- This API function is used for testing. One should not rely on
- its presence in plugins.
+nvim_set_vvar({name}, {value}) *nvim_set_vvar()*
+ Sets a v: variable, if it is not readonly.
Parameters: ~
- {flt} Value to return.
-
- Return: ~
- its argument.
-
-nvim__stats() *nvim__stats()*
- Gets internal stats.
-
- Return: ~
- Map of various internal stats.
-
-nvim_list_uis() *nvim_list_uis()*
- Gets a list of dictionaries representing attached UIs.
+ {name} Variable name
+ {value} Variable value
- Return: ~
- Array of UI dictionaries, each with these keys:
- • "height" Requested height of the UI
- • "width" Requested width of the UI
- • "rgb" true if the UI uses RGB colors (false implies
- |cterm-colors|)
- • "ext_..." Requested UI extensions, see |ui-option|
- • "chan" Channel id of remote UI (not present for TUI)
+nvim_strwidth({text}) *nvim_strwidth()*
+ Calculates the number of display cells occupied by `text` .
+ <Tab> counts as one cell.
-nvim_get_proc_children({pid}) *nvim_get_proc_children()*
- Gets the immediate children of process `pid` .
+ Parameters: ~
+ {text} Some text
Return: ~
- Array of child process ids, empty if process not found.
-
-nvim_get_proc({pid}) *nvim_get_proc()*
- Gets info describing process `pid` .
+ Number of cells
- Return: ~
- Map of process properties, or NIL if process not found.
+nvim_subscribe({event}) *nvim_subscribe()*
+ Subscribes to event broadcasts.
- *nvim_select_popupmenu_item()*
-nvim_select_popupmenu_item({item}, {insert}, {finish}, {opts})
- Selects an item in the completion popupmenu.
+ Parameters: ~
+ {event} Event type string
- If |ins-completion| is not active this API call is silently
- ignored. Useful for an external UI using |ui-popupmenu| to
- control the popupmenu with the mouse. Can also be used in a
- mapping; use <cmd> |:map-cmd| to ensure the mapping doesn't
- end completion mode.
+nvim_unsubscribe({event}) *nvim_unsubscribe()*
+ Unsubscribes to event broadcasts.
Parameters: ~
- {item} Index (zero-based) of the item to select. Value
- of -1 selects nothing and restores the original
- text.
- {insert} Whether the selection should be inserted in the
- buffer.
- {finish} Finish the completion and dismiss the popupmenu.
- Implies `insert` .
- {opts} Optional parameters. Reserved for future use.
-
-nvim__inspect_cell({grid}, {row}, {col}) *nvim__inspect_cell()*
- TODO: Documentation
+ {event} Event type string
==============================================================================
Buffer Functions *api-buffer*
+
+For more information on buffers, see |buffers|.
+
Unloaded Buffers:~
Buffers may be unloaded by the |:bunload| command or the
@@ -1466,139 +1564,266 @@ affected.
You can use |nvim_buf_is_loaded()| or |nvim_buf_line_count()|
to check whether a buffer is loaded.
-nvim_buf_line_count({buffer}) *nvim_buf_line_count()*
- Gets the buffer line count
+ *nvim__buf_add_decoration()*
+nvim__buf_add_decoration({buffer}, {ns_id}, {hl_group}, {start_row},
+ {start_col}, {end_row}, {end_col},
+ {virt_text})
+ TODO: Documentation
+
+ *nvim__buf_redraw_range()*
+nvim__buf_redraw_range({buffer}, {first}, {last})
+ TODO: Documentation
+
+nvim__buf_set_luahl({buffer}, {opts}) *nvim__buf_set_luahl()*
+ Unstabilized interface for defining syntax hl in lua.
+
+ This is not yet safe for general use, lua callbacks will need
+ to be restricted, like textlock and probably other stuff.
+
+ The API on_line/nvim__put_attr is quite raw and not intended
+ to be the final shape. Ideally this should operate on chunks
+ larger than a single line to reduce interpreter overhead, and
+ generate annotation objects (bufhl/virttext) on the fly but
+ using the same representation.
+
+nvim__buf_stats({buffer}) *nvim__buf_stats()*
+ TODO: Documentation
+
+ *nvim_buf_add_highlight()*
+nvim_buf_add_highlight({buffer}, {src_id}, {hl_group}, {line},
+ {col_start}, {col_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.
Parameters: ~
- {buffer} Buffer handle, or 0 for current buffer
+ {buffer} Buffer handle, or 0 for current buffer
+ {ns_id} namespace to use or -1 for ungrouped
+ highlight
+ {hl_group} Name of the highlight group to use
+ {line} Line to highlight (zero-indexed)
+ {col_start} Start of (byte-indexed) column range to
+ highlight
+ {col_end} End of (byte-indexed) column range to
+ highlight, or -1 to highlight to end of line
Return: ~
- Line count, or 0 for unloaded buffer. |api-buffer|
+ The ns_id that was used
nvim_buf_attach({buffer}, {send_buffer}, {opts}) *nvim_buf_attach()*
- Activates buffer-update events on a channel, or as lua
+ 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): >
+ events = {}
+ vim.api.nvim_buf_attach(0, false, {
+ on_lines=function(...) table.insert(events, {...}) end})
+<
+
Parameters: ~
{buffer} Buffer handle, or 0 for current buffer
- {send_buffer} Set to true if the initial notification
- should contain the whole buffer. If so, the
- first notification will be a
- `nvim_buf_lines_event` . Otherwise, the
- first notification will be a
- `nvim_buf_changedtick_event` . Not used for
- lua callbacks.
+ {send_buffer} 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.
{opts} Optional parameters.
- • `on_lines` : lua callback received on
- change.
- • `on_changedtick` : lua callback received
- on changedtick increment without text
- change.
- • `utf_sizes` : include UTF-32 and UTF-16
- size of the replaced region. See
- |api-buffer-updates-lua| for more
- information
+ • 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_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
+
+ • utf_sizes: include UTF-32 and UTF-16 size
+ of the replaced region, as args to
+ `on_lines` .
+
+ Return: ~
+ False if attach failed (invalid parameter, or buffer isn't
+ loaded); otherwise True. TODO: LUA_API_NO_EVAL
- Return: ~
- False when updates couldn't be enabled because the buffer
- isn't loaded or `opts` contained an invalid key; otherwise
- True. TODO: LUA_API_NO_EVAL
+ See also: ~
+ |nvim_buf_detach()|
+ |api-buffer-updates-lua|
-nvim_buf_detach({buffer}) *nvim_buf_detach()*
- Deactivates buffer-update events on the channel.
+ *nvim_buf_clear_namespace()*
+nvim_buf_clear_namespace({buffer}, {ns_id}, {line_start}, {line_end})
+ Clears namespaced 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.
+
+ Parameters: ~
+ {buffer} Buffer handle, or 0 for current buffer
+ {ns_id} Namespace to clear, or -1 to clear all
+ namespaces.
+ {line_start} Start of range of lines to clear
+ {line_end} End of range of lines to clear (exclusive)
+ or -1 to clear to end of buffer.
- For Lua callbacks see |api-lua-detach|.
+nvim_buf_del_extmark({buffer}, {ns_id}, {id}) *nvim_buf_del_extmark()*
+ Removes an extmark.
Parameters: ~
{buffer} Buffer handle, or 0 for current buffer
+ {ns_id} Namespace id from |nvim_create_namespace()|
+ {id} Extmark id
Return: ~
- False when updates couldn't be disabled because the buffer
- isn't loaded; otherwise True.
+ true if the extmark was found, else false
- *nvim_buf_get_lines()*
-nvim_buf_get_lines({buffer}, {start}, {end}, {strict_indexing})
- Gets a line-range from the buffer.
+nvim_buf_del_keymap({buffer}, {mode}, {lhs}) *nvim_buf_del_keymap()*
+ Unmaps a buffer-local |mapping| for the given mode.
- 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.
+ Parameters: ~
+ {buffer} Buffer handle, or 0 for current buffer
- Out-of-bounds indices are clamped to the nearest valid value,
- unless `strict_indexing` is set.
+ See also: ~
+ |nvim_del_keymap()|
+
+nvim_buf_del_var({buffer}, {name}) *nvim_buf_del_var()*
+ Removes a buffer-scoped (b:) variable
Parameters: ~
- {buffer} Buffer handle, or 0 for current buffer
- {start} First line index
- {end} Last line index (exclusive)
- {strict_indexing} Whether out-of-bounds should be an
- error.
+ {buffer} Buffer handle, or 0 for current buffer
+ {name} Variable name
- Return: ~
- Array of lines, or empty array for unloaded buffer.
+nvim_buf_detach({buffer}) *nvim_buf_detach()*
+ Deactivates buffer-update events on the channel.
- *nvim_buf_set_lines()*
-nvim_buf_set_lines({buffer}, {start}, {end}, {strict_indexing},
- {replacement})
- Sets (replaces) a line-range in the buffer.
+ Parameters: ~
+ {buffer} Buffer handle, or 0 for current 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.
+ Return: ~
+ False if detach failed (because the buffer isn't loaded);
+ otherwise True.
- 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.
+ See also: ~
+ |nvim_buf_attach()|
+ |api-lua-detach| for detaching Lua callbacks
- Out-of-bounds indices are clamped to the nearest valid value,
- unless `strict_indexing` is set.
+nvim_buf_get_changedtick({buffer}) *nvim_buf_get_changedtick()*
+ Gets a changed tick of a buffer
Parameters: ~
- {buffer} Buffer handle, or 0 for current buffer
- {start} First line index
- {end} Last line index (exclusive)
- {strict_indexing} Whether out-of-bounds should be an
- error.
- {replacement} Array of lines to use as replacement
-
-nvim_buf_get_offset({buffer}, {index}) *nvim_buf_get_offset()*
- Returns the byte offset of a line (0-indexed). |api-indexing|
+ {buffer} Buffer handle, or 0 for current buffer
- 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'.
+ Return: ~
+ `b:changedtick` value.
- Unlike |line2byte()|, throws error for out-of-bounds indexing.
- Returns -1 for unloaded buffer.
+nvim_buf_get_commands({buffer}, {opts}) *nvim_buf_get_commands()*
+ Gets a map of buffer-local |user-commands|.
Parameters: ~
{buffer} Buffer handle, or 0 for current buffer
- {index} Line index
+ {opts} Optional parameters. Currently not used.
Return: ~
- Integer byte offset, or -1 for unloaded buffer.
+ Map of maps describing commands.
-nvim_buf_get_var({buffer}, {name}) *nvim_buf_get_var()*
- Gets a buffer-scoped (b:) variable.
+ *nvim_buf_get_extmark_by_id()*
+nvim_buf_get_extmark_by_id({buffer}, {ns_id}, {id})
+ Returns position for a given extmark id
Parameters: ~
{buffer} Buffer handle, or 0 for current buffer
- {name} Variable name
+ {ns_id} Namespace id from |nvim_create_namespace()|
+ {id} Extmark id
Return: ~
- Variable value
+ (row, col) tuple or empty list () if extmark id was absent
-nvim_buf_get_changedtick({buffer}) *nvim_buf_get_changedtick()*
- Gets a changed tick of a buffer
+ *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|).
+
+ 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:
+>
+ nvim_buf_get_extmarks(0, my_ns, 0, -1, {})
+ 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.)
+
+ Example:
+>
+ 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, 0, {})
+ -- Create new extmark at line 3, column 1.
+ local m2 = a.nvim_buf_set_extmark(0, ns, 0, 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))
+<
Parameters: ~
{buffer} Buffer handle, or 0 for current buffer
+ {ns_id} Namespace id from |nvim_create_namespace()|
+ {start} Start of range, given as (row, col) or valid
+ extmark id (whose position defines the bound)
+ {end} End of range, given as (row, col) or valid
+ extmark id (whose position defines the bound)
+ {opts} Optional parameters. Keys:
+ • limit: Maximum number of marks to return
Return: ~
- `b:changedtick` value.
+ List of [extmark_id, row, col] tuples in "traversal
+ order".
nvim_buf_get_keymap({buffer}, {mode}) *nvim_buf_get_keymap()*
Gets a list of buffer-local |mapping| definitions.
@@ -1611,49 +1836,67 @@ nvim_buf_get_keymap({buffer}, {mode}) *nvim_buf_get_keymap()*
Array of maparg()-like dictionaries describing mappings.
The "buffer" key holds the associated buffer handle.
- *nvim_buf_set_keymap()*
-nvim_buf_set_keymap({buffer}, {mode}, {lhs}, {rhs}, {opts})
- Sets a buffer-local |mapping| for the given mode.
+ *nvim_buf_get_lines()*
+nvim_buf_get_lines({buffer}, {start}, {end}, {strict_indexing})
+ 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.
Parameters: ~
- {buffer} Buffer handle, or 0 for current buffer
+ {buffer} Buffer handle, or 0 for current buffer
+ {start} First line index
+ {end} Last line index (exclusive)
+ {strict_indexing} Whether out-of-bounds should be an
+ error.
- See also: ~
- |nvim_set_keymap()|
+ Return: ~
+ Array of lines, or empty array for unloaded buffer.
-nvim_buf_del_keymap({buffer}, {mode}, {lhs}) *nvim_buf_del_keymap()*
- Unmaps a buffer-local |mapping| for the given mode.
+nvim_buf_get_mark({buffer}, {name}) *nvim_buf_get_mark()*
+ Return a tuple (row,col) representing the position of the
+ named mark.
+
+ Marks are (1,0)-indexed. |api-indexing|
Parameters: ~
{buffer} Buffer handle, or 0 for current buffer
+ {name} Mark name
- See also: ~
- |nvim_del_keymap()|
+ Return: ~
+ (row, col) tuple
-nvim_buf_get_commands({buffer}, {opts}) *nvim_buf_get_commands()*
- Gets a map of buffer-local |user-commands|.
+nvim_buf_get_name({buffer}) *nvim_buf_get_name()*
+ Gets the full file name for the buffer
Parameters: ~
{buffer} Buffer handle, or 0 for current buffer
- {opts} Optional parameters. Currently not used.
Return: ~
- Map of maps describing commands.
+ Buffer name
-nvim_buf_set_var({buffer}, {name}, {value}) *nvim_buf_set_var()*
- Sets a buffer-scoped (b:) variable
+nvim_buf_get_offset({buffer}, {index}) *nvim_buf_get_offset()*
+ Returns the byte offset of a line (0-indexed). |api-indexing|
- Parameters: ~
- {buffer} Buffer handle, or 0 for current buffer
- {name} Variable name
- {value} Variable value
+ 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'.
-nvim_buf_del_var({buffer}, {name}) *nvim_buf_del_var()*
- Removes a buffer-scoped (b:) variable
+ Unlike |line2byte()|, throws error for out-of-bounds indexing.
+ Returns -1 for unloaded buffer.
Parameters: ~
{buffer} Buffer handle, or 0 for current buffer
- {name} Variable name
+ {index} Line index
+
+ Return: ~
+ Integer byte offset, or -1 for unloaded buffer.
nvim_buf_get_option({buffer}, {name}) *nvim_buf_get_option()*
Gets a buffer option value
@@ -1665,30 +1908,37 @@ nvim_buf_get_option({buffer}, {name}) *nvim_buf_get_option()*
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)
+nvim_buf_get_var({buffer}, {name}) *nvim_buf_get_var()*
+ Gets a buffer-scoped (b:) variable.
Parameters: ~
{buffer} Buffer handle, or 0 for current buffer
- {name} Option name
- {value} Option value
+ {name} Variable name
-nvim_buf_get_name({buffer}) *nvim_buf_get_name()*
- Gets the full file name for the buffer
+ Return: ~
+ Variable value
- Parameters: ~
- {buffer} Buffer handle, or 0 for current buffer
+ *nvim_buf_get_virtual_text()*
+nvim_buf_get_virtual_text({buffer}, {line})
+ Get the virtual text (annotation) for a buffer line.
- Return: ~
- Buffer name
+ The virtual text is returned as list of lists, whereas the
+ inner lists have either one or two elements. The first element
+ is the actual text, the optional second element is the
+ highlight group.
-nvim_buf_set_name({buffer}, {name}) *nvim_buf_set_name()*
- Sets the full file name for a buffer
+ The format is exactly the same as given to
+ nvim_buf_set_virtual_text().
+
+ If there is no virtual text associated with the given line, an
+ empty list is returned.
Parameters: ~
{buffer} Buffer handle, or 0 for current buffer
- {name} Buffer name
+ {line} Line to get the virtual text from (zero-indexed)
+
+ Return: ~
+ List of virtual text chunks
nvim_buf_is_loaded({buffer}) *nvim_buf_is_loaded()*
Checks if a buffer is valid and loaded. See |api-buffer| for
@@ -1713,78 +1963,98 @@ nvim_buf_is_valid({buffer}) *nvim_buf_is_valid()*
Return: ~
true if the buffer is valid, false otherwise.
-nvim_buf_get_mark({buffer}, {name}) *nvim_buf_get_mark()*
- Return a tuple (row,col) representing the position of the
- named mark.
+nvim_buf_line_count({buffer}) *nvim_buf_line_count()*
+ Gets the buffer line count
- Marks are (1,0)-indexed. |api-indexing|
+ Parameters: ~
+ {buffer} Buffer handle, or 0 for current buffer
+
+ Return: ~
+ Line count, or 0 for unloaded buffer. |api-buffer|
+
+ *nvim_buf_set_extmark()*
+nvim_buf_set_extmark({buffer}, {ns_id}, {id}, {line}, {col}, {opts})
+ Creates or updates an extmark.
+
+ To create a new extmark, pass id=0. The extmark id will be
+ returned. It is also allowed to create a new mark by passing
+ in a previously unused id, but the caller must then keep track
+ of existing and unused ids itself. (Useful over RPC, to avoid
+ waiting for the return value.)
Parameters: ~
{buffer} Buffer handle, or 0 for current buffer
- {name} Mark name
+ {ns_id} Namespace id from |nvim_create_namespace()|
+ {id} Extmark id, or 0 to create new
+ {line} Line number where to place the mark
+ {col} Column where to place the mark
+ {opts} Optional parameters. Currently not used.
Return: ~
- (row, col) tuple
+ Id of the created/updated extmark
- *nvim_buf_add_highlight()*
-nvim_buf_add_highlight({buffer}, {ns_id}, {hl_group}, {line},
- {col_start}, {col_end})
- Adds a highlight to buffer.
+ *nvim_buf_set_keymap()*
+nvim_buf_set_keymap({buffer}, {mode}, {lhs}, {rhs}, {opts})
+ Sets a buffer-local |mapping| for the given mode.
- 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.
+ Parameters: ~
+ {buffer} Buffer handle, or 0 for current buffer
- 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` .
+ See also: ~
+ |nvim_set_keymap()|
- 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.
+ *nvim_buf_set_lines()*
+nvim_buf_set_lines({buffer}, {start}, {end}, {strict_indexing},
+ {replacement})
+ 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.
Parameters: ~
- {buffer} Buffer handle, or 0 for current buffer
- {ns_id} namespace to use or -1 for ungrouped
- highlight
- {hl_group} Name of the highlight group to use
- {line} Line to highlight (zero-indexed)
- {col_start} Start of (byte-indexed) column range to
- highlight
- {col_end} End of (byte-indexed) column range to
- highlight, or -1 to highlight to end of line
+ {buffer} Buffer handle, or 0 for current buffer
+ {start} First line index
+ {end} Last line index (exclusive)
+ {strict_indexing} Whether out-of-bounds should be an
+ error.
+ {replacement} Array of lines to use as replacement
- Return: ~
- The ns_id that was used
+nvim_buf_set_name({buffer}, {name}) *nvim_buf_set_name()*
+ Sets the full file name for a buffer
- *nvim_buf_clear_namespace()*
-nvim_buf_clear_namespace({buffer}, {ns_id}, {line_start}, {line_end})
- Clears namespaced objects, highlights and virtual text, from a
- line range
+ Parameters: ~
+ {buffer} Buffer handle, or 0 for current buffer
+ {name} Buffer name
- Lines are 0-indexed. |api-indexing| To clear the namespace in
- the entire buffer, specify line_start=0 and line_end=-1.
+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
- {ns_id} Namespace to clear, or -1 to clear all
- namespaces.
- {line_start} Start of range of lines to clear
- {line_end} End of range of lines to clear (exclusive)
- or -1 to clear to end of buffer.
+ {buffer} Buffer handle, or 0 for current buffer
+ {name} Option name
+ {value} Option value
+
+nvim_buf_set_var({buffer}, {name}, {value}) *nvim_buf_set_var()*
+ Sets a buffer-scoped (b:) variable
+
+ Parameters: ~
+ {buffer} Buffer handle, or 0 for current buffer
+ {name} Variable name
+ {value} Variable value
*nvim_buf_set_virtual_text()*
-nvim_buf_set_virtual_text({buffer}, {ns_id}, {line}, {chunks}, {opts})
+nvim_buf_set_virtual_text({buffer}, {src_id}, {line}, {chunks},
+ {opts})
Set the virtual text (annotation) for a buffer line.
By default (and currently the only option) the text will be
@@ -1821,105 +2091,77 @@ nvim_buf_set_virtual_text({buffer}, {ns_id}, {line}, {chunks}, {opts})
Return: ~
The ns_id that was used
-nvim__buf_stats({buffer}) *nvim__buf_stats()*
- TODO: Documentation
-
==============================================================================
Window Functions *api-window*
-nvim_win_get_buf({window}) *nvim_win_get_buf()*
- Gets the current buffer in a window
+nvim_win_close({window}, {force}) *nvim_win_close()*
+ Closes the window (like |:close| with a |window-ID|).
Parameters: ~
{window} Window handle, or 0 for current window
+ {force} 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.
- Return: ~
- Buffer handle
-
-nvim_win_set_buf({window}, {buffer}) *nvim_win_set_buf()*
- Sets the current buffer in a window, without side-effects
+nvim_win_del_var({window}, {name}) *nvim_win_del_var()*
+ Removes a window-scoped (w:) variable
Parameters: ~
{window} Window handle, or 0 for current window
- {buffer} Buffer handle
+ {name} Variable name
-nvim_win_get_cursor({window}) *nvim_win_get_cursor()*
- Gets the (1,0)-indexed cursor position in the window.
- |api-indexing|
+nvim_win_get_buf({window}) *nvim_win_get_buf()*
+ Gets the current buffer in a window
Parameters: ~
{window} Window handle, or 0 for current window
Return: ~
- (row, col) tuple
+ Buffer handle
-nvim_win_set_cursor({window}, {pos}) *nvim_win_set_cursor()*
- Sets the (1,0)-indexed cursor position in the window.
- |api-indexing|
+nvim_win_get_config({window}) *nvim_win_get_config()*
+ Gets window configuration.
- Parameters: ~
- {window} Window handle, or 0 for current window
- {pos} (row, col) tuple representing the new position
+ The returned value may be given to |nvim_open_win()|.
-nvim_win_get_height({window}) *nvim_win_get_height()*
- Gets the window height
+ `relative` is empty for normal windows.
Parameters: ~
{window} Window handle, or 0 for current window
Return: ~
- Height as a count of rows
-
-nvim_win_set_height({window}, {height}) *nvim_win_set_height()*
- Sets the window height. This will only succeed if the screen
- is split horizontally.
-
- Parameters: ~
- {window} Window handle, or 0 for current window
- {height} Height as a count of rows
+ Map defining the window configuration, see
+ |nvim_open_win()|
-nvim_win_get_width({window}) *nvim_win_get_width()*
- Gets the window width
+nvim_win_get_cursor({window}) *nvim_win_get_cursor()*
+ Gets the (1,0)-indexed cursor position in the window.
+ |api-indexing|
Parameters: ~
{window} Window handle, or 0 for current window
Return: ~
- Width as a count of columns
-
-nvim_win_set_width({window}, {width}) *nvim_win_set_width()*
- Sets the window width. This will only succeed if the screen is
- split vertically.
-
- Parameters: ~
- {window} Window handle, or 0 for current window
- {width} Width as a count of columns
+ (row, col) tuple
-nvim_win_get_var({window}, {name}) *nvim_win_get_var()*
- Gets a window-scoped (w:) variable
+nvim_win_get_height({window}) *nvim_win_get_height()*
+ Gets the window height
Parameters: ~
{window} Window handle, or 0 for current window
- {name} Variable name
Return: ~
- Variable value
+ Height as a count of rows
-nvim_win_set_var({window}, {name}, {value}) *nvim_win_set_var()*
- Sets a window-scoped (w:) variable
+nvim_win_get_number({window}) *nvim_win_get_number()*
+ Gets the window number
Parameters: ~
{window} Window handle, or 0 for current window
- {name} Variable name
- {value} Variable value
-
-nvim_win_del_var({window}, {name}) *nvim_win_del_var()*
- Removes a window-scoped (w:) variable
- Parameters: ~
- {window} Window handle, or 0 for current window
- {name} Variable name
+ Return: ~
+ Window number
nvim_win_get_option({window}, {name}) *nvim_win_get_option()*
Gets a window option value
@@ -1931,15 +2173,6 @@ nvim_win_get_option({window}, {name}) *nvim_win_get_option()*
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
-
nvim_win_get_position({window}) *nvim_win_get_position()*
Gets the window position in display cells. First position is
zero.
@@ -1959,14 +2192,24 @@ nvim_win_get_tabpage({window}) *nvim_win_get_tabpage()*
Return: ~
Tabpage that contains the window
-nvim_win_get_number({window}) *nvim_win_get_number()*
- Gets the window number
+nvim_win_get_var({window}, {name}) *nvim_win_get_var()*
+ Gets a window-scoped (w:) variable
Parameters: ~
{window} Window handle, or 0 for current window
+ {name} Variable name
Return: ~
- Window number
+ Variable value
+
+nvim_win_get_width({window}) *nvim_win_get_width()*
+ Gets the window width
+
+ Parameters: ~
+ {window} Window handle, or 0 for current window
+
+ Return: ~
+ Width as a count of columns
nvim_win_is_valid({window}) *nvim_win_is_valid()*
Checks if a window is valid
@@ -1977,6 +2220,13 @@ nvim_win_is_valid({window}) *nvim_win_is_valid()*
Return: ~
true if the window is valid, false otherwise
+nvim_win_set_buf({window}, {buffer}) *nvim_win_set_buf()*
+ Sets the current buffer in a window, without side-effects
+
+ Parameters: ~
+ {window} Window handle, or 0 for current window
+ {buffer} Buffer handle
+
nvim_win_set_config({window}, {config}) *nvim_win_set_config()*
Configures window layout. Currently only for floating and
external windows (including changing a split window to those
@@ -1994,67 +2244,76 @@ nvim_win_set_config({window}, {config}) *nvim_win_set_config()*
See also: ~
|nvim_open_win()|
-nvim_win_get_config({window}) *nvim_win_get_config()*
- Gets window configuration.
+nvim_win_set_cursor({window}, {pos}) *nvim_win_set_cursor()*
+ Sets the (1,0)-indexed cursor position in the window.
+ |api-indexing|
- The returned value may be given to |nvim_open_win()|.
+ Parameters: ~
+ {window} Window handle, or 0 for current window
+ {pos} (row, col) tuple representing the new position
- `relative` is empty for normal windows.
+nvim_win_set_height({window}, {height}) *nvim_win_set_height()*
+ Sets the window height. This will only succeed if the screen
+ is split horizontally.
Parameters: ~
{window} Window handle, or 0 for current window
+ {height} Height as a count of rows
- Return: ~
- Map defining the window configuration, see
- |nvim_open_win()|
+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)
-nvim_win_close({window}, {force}) *nvim_win_close()*
- Closes the window (like |:close| with a |window-ID|).
+ Parameters: ~
+ {window} Window handle, or 0 for current window
+ {name} Option name
+ {value} Option value
+
+nvim_win_set_var({window}, {name}, {value}) *nvim_win_set_var()*
+ Sets a window-scoped (w:) variable
Parameters: ~
{window} Window handle, or 0 for current window
- {force} 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.
+ {name} Variable name
+ {value} Variable value
+
+nvim_win_set_width({window}, {width}) *nvim_win_set_width()*
+ Sets the window width. This will only succeed if the screen is
+ split vertically.
+
+ Parameters: ~
+ {window} Window handle, or 0 for current window
+ {width} Width as a count of columns
==============================================================================
Tabpage Functions *api-tabpage*
-nvim_tabpage_list_wins({tabpage}) *nvim_tabpage_list_wins()*
- Gets the windows in a tabpage
+nvim_tabpage_del_var({tabpage}, {name}) *nvim_tabpage_del_var()*
+ Removes a tab-scoped (t:) variable
Parameters: ~
{tabpage} Tabpage handle, or 0 for current tabpage
+ {name} Variable name
- Return: ~
- List of windows in `tabpage`
-
-nvim_tabpage_get_var({tabpage}, {name}) *nvim_tabpage_get_var()*
- Gets a tab-scoped (t:) variable
+nvim_tabpage_get_number({tabpage}) *nvim_tabpage_get_number()*
+ Gets the tabpage number
Parameters: ~
{tabpage} Tabpage handle, or 0 for current tabpage
- {name} Variable name
Return: ~
- Variable value
+ Tabpage number
-nvim_tabpage_set_var({tabpage}, {name}, {value}) *nvim_tabpage_set_var()*
- Sets a tab-scoped (t:) variable
+nvim_tabpage_get_var({tabpage}, {name}) *nvim_tabpage_get_var()*
+ Gets a tab-scoped (t:) variable
Parameters: ~
{tabpage} Tabpage handle, or 0 for current tabpage
{name} Variable name
- {value} Variable value
-
-nvim_tabpage_del_var({tabpage}, {name}) *nvim_tabpage_del_var()*
- Removes a tab-scoped (t:) variable
- Parameters: ~
- {tabpage} Tabpage handle, or 0 for current tabpage
- {name} Variable name
+ Return: ~
+ Variable value
nvim_tabpage_get_win({tabpage}) *nvim_tabpage_get_win()*
Gets the current window in a tabpage
@@ -2065,23 +2324,32 @@ nvim_tabpage_get_win({tabpage}) *nvim_tabpage_get_win()*
Return: ~
Window handle
-nvim_tabpage_get_number({tabpage}) *nvim_tabpage_get_number()*
- Gets the tabpage number
+nvim_tabpage_is_valid({tabpage}) *nvim_tabpage_is_valid()*
+ Checks if a tabpage is valid
Parameters: ~
{tabpage} Tabpage handle, or 0 for current tabpage
Return: ~
- Tabpage number
+ true if the tabpage is valid, false otherwise
-nvim_tabpage_is_valid({tabpage}) *nvim_tabpage_is_valid()*
- Checks if a tabpage is valid
+nvim_tabpage_list_wins({tabpage}) *nvim_tabpage_list_wins()*
+ Gets the windows in a tabpage
Parameters: ~
{tabpage} Tabpage handle, or 0 for current tabpage
Return: ~
- true if the tabpage is valid, false otherwise
+ List of windows in `tabpage`
+
+ *nvim_tabpage_set_var()*
+nvim_tabpage_set_var({tabpage}, {name}, {value})
+ Sets a tab-scoped (t:) variable
+
+ Parameters: ~
+ {tabpage} Tabpage handle, or 0 for current tabpage
+ {name} Variable name
+ {value} Variable value
==============================================================================
@@ -2110,12 +2378,38 @@ nvim_ui_detach() *nvim_ui_detach()*
Removes the client from the list of UIs. |nvim_list_uis()|
-nvim_ui_try_resize({width}, {height}) *nvim_ui_try_resize()*
- TODO: Documentation
+ *nvim_ui_pum_set_bounds()*
+nvim_ui_pum_set_bounds({width}, {height}, {row}, {col})
+ Tells Nvim the geometry of the popumenu, to align floating
+ windows with an external popup menu.
+
+ Note that this method is not to be confused with
+ |nvim_ui_pum_set_height()|, which sets the number of visible
+ items in the popup menu, while this function sets the bounding
+ box of the popup menu, including visual decorations such as
+ boarders and sliders. Floats need not use the same font size,
+ nor be anchored to exact grid corners, so one can set
+ floating-point numbers to the popup menu geometry.
+
+ Parameters: ~
+ {width} Popupmenu width.
+ {height} Popupmenu height.
+ {row} Popupmenu row.
+ {col} Popupmenu height.
+
+nvim_ui_pum_set_height({height}) *nvim_ui_pum_set_height()*
+ Tells Nvim the number of elements displaying in the popumenu,
+ to decide <PageUp> and <PageDown> movement.
+
+ Parameters: ~
+ {height} Popupmenu height, must be greater than zero.
nvim_ui_set_option({name}, {value}) *nvim_ui_set_option()*
TODO: Documentation
+nvim_ui_try_resize({width}, {height}) *nvim_ui_try_resize()*
+ TODO: Documentation
+
*nvim_ui_try_resize_grid()*
nvim_ui_try_resize_grid({grid}, {width}, {height})
Tell Nvim to resize a grid. Triggers a grid_resize event with
@@ -2129,11 +2423,4 @@ nvim_ui_try_resize_grid({grid}, {width}, {height})
{width} The new requested width.
{height} The new requested height.
-nvim_ui_pum_set_height({height}) *nvim_ui_pum_set_height()*
- Tells Nvim the number of elements displaying in the popumenu,
- to decide <PageUp> and <PageDown> movement.
-
- Parameters: ~
- {height} Popupmenu height, must be greater than zero.
-
vim:tw=78:ts=8:ft=help:norl:
diff --git a/runtime/doc/autocmd.txt b/runtime/doc/autocmd.txt
index 34ea083f96..f1753b75cc 100644
--- a/runtime/doc/autocmd.txt
+++ b/runtime/doc/autocmd.txt
@@ -251,7 +251,6 @@ Name triggered by ~
Buffers
|BufAdd| just after adding a buffer to the buffer list
-|BufCreate| just after adding a buffer to the buffer list
|BufDelete| before deleting a buffer from the buffer list
|BufWipeout| before completely deleting a buffer
@@ -318,6 +317,7 @@ Name triggered by ~
|CursorMoved| the cursor was moved in Normal mode
|CursorMovedI| the cursor was moved in Insert mode
+|WinClosed| after closing a window
|WinNew| after creating a new window
|WinEnter| after entering another window
|WinLeave| before leaving a window
@@ -358,7 +358,10 @@ Name triggered by ~
|MenuPopup| just before showing the popup menu
|CompleteChanged| after popup menu changed, not fired on popup menu hide
-|CompleteDone| after Insert mode completion is done
+|CompleteDonePre| after Insert mode completion is done, before clearing
+ info
+|CompleteDone| after Insert mode completion is done, after clearing
+ info
|User| to be used in combination with ":doautocmd"
|Signal| after Nvim receives a signal
@@ -367,32 +370,29 @@ Name triggered by ~
The alphabetical list of autocommand events: *autocmd-events-abc*
- *BufCreate* *BufAdd*
-BufAdd or BufCreate Just after creating a new buffer which is
+ *BufAdd*
+BufAdd Just after creating a new buffer which is
added to the buffer list, or adding a buffer
- to the buffer list.
- Also used just after a buffer in the buffer
- list has been renamed.
- The BufCreate event is for historic reasons.
- NOTE: When this autocommand is executed, the
- current buffer "%" may be different from the
- buffer being created "<afile>".
+ to the buffer list, a buffer in the buffer
+ list was renamed.
+ Before |BufEnter|.
+ NOTE: Current buffer "%" may be different from
+ the buffer being created "<afile>".
*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: When this autocommand is executed, the
- current buffer "%" may be different from the
- buffer being deleted "<afile>" and "<abuf>".
- Don't change to another buffer, it will cause
- problems.
+ NOTE: Current buffer "%" may be different from
+ the buffer being deleted "<afile>" and "<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, after the
- BufReadPost autocommands.
+ After |BufAdd|.
+ After |BufReadPost|.
*BufFilePost*
BufFilePost After changing the name of the current buffer
with the ":file" or ":saveas" command.
@@ -400,27 +400,26 @@ BufFilePost After changing the name of the current buffer
BufFilePre Before changing the name of the current buffer
with the ":file" or ":saveas" command.
*BufHidden*
-BufHidden Just before a buffer becomes hidden. That is,
- when there are no longer windows that show
- the buffer, but the buffer is not unloaded or
- deleted. Not used for ":qa" or ":q" when
- exiting Vim.
- NOTE: When this autocommand is executed, the
- current buffer "%" may be different from the
- buffer being unloaded "<afile>".
+BufHidden Before a buffer becomes hidden: when there are
+ no longer windows that show the buffer, but
+ 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>".
*BufLeave*
BufLeave Before leaving to another buffer. Also when
leaving or closing the current window and the
new current window is not for the same buffer.
+
Not used for ":qa" or ":q" when exiting Vim.
*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: When this autocommand is executed, the
- current buffer "%" may be different from the
- buffer being created "<afile>".
+ NOTE: Current buffer "%" may be different from
+ the buffer being created "<afile>".
*BufNewFile*
BufNewFile When starting to edit a file that doesn't
exist. Can be used to read in a skeleton
@@ -428,16 +427,17 @@ BufNewFile When starting to edit a file that doesn't
*BufRead* *BufReadPost*
BufRead or BufReadPost When starting to edit a new buffer, after
reading the file into the buffer, before
- executing the modelines. See |BufWinEnter|
- for when you need to do something after
- processing the modelines.
- This does NOT work for ":r file". Not used
- when the file doesn't exist. Also used after
- successfully recovering a file.
- Also triggered for the filetypedetect group
- when executing ":filetype detect" and when
- writing an unnamed buffer in a way that the
- buffer gets a name.
+ processing modelines. See |BufWinEnter| to do
+ something after processing modelines.
+ Also triggered:
+ - when writing an unnamed buffer such that the
+ buffer gets a name
+ - after successfully recovering a file
+ - for the "filetypedetect" group when
+ executing ":filetype detect"
+ Not triggered:
+ - for ":r file"
+ - if the file doesn't exist
*BufReadCmd*
BufReadCmd Before starting to edit a new buffer. Should
read the file into the buffer. |Cmd-event|
@@ -446,41 +446,37 @@ 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. This is when the
- text in the buffer is going to be freed. This
- may be after a BufWritePost and before a
- BufDelete. Also used for all buffers that are
- loaded when Vim is going to exit.
- NOTE: When this autocommand is executed, the
- current buffer "%" may be different from the
- buffer being unloaded "<afile>".
- Don't change to another buffer or window, it
- will cause problems!
- When exiting and v:dying is 2 or more this
- event is not triggered.
+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>".
+ Do not switch buffers or windows!
+ Not triggered when exiting and v:dying is 2 or
+ more.
*BufWinEnter*
BufWinEnter After a buffer is displayed in a window. This
- can be when the buffer is loaded (after
- processing the modelines) or when a hidden
- buffer is displayed in a window (and is no
- longer hidden).
- Does not happen for |:split| without
- arguments, since you keep editing the same
- buffer, or ":split" with a file that's already
- open in a window, because it re-uses an
- existing buffer. But it does happen for a
- ":split" with the name of the current buffer,
- since it reloads that buffer.
+ may be when the buffer is loaded (after
+ processing modelines) or when a hidden buffer
+ is displayed (and is no longer hidden).
+
+ Not triggered for |:split| without arguments,
+ since the buffer does not change, or :split
+ with a file already open in a window.
+ Triggered for ":split" with the name of the
+ current buffer, since it reloads that buffer.
*BufWinLeave*
BufWinLeave Before a buffer is removed from a window.
Not when it's still visible in another window.
- Also triggered when exiting. It's triggered
- before BufUnload or BufHidden.
- NOTE: When this autocommand is executed, the
- current buffer "%" may be different from the
- buffer being unloaded "<afile>".
- When exiting and v:dying is 2 or more this
- event is not triggered.
+ Also triggered when exiting.
+ Before BufUnload, BufHidden.
+ NOTE: Current buffer "%" may be different from
+ the buffer being unloaded "<afile>".
+ Not triggered when exiting and v:dying is 2 or
+ more.
*BufWipeout*
BufWipeout Before completely deleting a buffer. The
BufUnload and BufDelete events may be called
@@ -488,11 +484,9 @@ 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: When this autocommand is executed, the
- current buffer "%" may be different from the
- buffer being deleted "<afile>".
- Don't change to another buffer, it will cause
- problems.
+ NOTE: Current buffer "%" may be different from
+ the buffer being deleted "<afile>".
+ Do not change to another buffer.
*BufWrite* *BufWritePre*
BufWrite or BufWritePre Before writing the whole buffer to a file.
*BufWriteCmd*
@@ -526,7 +520,7 @@ CmdUndefined When a user command is used but it isn't
defined. Useful for defining a command only
when it's used. The pattern is matched
against the command name. Both <amatch> and
- <afile> are set to the name of the command.
+ <afile> expand to the command name.
NOTE: Autocompletion won't work until the
command is defined. An alternative is to
always define the user command and have it
@@ -535,12 +529,12 @@ CmdUndefined When a user command is used but it isn't
CmdlineChanged After a change was made to the text inside
command line. Be careful not to mess up the
command line, it may cause Vim to lock up.
- <afile> is set to the |cmdline-char|.
+ <afile> expands to the |cmdline-char|.
*CmdlineEnter*
CmdlineEnter After entering the command-line (including
non-interactive use of ":" in a mapping: use
|<Cmd>| instead to avoid this).
- <afile> is set to the |cmdline-char|.
+ <afile> expands to the |cmdline-char|.
Sets these |v:event| keys:
cmdlevel
cmdtype
@@ -548,28 +542,26 @@ CmdlineEnter After entering the command-line (including
CmdlineLeave Before leaving the command-line (including
non-interactive use of ":" in a mapping: use
|<Cmd>| instead to avoid this).
- <afile> is set to the |cmdline-char|.
+ <afile> expands to the |cmdline-char|.
Sets these |v:event| keys:
abort (mutable)
cmdlevel
cmdtype
Note: `abort` can only be changed from false
- to true. An autocmd cannot execute an already
- aborted cmdline by changing it to false.
+ to true: cannot execute an already aborted
+ cmdline by changing it to false.
*CmdwinEnter*
CmdwinEnter After entering the command-line window.
Useful for setting options specifically for
- this special type of window. This is
- triggered _instead_ of BufEnter and WinEnter.
- <afile> is set to a single character,
+ this special type of window.
+ <afile> expands to a single character,
indicating the type of command-line.
|cmdwin-char|
*CmdwinLeave*
CmdwinLeave Before leaving the command-line window.
Useful to clean up any global setting done
- with CmdwinEnter. This is triggered _instead_
- of BufLeave and WinLeave.
- <afile> is set to a single character,
+ with CmdwinEnter.
+ <afile> expands to a single character,
indicating the type of command-line.
|cmdwin-char|
*ColorScheme*
@@ -585,18 +577,11 @@ ColorSchemePre Before loading a color scheme. |:colorscheme|
Useful to setup removing things added by a
color scheme, before another one is loaded.
- *CompleteDone*
-CompleteDone After Insert mode completion is done. Either
- when something was completed or abandoning
- completion. |ins-completion|
- The |v:completed_item| variable contains the
- completed item.
-
CompleteChanged *CompleteChanged*
After each time the Insert mode completion
menu changed. Not fired on popup menu hide,
- use |CompleteDone| for that. Never triggered
- recursively.
+ use |CompleteDonePre| or |CompleteDone| for
+ that.
Sets these |v:event| keys:
completed_item See |complete-items|.
@@ -607,7 +592,29 @@ CompleteChanged *CompleteChanged*
size total nr of items
scrollbar TRUE if visible
- It is not allowed to change the text |textlock|.
+ Non-recursive (event cannot trigger itself).
+ Cannot change the text. |textlock|
+
+ The size and position of the popup are also
+ available by calling |pum_getpos()|.
+
+ *CompleteDonePre*
+CompleteDonePre After Insert mode completion is done. Either
+ when something was completed or abandoning
+ completion. |ins-completion|
+ |complete_info()| can be used, the info is
+ cleared after triggering CompleteDonePre.
+ The |v:completed_item| variable contains
+ information about the completed item.
+
+ *CompleteDone*
+CompleteDone After Insert mode completion is done. Either
+ when something was completed or abandoning
+ completion. |ins-completion|
+ |complete_info()| cannot be used, the info is
+ cleared before triggering CompleteDone. Use
+ CompleteDonePre if you need it.
+ |v:completed_item| gives the completed item.
*CursorHold*
CursorHold When the user doesn't press a key for the time
@@ -637,9 +644,9 @@ CursorHold When the user doesn't press a key for the time
:let &ro = &ro
*CursorHoldI*
-CursorHoldI Just like CursorHold, but in Insert mode.
- Not triggered when waiting for another key,
- e.g. after CTRL-V, and not when in CTRL-X mode
+CursorHoldI Like CursorHold, but in Insert mode. Not
+ triggered when waiting for another key, e.g.
+ after CTRL-V, and not in CTRL-X mode
|insert_expand|.
*CursorMoved*
@@ -650,7 +657,7 @@ CursorMoved After the cursor was moved in Normal or Visual
Not triggered when there is typeahead or when
an operator is pending.
For an example see |match-parens|.
- Note: Cannot be skipped with `:noautocmd`.
+ Note: Cannot be skipped with |:noautocmd|.
Careful: This is triggered very often, don't
do anything that the user does not expect or
that is slow.
@@ -668,11 +675,11 @@ DirChanged After the |current-directory| was changed.
Sets these |v:event| keys:
cwd: current working directory
scope: "global", "tab", "window"
- Recursion is ignored.
+ Non-recursive (event cannot trigger itself).
*FileAppendCmd*
FileAppendCmd Before appending to a file. Should do the
appending to the file. Use the '[ and ']
- marks for the range of lines.|Cmd-event|
+ marks for the range of lines. |Cmd-event|
*FileAppendPost*
FileAppendPost After appending to a file.
*FileAppendPre*
@@ -680,19 +687,18 @@ FileAppendPre Before appending to a file. Use the '[ and ']
marks for the range of lines.
*FileChangedRO*
FileChangedRO Before making the first change to a read-only
- file. Can be used to check-out the file from
+ file. Can be used to checkout the file from
a source control system. Not triggered when
the change was caused by an autocommand.
- This event is triggered when making the first
- change in a buffer or the first change after
- 'readonly' was set, just before the change is
- applied to the text.
+ Triggered when making the first change in
+ a buffer or the first change after 'readonly'
+ was set, just before the change is applied to
+ the text.
WARNING: If the autocommand moves the cursor
the effect of the change is undefined.
*E788*
- It is not allowed to change to another buffer
- here. You can reload the buffer but not edit
- another one.
+ Cannot switch buffers. You can reload the
+ buffer but not edit another one.
*E881*
If the number of lines changes saving for undo
may fail and the change will be aborted.
@@ -704,34 +710,28 @@ ExitPre When using `:quit`, `:wq` in a way it makes
cancelled if there is a modified buffer that
isn't automatically saved, use |VimLeavePre|
for really exiting.
+ See also |QuitPre|, |WinClosed|.
*FileChangedShell*
FileChangedShell When Vim notices that the modification time of
a file has changed since editing started.
Also when the file attributes of the file
change or when the size of the file changes.
|timestamp|
- Mostly triggered after executing a shell
- command, but also with a |:checktime| command
- or when gvim regains input focus.
- This autocommand is triggered for each changed
- file. It is not used when 'autoread' is set
- and the buffer was not changed. If a
- FileChangedShell autocommand is present the
- warning message and prompt is not given.
- The |v:fcs_reason| variable is set to indicate
- what happened and |v:fcs_choice| can be used
- to tell Vim what to do next.
- NOTE: When this autocommand is executed, the
- current buffer "%" may be different from the
- buffer that was changed, which is in "<afile>".
- NOTE: The commands must not change the current
- buffer, jump to another buffer or delete a
- buffer. *E246* *E811*
- NOTE: This event never nests, to avoid an
- endless loop. This means that while executing
- commands for the FileChangedShell event no
- other FileChangedShell event will be
- triggered.
+ Triggered for each changed file, after:
+ - executing a shell command
+ - |:checktime|
+ - |FocusGained|
+ Not used when 'autoread' is set and the buffer
+ was not changed. If a FileChangedShell
+ autocommand exists the warning message and
+ 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>".
+ *E246* *E811*
+ Cannot switch, jump to or delete buffers.
+ Non-recursive (event cannot trigger itself).
*FileChangedShellPost*
FileChangedShellPost After handling a file that was changed outside
of Vim. Can be used to update the statusline.
@@ -748,10 +748,10 @@ FileReadPre Before reading a file with a ":read" command.
*FileType*
FileType When the 'filetype' option has been set. The
pattern is matched against the filetype.
- <afile> can be used for the name of the file
- where this option was set, and <amatch> for
- the new value of 'filetype'. Navigating to
- another window or buffer is not allowed.
+ <afile> is the name of the file where this
+ option was set. <amatch> is the new value of
+ 'filetype'.
+ Cannot switch windows or buffers.
See |filetypes|.
*FileWriteCmd*
FileWriteCmd Before writing to a file, when not writing the
@@ -844,11 +844,12 @@ TextYankPost Just after a |yank| or |deleting| command, but not
regcontents
regname
regtype
+ visual
The `inclusive` flag combined with the |'[|
and |']| marks can be used to calculate the
precise region of the operation.
- Recursion is ignored.
+ Non-recursive (event cannot trigger itself).
Cannot change the text. |textlock|
*InsertEnter*
InsertEnter Just before starting Insert mode. Also for
@@ -915,8 +916,8 @@ OptionSet After setting an option (except during
always use |:noautocmd| to prevent triggering
OptionSet.
- Recursion is ignored, thus |:set| in the
- autocommand does not trigger OptionSet again.
+ Non-recursive: |:set| in the autocommand does
+ not trigger OptionSet again.
*QuickFixCmdPre*
QuickFixCmdPre Before a quickfix command is run (|:make|,
@@ -948,7 +949,7 @@ QuitPre When using `:quit`, `:wq` or `:qall`, before
or quits Vim. Can be used to close any
non-essential window if the current window is
the last ordinary window.
- Also see |ExitPre|.
+ See also |ExitPre|, ||WinClosed|.
*RemoteReply*
RemoteReply When a reply from a Vim that functions as
server was received |server2client()|. The
@@ -1024,33 +1025,32 @@ SwapExists Detected an existing swap file when starting
When set to an empty string the user will be
asked, as if there was no SwapExists autocmd.
*E812*
- It is not allowed to change to another buffer,
- change a buffer name or change directory
- here.
+ Cannot change to another buffer, change
+ the buffer name or change directory.
*Syntax*
Syntax When the 'syntax' option has been set. The
pattern is matched against the syntax name.
- <afile> can be used for the name of the file
- where this option was set, and <amatch> for
- the new value of 'syntax'.
+ <afile> expands to the name of the file where
+ this option was set. <amatch> expands to the
+ new value of 'syntax'.
See |:syn-on|.
*TabEnter*
TabEnter Just after entering a tab page. |tab-page|
- After triggering the WinEnter and before
- triggering the BufEnter event.
+ After WinEnter.
+ Before BufEnter.
*TabLeave*
TabLeave Just before leaving a tab page. |tab-page|
- A WinLeave event will have been triggered
- first.
+ After WinLeave.
*TabNew*
TabNew When creating a new tab page. |tab-page|
- After WinEnter and before TabEnter.
+ After WinEnter.
+ Before TabEnter.
*TabNewEntered*
TabNewEntered After entering a new tab page. |tab-page|
After BufEnter.
*TabClosed*
-TabClosed After closing a tab page. <afile> can be used
- for the tab page number.
+TabClosed After closing a tab page. <afile> expands to
+ the tab page number.
*TermOpen*
TermOpen When a |terminal| job is starting. Can be
used to configure the terminal buffer.
@@ -1066,10 +1066,9 @@ TermClose When a |terminal| job ends.
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. Note that this event may be
- triggered halfway through another event
- (especially if file I/O, a shell command, or
- anything else that takes time is involved).
+ terminal version. May be triggered halfway
+ through another event (file I/O, a shell
+ command, or anything else that takes time).
*TextChanged*
TextChanged After a change was made to the text in the
current buffer in Normal mode. That is after
@@ -1139,6 +1138,12 @@ VimResized After the Vim window was resized, thus 'lines'
VimResume After Nvim resumes from |suspend| state.
*VimSuspend*
VimSuspend Before Nvim enters |suspend| state.
+ *WinClosed*
+WinClosed After closing a window. <afile> expands to the
+ |window-ID|.
+ After WinLeave.
+ Non-recursive (event cannot trigger itself).
+ See also |ExitPre|, |QuitPre|.
*WinEnter*
WinEnter After entering another window. Not done for
the first window, when Vim has just started.
@@ -1156,11 +1161,11 @@ WinLeave Before leaving a window. If the window to be
executes the BufLeave autocommands before the
WinLeave autocommands (but not for ":new").
Not used for ":qa" or ":q" when exiting Vim.
-
+ After WinClosed.
*WinNew*
WinNew When a new window was created. Not done for
the first window, when Vim has just started.
- Before a WinEnter event.
+ Before WinEnter.
==============================================================================
6. Patterns *autocmd-pattern* *{pat}*
diff --git a/runtime/doc/change.txt b/runtime/doc/change.txt
index bd3f22a371..dcebbc524c 100644
--- a/runtime/doc/change.txt
+++ b/runtime/doc/change.txt
@@ -90,7 +90,7 @@ start and end of the motion are not in the same line, and there are only
blanks before the start and there are no non-blanks after the end of the
motion, the delete becomes linewise. This means that the delete also removes
the line of blanks that you might expect to remain. Use the |o_v| operator to
-force the motion to be characterwise.
+force the motion to be charwise.
Trying to delete an empty region of text (e.g., "d0" in the first column)
is an error when 'cpoptions' includes the 'E' flag.
@@ -1074,7 +1074,7 @@ also use these commands to move text from one file to another, because Vim
preserves all registers when changing buffers (the CTRL-^ command is a quick
way to toggle between two files).
- *linewise-register* *characterwise-register*
+ *linewise-register* *charwise-register*
You can repeat the put commands with "." (except for :put) and undo them. If
the command that was used to get the text into the register was |linewise|,
Vim inserts the text below ("p") or above ("P") the line where the cursor is.
@@ -1116,10 +1116,9 @@ this happen. However, if the width of the block is not a multiple of a <Tab>
width and the text after the inserted block contains <Tab>s, that text may be
misaligned.
-Note that after a characterwise yank command, Vim leaves the cursor on the
-first yanked character that is closest to the start of the buffer. This means
-that "yl" doesn't move the cursor, but "yh" moves the cursor one character
-left.
+Note that after a charwise yank command, Vim leaves the cursor on the first
+yanked character that is closest to the start of the buffer. This means that
+"yl" doesn't move the cursor, but "yh" moves the cursor one character left.
Rationale: In Vi the "y" command followed by a backwards motion would
sometimes not move the cursor to the first yanked character,
because redisplaying was skipped. In Vim it always moves to
diff --git a/runtime/doc/cmdline.txt b/runtime/doc/cmdline.txt
index ee1f76e4e4..b31177ce0e 100644
--- a/runtime/doc/cmdline.txt
+++ b/runtime/doc/cmdline.txt
@@ -1122,11 +1122,9 @@ edited as described in |cmdwin-char|.
AUTOCOMMANDS
-Two autocommand events are used: |CmdwinEnter| and |CmdwinLeave|. Since this
-window is of a special type, the WinEnter, WinLeave, BufEnter and BufLeave
-events are not triggered. You can use the Cmdwin events to do settings
-specifically for the command-line window. Be careful not to cause side
-effects!
+Two autocommand events are used: |CmdwinEnter| and |CmdwinLeave|. You can use
+the Cmdwin events to do settings specifically for the command-line window.
+Be careful not to cause side effects!
Example: >
:au CmdwinEnter : let b:cpt_save = &cpt | set cpt=.
:au CmdwinLeave : let &cpt = b:cpt_save
diff --git a/runtime/doc/deprecated.txt b/runtime/doc/deprecated.txt
index b76a37810c..ce075e1bee 100644
--- a/runtime/doc/deprecated.txt
+++ b/runtime/doc/deprecated.txt
@@ -14,6 +14,8 @@ updated.
API ~
*nvim_buf_clear_highlight()* Use |nvim_buf_clear_namespace()| instead.
+*nvim_command_output()* Use |nvim_exec()| instead.
+*nvim_execute_lua()* Use |nvim_exec_lua()| instead.
Commands ~
*:rv*
@@ -26,6 +28,7 @@ Environment Variables ~
$NVIM_LISTEN_ADDRESS is ignored.
Events ~
+*BufCreate* Use |BufAdd| instead.
*EncodingChanged* Never fired; 'encoding' is always "utf-8".
*FileEncoding* Never fired; equivalent to |EncodingChanged|.
*GUIEnter* Never fired; use |UIEnter| instead.
diff --git a/runtime/doc/develop.txt b/runtime/doc/develop.txt
index 90c2e30771..09c5b7c4ad 100644
--- a/runtime/doc/develop.txt
+++ b/runtime/doc/develop.txt
@@ -143,6 +143,87 @@ DOCUMENTATION *dev-doc*
/// @param dirname The path fragment before `pend`
<
+C docstrings ~
+
+Nvim API documentation lives in the source code, as docstrings (Doxygen
+comments) on the function definitions. The |api| :help is generated
+from the docstrings defined in src/nvim/api/*.c.
+
+Docstring format:
+- Lines start with `///`
+- Special tokens start with `@` followed by the token name:
+ `@note`, `@param`, `@returns`
+- Limited markdown is supported.
+ - List-items start with `-` (useful to nest or "indent")
+- Use `<pre>` for code samples.
+
+Example: the help for |nvim_open_win()| is generated from a docstring defined
+in src/nvim/api/vim.c like this: >
+
+ /// Opens a new window.
+ /// ...
+ ///
+ /// Example (Lua): window-relative float
+ /// <pre>
+ /// vim.api.nvim_open_win(0, false,
+ /// {relative='win', row=3, col=3, width=12, height=3})
+ /// </pre>
+ ///
+ /// @param buffer Buffer to display
+ /// @param enter Enter the window
+ /// @param config Map defining the window configuration. Keys:
+ /// - relative: Sets the window layout, relative to:
+ /// - "editor" The global editor grid.
+ /// - "win" Window given by the `win` field.
+ /// - "cursor" Cursor position in current window.
+ /// ...
+ /// @param[out] err Error details, if any
+ ///
+ /// @return Window handle, or 0 on error
+
+
+Lua docstrings ~
+ *dev-lua-doc*
+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`
+- Limited markdown is supported.
+ - List-items start with `-` (useful to nest or "indent")
+- Use `<pre>` for code samples.
+
+Example: the help for |vim.paste()| is generated from a docstring decorating
+vim.paste in src/nvim/lua/vim.lua like this: >
+
+ --- 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:
+ --- <pre>
+ --- vim.paste = (function()
+ --- local overridden = vim.paste
+ --- ...
+ --- end)()
+ --- </pre>
+ ---
+ --@see |paste|
+ ---
+ --@param lines ...
+ --@param phase ...
+ --@returns false if client should cancel the paste.
+
+
+LUA *dev-lua*
+
+- Keep the core Lua modules |lua-stdlib| simple. Avoid elaborate OOP or
+ pseudo-OOP designs. Plugin authors just want functions to call, they don't
+ want to learn a big, fancy inheritance hierarchy. So we should avoid complex
+ objects: tables are usually better.
+
+
API *dev-api*
Use this template to name new API functions:
@@ -155,10 +236,11 @@ 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
- get Get a thing (or group of things by query)
- set Set a thing (or group of things)
del Delete a thing (or group of things)
+ exec Execute code
+ get Get a thing (or group of things by query)
list Get all things
+ set Set a thing (or group of things)
Use consistent names for {thing} in all API functions. E.g. a buffer is called
"buf" everywhere, not "buffer" in some places and "buf" in others.
@@ -268,8 +350,8 @@ External UIs are expected to implement these common features:
chords (<C-,> <C-Enter> <C-S-x> <D-x>) and patterns ("shift shift") that do
not potentially conflict with Nvim defaults, plugins, etc.
- Consider the "option_set" |ui-global| event as a hint for other GUI
- behaviors. UI-related options ('guifont', 'ambiwidth', …) are published in
- this event.
+ behaviors. Various UI-related options ('guifont', 'ambiwidth', …) are
+ published in this event. See also "mouse_on", "mouse_off".
vim:tw=78:ts=8:noet:ft=help:norl:
diff --git a/runtime/doc/digraph.txt b/runtime/doc/digraph.txt
index b106e625f2..7f807b5eee 100644
--- a/runtime/doc/digraph.txt
+++ b/runtime/doc/digraph.txt
@@ -20,7 +20,9 @@ An alternative is using the 'keymap' option.
1. Defining digraphs *digraphs-define*
*:dig* *:digraphs*
-:dig[raphs] show currently defined digraphs.
+:dig[raphs][!] Show currently defined digraphs.
+ With [!] headers are used to make it a bit easier to
+ find a specific character.
*E104* *E39*
:dig[raphs] {char1}{char2} {number} ...
Add digraph {char1}{char2} to the list. {number} is
diff --git a/runtime/doc/editing.txt b/runtime/doc/editing.txt
index 23a65f16e4..ac398ec494 100644
--- a/runtime/doc/editing.txt
+++ b/runtime/doc/editing.txt
@@ -1265,7 +1265,7 @@ exist, the next-higher scope in the hierarchy applies.
other tabs and windows is not changed.
*:tcd-*
-:tcd[!] - Change to the previous current directory (before the
+:tc[d][!] - Change to the previous current directory (before the
previous ":tcd {path}" command).
*:tch* *:tchdir*
@@ -1280,7 +1280,7 @@ exist, the next-higher scope in the hierarchy applies.
:lch[dir][!] Same as |:lcd|.
*:lcd-*
-:lcd[!] - Change to the previous current directory (before the
+:lc[d][!] - Change to the previous current directory (before the
previous ":lcd {path}" command).
*:pw* *:pwd* *E187*
diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt
index 512cfc4e58..efb6272e58 100644
--- a/runtime/doc/eval.txt
+++ b/runtime/doc/eval.txt
@@ -1217,7 +1217,7 @@ lambda expression *expr-lambda* *lambda*
{args -> expr1} lambda expression
A lambda expression creates a new unnamed function which returns the result of
-evaluating |expr1|. Lambda expressions differ from |user-functions| in
+evaluating |expr1|. Lambda expressions differ from |user-function|s in
the following ways:
1. The body of the lambda expression is an |expr1| and not a sequence of |Ex|
@@ -1423,6 +1423,10 @@ PREDEFINED VIM VARIABLES *vim-variable* *v:var* *v:*
*E963*
Some variables can be set by the user, but the type cannot be changed.
+ *v:argv* *argv-variable*
+v:argv The command line arguments Vim was invoked with. This is a
+ list of strings. The first item is the Vim command.
+
*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.
@@ -1547,10 +1551,12 @@ v:errmsg Last given error message.
:if v:errmsg != ""
: ... handle error
<
- *v:errors* *errors-variable*
+ *v:errors* *errors-variable* *assert-return*
v:errors Errors found by assert functions, such as |assert_true()|.
This is a list of strings.
The assert functions append an item when an assert fails.
+ The return value indicates this: a one is returned if an item
+ was added to v:errors, otherwise zero is returned.
To remove old results make it empty: >
:let v:errors = []
< If v:errors is set to anything but a list it is made an empty
@@ -1585,6 +1591,8 @@ v:event Dictionary of event data for the current |autocommand|. Valid
operation.
regtype Type of register as returned by
|getregtype()|.
+ visual Selection is visual (as opposed to,
+ e.g., via motion).
completed_item Current selected complete item on
|CompleteChanged|, Is `{}` when no complete
item selected.
@@ -1735,6 +1743,10 @@ v:lnum Line number for the 'foldexpr' |fold-expr|, 'formatexpr' and
expressions is being evaluated. Read-only when in the
|sandbox|.
+ *v:lua* *lua-variable*
+v:lua Prefix for calling Lua functions from expressions.
+ See |v:lua-call| for more information.
+
*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
@@ -1984,9 +1996,12 @@ v:windowid Application-specific window "handle" which may be set by any
|window-ID|.
==============================================================================
-4. Builtin Functions *functions*
+4. Builtin Functions *vim-function* *functions*
+
+The Vimscript subsystem (referred to as "eval" internally) provides the
+following builtin functions. Scripts can also define |user-function|s.
-See |function-list| for a list grouped by what the function is used for.
+See |function-list| to browse functions by topic.
(Use CTRL-] on the function name to jump to the full explanation.)
@@ -2004,25 +2019,27 @@ 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
-assert_beeps({cmd}) none assert {cmd} causes a beep
+asin({expr}) Float arc sine of {expr}
+assert_beeps({cmd}) Number assert {cmd} causes a beep
assert_equal({exp}, {act} [, {msg}])
- none assert {exp} is equal to {act}
+ Number assert {exp} is equal to {act}
+assert_equalfile({fname-one}, {fname-two} [, {msg}])
+ Number assert file contents are equal
assert_exception({error} [, {msg}])
- none assert {error} is in v:exception
-assert_fails({cmd} [, {error}]) none assert {cmd} fails
+ Number assert {error} is in v:exception
+assert_fails({cmd} [, {error}]) Number assert {cmd} fails
assert_false({actual} [, {msg}])
- none assert {actual} is false
+ Number assert {actual} is false
assert_inrange({lower}, {upper}, {actual} [, {msg}])
- none assert {actual} is inside the range
+ Number assert {actual} is inside the range
assert_match({pat}, {text} [, {msg}])
- none assert {pat} matches {text}
+ Number assert {pat} matches {text}
assert_notequal({exp}, {act} [, {msg}])
- none assert {exp} is not equal {act}
+ Number assert {exp} is not equal {act}
assert_notmatch({pat}, {text} [, {msg}])
- none assert {pat} not matches {text}
-assert_report({msg}) none report a test failure
-assert_true({actual} [, {msg}]) none assert {actual} is true
-asin({expr}) Float arc sine of {expr}
+ 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({expr}, {expr}) Float arc tangent of {expr1} / {expr2}
browse({save}, {title}, {initdir}, {default})
@@ -2048,7 +2065,7 @@ 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/UTF8 value of first char in {expr}
cindent({lnum}) Number C indent for line {lnum}
-clearmatches() none clear all matches
+clearmatches([{win}]) none clear all matches
col({expr}) Number column nr of cursor or mark
complete({startcol}, {matches}) none set Insert mode completion
complete_add({expr}) Number add completion match
@@ -2073,6 +2090,7 @@ 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({expr}, {first}[, {last}])
@@ -2098,6 +2116,7 @@ extend({expr1}, {expr2} [, {expr3}])
exp({expr}) Float exponential of {expr}
expand({expr} [, {nosuf} [, {list}]])
any expand special keywords in {expr}
+expandcmd({expr}) String expand {expr} like with `:edit`
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
@@ -2107,6 +2126,7 @@ 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}
@@ -2154,7 +2174,7 @@ getjumplist([{winnr} [, {tabnr}]])
getline({lnum}) String line {lnum} of current buffer
getline({lnum}, {end}) List lines {lnum} to {end} of current buffer
getloclist({nr} [, {what}]) List list of location list items
-getmatches() List list of current matches
+getmatches([{win}]) List list of current matches
getpid() Number process ID of Vim
getpos({expr}) List position of cursor, mark, etc.
getqflist([{what}]) List list of quickfix items
@@ -2228,6 +2248,7 @@ libcallnr({lib}, {func}, {arg}) Number idem, but return a Number
line({expr}) 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
@@ -2245,7 +2266,7 @@ matchadd({group}, {pattern}[, {priority}[, {id}]])
matchaddpos({group}, {list}[, {priority}[, {id}]])
Number highlight positions with {group}
matcharg({nr}) List arguments of |:match|
-matchdelete({id}) Number delete match identified by {id}
+matchdelete({id} [, {win}]) Number delete match identified by {id}
matchend({expr}, {pat}[, {start}[, {count}]])
Number position where {pat} ends in {expr}
matchlist({expr}, {pat}[, {start}[, {count}]])
@@ -2269,6 +2290,11 @@ pathshorten({expr}) String shorten directory names in a path
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_addtext({buf}, {expr}) none add text to a prompt buffer
+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
pyeval({expr}) any evaluate |Python| expression
py3eval({expr}) any evaluate |python3| expression
@@ -2278,7 +2304,7 @@ range({expr} [, {max} [, {stride}]])
readdir({dir} [, {expr}]) List file names in {dir} selected by {expr}
readfile({fname} [, {binary} [, {max}]])
List get list of lines from file {fname}
-reg_executing() Number get the executing register name
+reg_executing() String get the executing 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
@@ -2333,7 +2359,7 @@ setfperm({fname}, {mode} Number set {fname} file permissions to {mode}
setline({lnum}, {line}) Number set line {lnum} to {line}
setloclist({nr}, {list}[, {action}[, {what}]])
Number modify location list using {list}
-setmatches({list}) Number restore a list of matches
+setmatches({list} [, {win}]) Number restore a list of matches
setpos({expr}, {list}) Number set the {expr} position to {list}
setqflist({list}[, {action}[, {what}]]
Number modify quickfix list using {list}
@@ -2377,6 +2403,8 @@ sqrt({expr}) Float square root of {expr}
stdioopen({dict}) Number open stdio in a headless instance.
stdpath({what}) String/List returns the standard path(s) for {what}
str2float({expr}) Float convert String to Float
+str2list({expr} [, {utf8}]) List convert each character of {expr} to
+ ASCII/UTF8 value
str2nr({expr} [, {base}]) Number convert String to Number
strchars({expr} [, {skipcc}]) Number character length of the String {expr}
strcharpart({str}, {start} [, {len}])
@@ -2508,6 +2536,9 @@ and({expr}, {expr}) *and()*
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()))
+
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.
@@ -2576,16 +2607,18 @@ argv([{nr} [, {winid}])
the whole |arglist| is returned.
The {winid} argument specifies the window ID, see |argc()|.
+ For the Vim command line arguments see |v:argv|.
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()|.
+ Also see |assert_fails()| and |assert-return|.
*assert_equal()*
assert_equal({expected}, {actual}, [, {msg}])
When {expected} and {actual} are not equal an error message is
- added to |v:errors|.
+ 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
@@ -2597,9 +2630,17 @@ assert_equal({expected}, {actual}, [, {msg}])
< Will result in a string to be added to |v:errors|:
test.vim line 12: Expected 'foo' but got 'bar' ~
+ *assert_equalfile()*
+assert_equalfile({fname-one}, {fname-two} [, {msg}])
+ 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|.
+ 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: >
@@ -2612,7 +2653,7 @@ assert_exception({error} [, {msg}]) *assert_exception()*
assert_fails({cmd} [, {error} [, {msg}]]) *assert_fails()*
Run {cmd} and add an error message to |v:errors| if it does
- NOT produce an error.
+ NOT produce an error. Also see |assert-return|.
When {error} is given it must match in |v:errmsg|.
Note that beeping is not considered an error, and some failing
commands only beep. Use |assert_beeps()| for those.
@@ -2620,6 +2661,7 @@ assert_fails({cmd} [, {error} [, {msg}]]) *assert_fails()*
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 or |v:false|. When "{actual}"
is not a number or |v:false| the assert fails.
When {msg} is omitted an error in the form
@@ -2636,7 +2678,7 @@ assert_inrange({lower}, {upper}, {actual} [, {msg}]) *assert_inrange()*
*assert_match()*
assert_match({pattern}, {actual} [, {msg}])
When {pattern} does not match {actual} an error message is
- added to |v:errors|.
+ added to |v:errors|. Also see |assert-return|.
{pattern} is used as with |=~|: The matching is always done
like 'magic' was set and 'cpoptions' is empty, no matter what
@@ -2657,18 +2699,22 @@ assert_match({pattern}, {actual} [, {msg}])
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|.
*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|.
assert_report({msg}) *assert_report()*
Report a test failure directly, using {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 omitted an error in the form "Expected True but
@@ -2967,9 +3013,11 @@ cindent({lnum}) *cindent()*
When {lnum} is invalid -1 is returned.
See |C-indenting|.
-clearmatches() *clearmatches()*
- Clears all matches previously defined for the current window
- by |matchadd()| and the |:match| commands.
+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.
*col()*
col({expr}) The result is a Number, which is the byte index of the column
@@ -3095,6 +3143,10 @@ complete_info([{what}])
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.
+
Examples: >
" Get all items
call complete_info()
@@ -3525,7 +3577,7 @@ exists({expr}) The result is a Number, which is |TRUE| if {expr} is
string)
*funcname built-in function (see |functions|)
or user defined function (see
- |user-functions|). Also works for a
+ |user-function|). Also works for a
variable that is a Funcref.
varname internal variable (see
|internal-variables|). Also works
@@ -3604,6 +3656,11 @@ exp({expr}) *exp()*
:echo exp(-1)
< 0.367879
+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-debugger|.
+ {Sends a SIGINT to a process {pid} other than MS-Windows}
expand({expr} [, {nosuf} [, {list}]]) *expand()*
Expand wildcards and the following special keywords in {expr}.
@@ -3687,6 +3744,14 @@ expand({expr} [, {nosuf} [, {list}]]) *expand()*
See |glob()| for finding existing files. See |system()| for
getting the raw output of an external command.
+expandcmd({expr}) *expandcmd()*
+ Expand special items in {expr} like what is done for an Ex
+ command such as `:edit`. This expands special keywords, like
+ with |expand()|, and environment variables, anywhere in
+ {expr}. Returns the expanded string.
+ Example: >
+ :echo expandcmd('make %<.o')
+<
extend({expr1}, {expr2} [, {expr3}]) *extend()*
{expr1} and {expr2} must be both |Lists| or both
|Dictionaries|.
@@ -3741,6 +3806,8 @@ feedkeys({string} [, {mode}]) *feedkeys()*
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,
@@ -3852,6 +3919,25 @@ findfile({name} [, {path} [, {count}]]) *findfile()*
< Searches from the directory of the current file upwards until
it finds the file "tags.vim".
+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
+ not want that.
+ *E964*
+ {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: >
+ :echo flatten([1, [2, [3, 4]], 5])
+< [1, 2, 3, 4, 5] >
+ :echo flatten([1, [2, [3, 4]], 5], 1)
+< [1, 2, [3, 4], 5]
+
float2nr({expr}) *float2nr()*
Convert {expr} to a Number by omitting the part after the
decimal point.
@@ -4107,8 +4193,13 @@ getbufinfo([{dict}])
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 current line number in buffer.
+ 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.
@@ -4479,8 +4570,7 @@ getftype({fname}) *getftype()*
systems that support it. On some systems only "dir" and
"file" are returned.
- *getjumplist()*
-getjumplist([{winnr} [, {tabnr}]])
+getjumplist([{winnr} [, {tabnr}]]) *getjumplist()*
Returns the |jumplist| for the specified window.
Without arguments use the current window.
@@ -4505,7 +4595,7 @@ getline({lnum} [, {end}])
from the current buffer. Example: >
getline(1)
< When {lnum} is a String that doesn't start with a
- digit, line() is called to translate the String into a Number.
+ digit, |line()| is called to translate the String into a Number.
To get the line under the cursor: >
getline(".")
< When {lnum} is smaller than 1 or bigger than the number of
@@ -4536,8 +4626,12 @@ getloclist({nr},[, {what}]) *getloclist()*
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}.
+ If {what} contains 'filewinid', then returns the id of the
+ window used to display files from the location list. This
+ field is applicable only when called from a location list
+ window.
-getmatches() *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()|,
@@ -4699,7 +4793,7 @@ getreg([{regname} [, 1 [, {list}]]]) *getreg()*
getregtype([{regname}]) *getregtype()*
The result is a String, which is type of register {regname}.
The value will be one of:
- "v" for |characterwise| text
+ "v" for |charwise| text
"V" for |linewise| text
"<CTRL-V>{width}" for |blockwise-visual| text
"" for an empty or unknown register
@@ -4941,9 +5035,11 @@ has({feature}) Returns 1 if {feature} is supported, 0 otherwise. The
< *feature-list*
List of supported pseudo-feature names:
acl |ACL| support
+ bsd BSD system (not macOS, use "mac" for that).
iconv Can use |iconv()| for conversion.
+shellslash Can use backslashes in filenames (Windows)
clipboard |clipboard| provider is available.
+ mac MacOS system.
nvim This is Nvim.
python2 Legacy Vim |python2| interface. |has-python|
python3 Legacy Vim |python3| interface. |has-python|
@@ -4953,6 +5049,7 @@ has({feature}) Returns 1 if {feature} is supported, 0 otherwise. The
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*
@@ -5413,32 +5510,46 @@ jobstart({cmd}[, {opts}]) *jobstart()*
*jobstart-options*
{opts} is a dictionary with these keys:
- |on_stdout|: stdout event handler (function name or |Funcref|)
- stdout_buffered : read stdout in |channel-buffered| mode.
- |on_stderr|: stderr event handler (function name or |Funcref|)
- stderr_buffered : read stderr in |channel-buffered| mode.
- |on_exit| : exit event handler (function name or |Funcref|)
- cwd : Working directory of the job; defaults to
- |current-directory|.
- rpc : If set, |msgpack-rpc| will be used to communicate
- with the job over stdin and stdout. "on_stdout" is
- then ignored, but "on_stderr" can still be used.
- pty : If set, the job will be connected to a new pseudo
- terminal and the job streams are connected to the
- master file descriptor. "on_stderr" is ignored,
- "on_stdout" receives all output.
-
- width : (pty only) Width of the terminal screen
- height : (pty only) Height of the terminal screen
- TERM : (pty only) $TERM environment variable
- detach : (non-pty only) Detach the job process: it will
- not be killed when Nvim exits. If the process
- exits before Nvim, "on_exit" will be invoked.
+ 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 replacing if |clear_env|)
+ the current environment.
+ 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) 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.)
+ pty: (boolean) Connect the job to a new pseudo
+ terminal, and its streams to the master file
+ descriptor. Then `on_stderr` is ignored,
+ `on_stdout` receives all output.
+ 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|
+ TERM: (string) Sets the `pty` $TERM environment variable.
+ 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:
- - The channel ID on success
+ - |channel-id| on success
- 0 on invalid arguments
- -1 if {cmd}[0] is not executable.
See also |job-control|, |channel|, |msgpack-rpc|.
@@ -5450,6 +5561,9 @@ jobstop({id}) *jobstop()*
(if any) will be invoked.
See |job-control|.
+ Returns 1 for valid job id, 0 for invalid id, including jobs have
+ exited or stopped.
+
jobwait({jobs}[, {timeout}]) *jobwait()*
Waits for jobs and their |on_exit| handlers to complete.
@@ -5622,6 +5736,20 @@ lispindent({lnum}) *lispindent()*
When {lnum} is invalid or Vim was not compiled the
|+lispindent| feature, -1 is returned.
+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)}), '')
+< |str2list()| does the opposite.
+
+ When {utf8} is omitted or zero, the current 'encoding' is used.
+ With {utf8} is 1, always return utf-8 characters.
+ With utf-8 composing characters work as expected: >
+ list2str([97, 769]) returns "aÌ"
+<
localtime() *localtime()*
Return the current time, measured as seconds since 1st Jan
1970. See also |strftime()| and |getftime()|.
@@ -5732,6 +5860,7 @@ maparg({name} [, {mode} [, {abbr} [, {dict}]]]) *maparg()*
"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
@@ -5849,7 +5978,7 @@ matchadd({group}, {pattern}[, {priority}[, {id} [, {dict}]]])
Defines a pattern to be highlighted in the current window (a
"match"). It will be highlighted with {group}. Returns an
identification number (ID), which can be used to delete the
- match using |matchdelete()|.
+ 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.
@@ -5949,11 +6078,13 @@ matcharg({nr}) *matcharg()*
Highlighting matches using the |:match| commands are limited
to three matches. |matchadd()| does not have this limitation.
-matchdelete({id}) *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
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.
matchend({expr}, {pat} [, {start} [, {count}]]) *matchend()*
Same as |match()|, but return the index of first character
@@ -6103,7 +6234,7 @@ mode([expr]) Return a string that indicates the current mode.
n Normal
no Operator-pending
- nov Operator-pending (forced characterwise |o_v|)
+ nov Operator-pending (forced charwise |o_v|)
noV Operator-pending (forced linewise |o_V|)
noCTRL-V Operator-pending (forced blockwise |o_CTRL-V|)
niI Normal using |i_CTRL-O| in |Insert-mode|
@@ -6486,6 +6617,63 @@ 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_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".
+
+ 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: >
+ call prompt_setcallback(bufnr(''), function('s:TextEntered'))
+ func s:TextEntered(text)
+ if a:text == 'exit' || a:text == 'quit'
+ stopinsert
+ close
+ else
+ call append(line('$') - 1, 'Entered: "' . a:text . '"')
+ " Reset 'modified' to allow the buffer to be closed.
+ set nomodified
+ endif
+ endfunc
+
+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".
+
+ 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.
+
+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: >
+ call prompt_setprompt(bufnr(''), 'command: ')
+
+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:
+ 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 visible
+
+ The values are the same as in |v:event| during |CompleteChanged|.
pumvisible() *pumvisible()*
Returns non-zero when the popup menu is visible, zero
@@ -6538,6 +6726,33 @@ range({expr} [, {max} [, {stride}]]) *range()*
range(0) " []
range(2, 0) " error!
<
+ *readdir()*
+readdir({directory} [, {expr}])
+ Return a list with file and directory names in {directory}.
+
+ 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": >
+ 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),
+ \ {_, x -> isdirectory(x) ?
+ \ {x : s:tree(a:dir . '/' . x)} : x})}
+ endfunction
+ echo s:tree(".")
+<
*readfile()*
readfile({fname} [, {binary} [, {max}]])
Read file {fname} and return a |List|, each line of the file
@@ -6569,17 +6784,6 @@ readfile({fname} [, {binary} [, {max}]])
the result is an empty list.
Also see |writefile()|.
- *readdir()*
-readdir({directory} [, {expr}])
- 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.
-
reg_executing() *reg_executing()*
Returns the single letter name of the register being executed.
Returns an empty string when no register is being executed.
@@ -6640,7 +6844,7 @@ remote_expr({server}, {string} [, {idvar} [, {timeout}]])
between (not at the end), like with join(expr, "\n").
If {idvar} is present and not empty, it is taken as the name
of a variable and a {serverid} for later use with
- remote_read() is stored there.
+ |remote_read()| is stored there.
If {timeout} is given the read times out after this many
seconds. Otherwise a timeout of 600 seconds is used.
See also |clientserver| |RemoteReply|.
@@ -7253,11 +7457,13 @@ 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}.
-setmatches({list}) *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
example for |getmatches()|.
+ If {win} is specified, use the window with this number or
+ window ID instead of the current window.
*setpos()*
setpos({expr}, {list})
@@ -7413,7 +7619,7 @@ setreg({regname}, {value} [, {options}])
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" |characterwise| mode
+ "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
@@ -7482,11 +7688,21 @@ settagstack({nr}, {dict} [, {action}]) *settagstack()*
{nr} can be the window number or the |window-ID|.
For a list of supported items in {dict}, refer to
- |gettagstack()|
+ |gettagstack()|. "curidx" takes effect before changing the tag
+ stack.
*E962*
- 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 onto the tag stack.
+ 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.
@@ -7660,7 +7876,7 @@ sign_getplaced([{expr} [, {dict}]]) *sign_getplaced()*
priority sign priority
The returned signs in a buffer are ordered by their line
- number.
+ number and priority.
Returns an empty list on failure or if there are no placed
signs.
@@ -8065,6 +8281,18 @@ str2float({expr}) *str2float()*
|substitute()|: >
let f = str2float(substitute(text, ',', '', 'g'))
+str2list({expr} [, {utf8}]) *str2list()*
+ Return a list containing the number values which represent
+ each character in String {expr}. Examples: >
+ str2list(" ") returns [32]
+ str2list("ABC") returns [65, 66, 67]
+< |list2str()| does the opposite.
+
+ When {utf8} is omitted or zero, the current 'encoding' is used.
+ With {utf8} set to 1, always treat the String as utf-8
+ characters. With utf-8 composing characters are handled
+ properly: >
+ str2list("aÌ") returns [97, 769]
str2nr({expr} [, {base}]) *str2nr()*
Convert string {expr} to a number.
@@ -8535,8 +8763,12 @@ tabpagebuflist([{arg}]) *tabpagebuflist()*
tabpagenr([{arg}]) *tabpagenr()*
The result is a Number, which is the number of the current
tab page. The first tab page has number 1.
- When the optional argument is "$", the number of the last tab
- page is returned (the tab page count).
+ 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.
@@ -9155,7 +9387,7 @@ wordcount() *wordcount()*
(only in Visual mode)
visual_chars Number of chars visually selected
(only in Visual mode)
- visual_words Number of chars visually selected
+ visual_words Number of words visually selected
(only in Visual mode)
@@ -9222,7 +9454,7 @@ Don't forget that "^" will only match at the first character of the String and
"\n".
==============================================================================
-5. Defining functions *user-functions*
+5. Defining functions *user-function*
New functions can be defined. These can be called just like builtin
functions. The function executes a sequence of Ex commands. Normal mode
@@ -9680,7 +9912,7 @@ This does NOT work: >
register, "@/" for the search pattern.
If the result of {expr1} ends in a <CR> or <NL>, the
register will be linewise, otherwise it will be set to
- characterwise.
+ charwise.
This can be used to clear the last search pattern: >
:let @/ = ""
< This is different from searching for an empty string,
@@ -9762,6 +9994,54 @@ This does NOT work: >
Like above, but append/add/subtract the value for each
|List| item.
+ *:let=<<* *:let-heredoc*
+ *E990* *E991* *E172* *E221*
+:let {var-name} =<< [trim] {marker}
+text...
+text...
+{marker}
+ Set internal variable {var-name} to a List containing
+ the lines of text bounded by the string {marker}.
+ {marker} cannot start with a lower case character.
+ The last line should end only with the {marker} string
+ without any other character. Watch out for white
+ space after {marker}!
+
+ Without "trim" any white space characters in the lines
+ of text are preserved. If "trim" is specified before
+ {marker}, then indentation is stripped so you can do: >
+ let text =<< trim END
+ if ok
+ echo 'done'
+ endif
+ END
+< Results in: ["if ok", " echo 'done'", "endif"]
+ The marker must line up with "let" and the indentation
+ of the first line is removed from all the text lines.
+ Specifically: all the leading indentation exactly
+ matching the leading indentation of the first
+ non-empty text line is stripped from the input lines.
+ All leading indentation exactly matching the leading
+ indentation before `let` is stripped from the line
+ containing {marker}. Note that the difference between
+ space and tab matters here.
+
+ If {var-name} didn't exist yet, it is created.
+ Cannot be followed by another command, but can be
+ followed by a comment.
+
+ Examples: >
+ let var1 =<< END
+ Sample text 1
+ Sample text 2
+ Sample text 3
+ END
+
+ let data =<< trim DATA
+ 1 2 3 4
+ 5 6 7 8
+ DATA
+<
*E121*
:let {var-name} .. List the value of variable {var-name}. Multiple
variable names may be given. Special names recognized
@@ -10157,8 +10437,8 @@ This does NOT work: >
The parsing works slightly different from |:echo|,
more like |:execute|. All the expressions are first
evaluated and concatenated before echoing anything.
- The expressions must evaluate to a Number or String, a
- Dictionary or List causes an error.
+ If expressions does not evaluate to a Number or
+ String, string() is used to turn it into a string.
Uses the highlighting set by the |:echohl| command.
Example: >
:echomsg "It's a Zizzer Zazzer Zuzz, as you can plainly see."
@@ -10169,7 +10449,7 @@ This does NOT work: >
message in the |message-history|. When used in a
script or function the line number will be added.
Spaces are placed between the arguments as with the
- :echo command. When used inside a try conditional,
+ |:echomsg| command. When used inside a try conditional,
the message is raised as an error exception instead
(see |try-echoerr|).
Example: >
diff --git a/runtime/doc/filetype.txt b/runtime/doc/filetype.txt
index c579c390c6..1fce37c594 100644
--- a/runtime/doc/filetype.txt
+++ b/runtime/doc/filetype.txt
@@ -275,7 +275,7 @@ Note that the last one is the value of $VIMRUNTIME which has been expanded.
Note that when using a plugin manager or |packages| many directories will be
added to 'runtimepath'. These plugins each require their own directory, don't
-put them directly in ~/.vim/plugin.
+put them directly in ~/.config/nvim/plugin.
What if it looks like your plugin is not being loaded? You can find out what
happens when Vim starts up by using the |-V| argument: >
@@ -549,7 +549,9 @@ Variables:
*b:man_default_sects* Comma-separated, ordered list of preferred sections.
For example in C one usually wants section 3 or 2: >
:let b:man_default_sections = '3,2'
-*g:man_hardwrap* Hard-wrap to $MANWIDTH. May improve layout.
+*g:man_hardwrap* Hard-wrap to $MANWIDTH or window width if $MANWIDTH is
+ empty. Enabled by default. Set |FALSE| to enable soft
+ wrapping.
To use Nvim as a manpager: >
export MANPAGER='nvim +Man!'
@@ -558,10 +560,13 @@ 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
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 `:Man`. You can set in your environment: >
+soft wrapped as with `g:man_hardwrap=0`. You can set in your environment: >
export MANWIDTH=999
-So `groff`'s pre-formatting output will be the same as with `:Man` i.e soft-wrapped.
+So `groff`'s pre-formatting output will be the same as with `g:man_hardwrap=0` i.e soft-wrapped.
+
+To disable bold highlighting: >
+ :highlight link manBold Normal
PDF *ft-pdf-plugin*
diff --git a/runtime/doc/fold.txt b/runtime/doc/fold.txt
index f2f6c70b0c..8e2cb2f728 100644
--- a/runtime/doc/fold.txt
+++ b/runtime/doc/fold.txt
@@ -527,8 +527,7 @@ FOLDCOLUMN *fold-foldcolumn*
'foldcolumn' is a number, which sets the width for a column on the side of the
window to indicate folds. When it is zero, there is no foldcolumn. A normal
-value is 4 or 5. The minimal useful value is 2, although 1 still provides
-some information. The maximum is 12.
+value is auto:9. The maximum is 9.
An open fold is indicated with a column that has a '-' at the top and '|'
characters below it. This column stops where the open fold stops. When folds
diff --git a/runtime/doc/help.txt b/runtime/doc/help.txt
index 284cd26583..a384b5f876 100644
--- a/runtime/doc/help.txt
+++ b/runtime/doc/help.txt
@@ -129,6 +129,7 @@ Advanced editing ~
|autocmd.txt| automatically executing commands on an event
|eval.txt| expression evaluation, conditional commands
|fold.txt| hide (fold) ranges of lines
+|lua.txt| Lua API
Special issues ~
|print.txt| printing
@@ -136,6 +137,7 @@ Special issues ~
Programming language support ~
|indent.txt| automatic indenting for C and other languages
+|lsp.txt| Language Server Protocol (LSP)
|syntax.txt| syntax highlighting
|filetype.txt| settings done specifically for a type of file
|quickfix.txt| commands for a quick edit-compile-fix cycle
@@ -157,7 +159,6 @@ GUI ~
Interfaces ~
|if_cscop.txt| using Cscope with Vim
-|if_lua.txt| Lua interface
|if_pyth.txt| Python interface
|if_ruby.txt| Ruby interface
|sign.txt| debugging signs
@@ -169,7 +170,7 @@ Versions ~
Standard plugins ~
|pi_gzip.txt| Reading and writing compressed files
|pi_health.txt| Healthcheck framework
-|pi_matchit.txt| Extended "%" matching
+|pi_matchit.txt| Extended |%| matching
|pi_msgpack.txt| msgpack utilities
|pi_netrw.txt| Reading and writing files over a network
|pi_paren.txt| Highlight matching parens
diff --git a/runtime/doc/if_lua.txt b/runtime/doc/if_lua.txt
index aa2d0a03c6..34bcf0f039 100644
--- a/runtime/doc/if_lua.txt
+++ b/runtime/doc/if_lua.txt
@@ -1,668 +1,8 @@
-*if_lua.txt* Nvim
- NVIM REFERENCE MANUAL
+ NVIM REFERENCE MANUAL
-
-Lua engine *lua* *Lua*
-
- Type |gO| to see the table of contents.
-
-==============================================================================
-Introduction *lua-intro*
-
-The Lua 5.1 language is builtin and always available. Try this command to get
-an idea of what lurks beneath: >
-
- :lua print(vim.inspect(package.loaded))
-
-Nvim includes a "standard library" |lua-stdlib| for Lua. It complements the
-"editor stdlib" (|functions| and Ex commands) and the |API|, all of which can
-be used from Lua code.
-
-Module conflicts are resolved by "last wins". For example if both of these
-are on 'runtimepath':
- runtime/lua/foo.lua
- ~/.config/nvim/lua/foo.lua
-then `require('foo')` loads "~/.config/nvim/lua/foo.lua", and
-"runtime/lua/foo.lua" is not used. See |lua-require| to understand how Nvim
-finds and loads Lua modules. The conventions are similar to VimL plugins,
-with some extra features. See |lua-require-example| for a walkthrough.
-
-==============================================================================
-Importing Lua modules *lua-require*
-
-Nvim automatically adjusts `package.path` and `package.cpath` according to
-effective 'runtimepath' value. Adjustment happens whenever 'runtimepath' is
-changed. `package.path` is adjusted by simply appending `/lua/?.lua` and
-`/lua/?/init.lua` to each directory from 'runtimepath' (`/` is actually the
-first character of `package.config`).
-
-Similarly to `package.path`, modified directories from 'runtimepath' are also
-added to `package.cpath`. In this case, instead of appending `/lua/?.lua` and
-`/lua/?/init.lua` to each runtimepath, all unique `?`-containing suffixes of
-the existing `package.cpath` are used. Example:
-
-1. Given that
- - 'runtimepath' contains `/foo/bar,/xxx;yyy/baz,/abc`;
- - initial (defined at compile-time or derived from
- `$LUA_CPATH`/`$LUA_INIT`) `package.cpath` contains
- `./?.so;/def/ghi/a?d/j/g.elf;/def/?.so`.
-2. It finds `?`-containing suffixes `/?.so`, `/a?d/j/g.elf` and `/?.so`, in
- order: parts of the path starting from the first path component containing
- question mark and preceding path separator.
-3. The suffix of `/def/?.so`, namely `/?.so` is not unique, as it’s the same
- as the suffix of the first path from `package.path` (i.e. `./?.so`). Which
- leaves `/?.so` and `/a?d/j/g.elf`, in this order.
-4. 'runtimepath' has three paths: `/foo/bar`, `/xxx;yyy/baz` and `/abc`. The
- second one contains semicolon which is a paths separator so it is out,
- leaving only `/foo/bar` and `/abc`, in order.
-5. The cartesian product of paths from 4. and suffixes from 3. is taken,
- giving four variants. In each variant `/lua` path segment is inserted
- between path and suffix, leaving
-
- - `/foo/bar/lua/?.so`
- - `/foo/bar/lua/a?d/j/g.elf`
- - `/abc/lua/?.so`
- - `/abc/lua/a?d/j/g.elf`
-
-6. New paths are prepended to the original `package.cpath`.
-
-The result will look like this:
-
- `/foo/bar,/xxx;yyy/baz,/abc` ('runtimepath')
- × `./?.so;/def/ghi/a?d/j/g.elf;/def/?.so` (`package.cpath`)
-
- = `/foo/bar/lua/?.so;/foo/bar/lua/a?d/j/g.elf;/abc/lua/?.so;/abc/lua/a?d/j/g.elf;./?.so;/def/ghi/a?d/j/g.elf;/def/?.so`
-
-Note:
-
-- To track 'runtimepath' updates, paths added at previous update are
- remembered and removed at the next update, while all paths derived from the
- new 'runtimepath' are prepended as described above. This allows removing
- paths when path is removed from 'runtimepath', adding paths when they are
- added and reordering `package.path`/`package.cpath` content if 'runtimepath'
- was reordered.
-
-- Although adjustments happen automatically, Nvim does not track current
- values of `package.path` or `package.cpath`. If you happen to delete some
- paths from there you can set 'runtimepath' to trigger an update: >
- let &runtimepath = &runtimepath
-
-- Skipping paths from 'runtimepath' which contain semicolons applies both to
- `package.path` and `package.cpath`. Given that there are some badly written
- plugins using shell which will not work with paths containing semicolons it
- is better to not have them in 'runtimepath' at all.
-
-------------------------------------------------------------------------------
-LUA PLUGIN EXAMPLE *lua-require-example*
-
-The following example plugin adds a command `:MakeCharBlob` which transforms
-current buffer into a long `unsigned char` array. Lua contains transformation
-function in a module `lua/charblob.lua` which is imported in
-`autoload/charblob.vim` (`require("charblob")`). Example plugin is supposed
-to be put into any directory from 'runtimepath', e.g. `~/.config/nvim` (in
-this case `lua/charblob.lua` means `~/.config/nvim/lua/charblob.lua`).
-
-autoload/charblob.vim: >
-
- function charblob#encode_buffer()
- call setline(1, luaeval(
- \ 'require("charblob").encode(unpack(_A))',
- \ [getline(1, '$'), &textwidth, ' ']))
- endfunction
-
-plugin/charblob.vim: >
-
- if exists('g:charblob_loaded')
- finish
- endif
- let g:charblob_loaded = 1
-
- command MakeCharBlob :call charblob#encode_buffer()
-
-lua/charblob.lua: >
-
- local function charblob_bytes_iter(lines)
- local init_s = {
- next_line_idx = 1,
- next_byte_idx = 1,
- lines = lines,
- }
- local function next(s, _)
- if lines[s.next_line_idx] == nil then
- return nil
- end
- if s.next_byte_idx > #(lines[s.next_line_idx]) then
- s.next_line_idx = s.next_line_idx + 1
- s.next_byte_idx = 1
- return ('\n'):byte()
- end
- local ret = lines[s.next_line_idx]:byte(s.next_byte_idx)
- if ret == ('\n'):byte() then
- ret = 0 -- See :h NL-used-for-NUL.
- end
- s.next_byte_idx = s.next_byte_idx + 1
- return ret
- end
- return next, init_s, nil
- end
-
- local function charblob_encode(lines, textwidth, indent)
- local ret = {
- 'const unsigned char blob[] = {',
- indent,
- }
- for byte in charblob_bytes_iter(lines) do
- -- .- space + number (width 3) + comma
- if #(ret[#ret]) + 5 > textwidth then
- ret[#ret + 1] = indent
- else
- ret[#ret] = ret[#ret] .. ' '
- end
- ret[#ret] = ret[#ret] .. (('%3u,'):format(byte))
- end
- ret[#ret + 1] = '};'
- return ret
- end
-
- return {
- bytes_iter = charblob_bytes_iter,
- encode = charblob_encode,
- }
-
-==============================================================================
-Commands *lua-commands*
-
- *:lua*
-:[range]lua {chunk}
- Execute Lua chunk {chunk}.
-
-Examples:
->
- :lua vim.api.nvim_command('echo "Hello, Nvim!"')
-<
-To see the Lua version: >
- :lua print(_VERSION)
-
-To see the LuaJIT version: >
- :lua print(jit.version)
-<
-
-:[range]lua << [endmarker]
-{script}
-{endmarker}
- Execute Lua script {script}. Useful for including Lua
- code in Vim scripts.
-
-The {endmarker} 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.
-
-Example:
->
- function! CurrentLineInfo()
- lua << EOF
- local linenr = vim.api.nvim_win_get_cursor(0)[1]
- local curline = vim.api.nvim_buf_get_lines(
- 0, linenr, linenr + 1, false)[1]
- print(string.format("Current line [%d] has %d bytes",
- linenr, #curline))
- EOF
- endfunction
-
-Note that the `local` variables will disappear when block finishes. This is
-not the case for globals.
-
- *:luado*
-:[range]luado {body} Execute Lua function "function (line, linenr) {body}
- end" for each line in the [range], with the function
- argument being set to the text of each line in turn,
- without a trailing <EOL>, and the current line number.
- If the value returned by the function is a string it
- becomes the text of the line in the current turn. The
- default for [range] is the whole file: "1,$".
-
-Examples:
->
- :luado return string.format("%s\t%d", line:reverse(), #line)
-
- :lua require"lpeg"
- :lua -- balanced parenthesis grammar:
- :lua bp = lpeg.P{ "(" * ((1 - lpeg.S"()") + lpeg.V(1))^0 * ")" }
- :luado if bp:match(line) then return "-->\t" .. line end
-<
-
- *:luafile*
-:[range]luafile {file}
- Execute Lua script in {file}.
- The whole argument is used as a single file name.
-
-Examples:
->
- :luafile script.lua
- :luafile %
-<
-
-All these commands execute a Lua chunk from either the command line (:lua and
-:luado) or a file (:luafile) with the given line [range]. Similarly to the Lua
-interpreter, each chunk has its own scope and so only global variables are
-shared between command calls. All Lua default libraries are available. In
-addition, Lua "print" function has its output redirected to the Nvim message
-area, with arguments separated by a white space instead of a tab.
-
-Lua uses the "vim" module (see |lua-vim|) to issue commands to Nvim. However,
-procedures that alter buffer content, open new buffers, and change cursor
-position are restricted when the command is executed in the |sandbox|.
-
-
-==============================================================================
-luaeval() *lua-eval* *luaeval()*
-
-The (dual) equivalent of "vim.eval" for passing Lua values to Nvim is
-"luaeval". "luaeval" takes an expression string and an optional argument used
-for _A inside expression and returns the result of the expression. It is
-semantically equivalent in Lua to:
->
- local chunkheader = "local _A = select(1, ...) return "
- function luaeval (expstr, arg)
- local chunk = assert(loadstring(chunkheader .. expstr, "luaeval"))
- return chunk(arg) -- return typval
- end
-
-Lua nils, numbers, strings, tables and booleans are converted to their
-respective VimL types. An error is thrown if conversion of any other Lua types
-is attempted.
-
-The magic global "_A" contains the second argument to luaeval().
-
-Example: >
- :echo luaeval('_A[1] + _A[2]', [40, 2])
- 42
- :echo luaeval('string.match(_A, "[a-z]+")', 'XYXfoo123')
- foo
-
-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:
-
-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.
-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
- considered to be a dictionary, but this time it is converted to
- a |msgpack-special-map|.
- *lua-special-tbl*
-4. Table with `vim.type_idx` key may be a dictionary, a list or floating-point
- value:
- - `{[vim.type_idx]=vim.types.float, [vim.val_idx]=1}` is converted to
- a floating-point 1.0. Note that by default integral lua numbers are
- converted to |Number|s, non-integral are converted to |Float|s. This
- variant allows integral |Float|s.
- - `{[vim.type_idx]=vim.types.dictionary}` is converted to an empty
- dictionary, `{[vim.type_idx]=vim.types.dictionary, [42]=1, a=2}` is
- converted to a dictionary `{'a': 42}`: non-string keys are ignored.
- Without `vim.type_idx` key tables with keys not fitting in 1., 2. or 3.
- are errors.
- - `{[vim.type_idx]=vim.types.list}` is converted to an empty list. As well
- as `{[vim.type_idx]=vim.types.list, [42]=1}`: integral keys that do not
- form a 1-step sequence from 1 to N are ignored, as well as all
- non-integral keys.
-
-Examples: >
-
- :echo luaeval('math.pi')
- :function Rand(x,y) " random uniform between x and y
- : return luaeval('(_A.y-_A.x)*math.random()+_A.x', {'x':a:x,'y':a:y})
- : endfunction
- :echo Rand(1,10)
-
-Note that currently second argument to `luaeval` undergoes VimL to lua
-conversion, so changing containers in lua do not affect values in VimL. Return
-value is also always converted. When converting, |msgpack-special-dict|s are
-treated specially.
-
-==============================================================================
-Lua standard modules *lua-stdlib*
-
-The Nvim Lua "standard library" (stdlib) is the `vim` module, which exposes
-various functions and sub-modules. It is always loaded, thus require("vim")
-is unnecessary.
-
-You can peek at the module properties: >
-
- :lua print(vim.inspect(vim))
-
-Result is something like this: >
-
- {
- _os_proc_children = <function 1>,
- _os_proc_info = <function 2>,
- ...
- api = {
- nvim__id = <function 5>,
- nvim__id_array = <function 6>,
- ...
- },
- deepcopy = <function 106>,
- gsplit = <function 107>,
- ...
- }
-
-To find documentation on e.g. the "deepcopy" function: >
-
- :help vim.deepcopy
-
-Note that underscore-prefixed functions (e.g. "_os_proc_children") are
-internal/private and must not be used by plugins.
-
-------------------------------------------------------------------------------
-VIM.API *lua-api*
-
-`vim.api` exposes the full Nvim |API| as a table of Lua functions.
-
-Example: to use the "nvim_get_current_line()" API function, call
-"vim.api.nvim_get_current_line()": >
-
- print(tostring(vim.api.nvim_get_current_line()))
-
-------------------------------------------------------------------------------
-VIM.LOOP *lua-loop*
-
-`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: >
-
- :lua print(vim.inspect(vim.loop))
-
-Reference: http://docs.libuv.org
-Examples: https://github.com/luvit/luv/tree/master/examples
-
- *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: >
-
- local timer = vim.loop.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: >
-
- local timer = vim.loop.new_timer()
- timer:start(1000, 0, vim.schedule_wrap(function()
- vim.api.nvim_command('echomsg "test"')
- end))
-
-Example: repeating timer
- 1. Save this code to a file.
- 2. Execute it with ":luafile %". >
-
- -- Create a timer handle (implementation detail: uv_timer_t).
- local timer = vim.loop.new_timer()
- local i = 0
- -- Waits 1000ms, then repeats every 750ms until timer:close().
- timer:start(1000, 750, function()
- print('timer invoked! i='..tostring(i))
- if i > 4 then
- timer:close() -- Always close handles to avoid leaks.
- end
- i = i + 1
- end)
- print('sleeping');
-
-
-Example: TCP echo-server *tcp-server*
- 1. Save this code to a file.
- 2. Execute it with ":luafile %".
- 3. Note the port number.
- 4. Connect from any TCP client (e.g. "nc 0.0.0.0 36795"): >
-
- local function create_server(host, port, on_connection)
- local server = vim.loop.new_tcp()
- server:bind(host, port)
- server:listen(128, function(err)
- assert(not err, err) -- Check for errors.
- local sock = vim.loop.new_tcp()
- server:accept(sock) -- Accept client connection.
- on_connection(sock) -- Start reading messages.
- end)
- return server
- end
- local server = create_server('0.0.0.0', 0, function(sock)
- sock:read_start(function(err, chunk)
- assert(not err, err) -- Check for errors.
- if chunk then
- sock:write(chunk) -- Echo received messages to the channel.
- else -- EOF (stream closed).
- sock:close() -- Always close handles to avoid leaks.
- end
- end)
- end)
- print('TCP echo-server listening on port: '..server:getsockname().port)
-
-------------------------------------------------------------------------------
-VIM *lua-util*
-
-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.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.
-
-vim.str_utfindex({str}[, {index}]) *vim.str_utfindex()*
- Convert byte index to UTF-32 and UTF-16 indicies. If {index} is not
- supplied, the length of the string is used. All indicies are zero-based.
- Returns two values: the UTF-32 and UTF-16 indicies respectively.
-
- 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.
-
-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.
-
-vim.schedule({callback}) *vim.schedule()*
- Schedules {callback} to be invoked soon by the main event-loop. Useful
- to avoid |textlock| or other temporary restrictions.
-
-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: >
- {
- [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.
+Moved to |lua.txt|
==============================================================================
-Lua module: vim *lua-vim*
-
-inspect({object}, {options}) *vim.inspect()*
- Return a human-readable representation of the given object.
-
- See also: ~
- https://github.com/kikito/inspect.lua
- https://github.com/mpeterv/vinspect
-
-paste({lines}, {phase}) *vim.paste()*
- Paste handler, invoked by |nvim_paste()| when a conforming UI
- (such as the |TUI|) pastes text into the editor.
-
- Parameters: ~
- {lines} |readfile()|-style list of lines to paste.
- |channel-lines|
- {phase} -1: "non-streaming" paste: the call contains all
- lines. If paste is "streamed", `phase` indicates the stream state:
- • 1: starts the paste (exactly once)
- • 2: continues the paste (zero or more times)
- • 3: ends the paste (exactly once)
-
- Return: ~
- false if client should cancel the paste.
-
- See also: ~
- |paste|
-
-schedule_wrap({cb}) *vim.schedule_wrap()*
- Defers callback `cb` until the Nvim API is safe to call.
-
- See also: ~
- |lua-loop-callbacks|
- |vim.schedule()|
- |vim.in_fast_event()|
-
-
-
-
-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.
-
- Parameters: ~
- {orig} Table to copy
-
- Return: ~
- New table of copied keys and (nested) values.
-
-gsplit({s}, {sep}, {plain}) *vim.gsplit()*
- Splits a string at each instance of a separator.
-
- Parameters: ~
- {s} String to split
- {sep} Separator string or pattern
- {plain} If `true` use `sep` literally (passed to
- String.find)
-
- Return: ~
- Iterator over the split components
-
- See also: ~
- |vim.split()|
- https://www.lua.org/pil/20.2.html
- http://lua-users.org/wiki/StringLibraryTutorial
-
-split({s}, {sep}, {plain}) *vim.split()*
- Splits a string at each instance of a separator.
-
- Examples: >
- split(":aa::b:", ":") --> {'','aa','','bb',''}
- split("axaby", "ab?") --> {'','x','y'}
- split(x*yz*o, "*", true) --> {'x','yz','o'}
-<
-
- Parameters: ~
- {s} String to split
- {sep} Separator string or pattern
- {plain} If `true` use `sep` literally (passed to
- String.find)
-
- Return: ~
- List-like table of the split components.
-
- See also: ~
- |vim.gsplit()|
-
-tbl_contains({t}, {value}) *vim.tbl_contains()*
- Checks if a list-like (vector) table contains `value` .
-
- Parameters: ~
- {t} Table to check
- {value} Value to compare
-
- Return: ~
- true if `t` contains `value`
-
-tbl_extend({behavior}, {...}) *vim.tbl_extend()*
- Merges two or more map-like tables.
-
- Parameters: ~
- {behavior} Decides what to do if a key is found in more
- than one map:
- • "error": raise an error
- • "keep": use value from the leftmost map
- • "force": use value from the rightmost map
- {...} Two or more map-like tables.
-
- See also: ~
- |extend()|
-
-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.
-
- Parameters: ~
- {t} List-like table
-
- Return: ~
- Flattened copy of the given list-like table.
-
-trim({s}) *vim.trim()*
- Trim whitespace (Lua pattern "%s") from both sides of a
- string.
-
- Parameters: ~
- {s} String to trim
-
- Return: ~
- String with whitespace removed from its beginning and end
-
- See also: ~
- https://www.lua.org/pil/20.2.html
-
-pesc({s}) *vim.pesc()*
- Escapes magic chars in a Lua pattern string.
-
- Parameters: ~
- {s} String to escape
-
- Return: ~
- %-escaped pattern string
-
- See also: ~
- https://github.com/rxi/lume
-
- vim:tw=78:ts=8:ft=help:norl:
+ vim:tw=78:ts=8:noet:ft=help:norl:
diff --git a/runtime/doc/index.txt b/runtime/doc/index.txt
index be9e25113a..bdab10c0e4 100644
--- a/runtime/doc/index.txt
+++ b/runtime/doc/index.txt
@@ -10,7 +10,7 @@ short description. The lists are sorted on ASCII value.
Tip: When looking for certain functionality, use a search command. E.g.,
to look for deleting something, use: "/delete".
-For an overview of options see help.txt |option-list|.
+For an overview of options see |option-list|.
For an overview of built-in functions see |functions|.
For a list of Vim variables see |vim-variable|.
For a complete listing of all help items see |help-tags|.
@@ -222,6 +222,8 @@ tag char note action in Normal mode ~
|CTRL-]| CTRL-] :ta to ident under cursor
|CTRL-^| CTRL-^ edit Nth alternate file (equivalent to
":e #N")
+|CTRL-<Tab>| CTRL-<Tab> same as `g<Tab>` : go to last accessed tab
+ page
CTRL-_ not used
|<Space>| <Space> 1 same as "l"
@@ -404,7 +406,7 @@ tag char note action in Normal mode ~
|t| t{char} 1 cursor till before Nth occurrence of {char}
to the right
|u| u 2 undo changes
-|v| v start characterwise Visual mode
+|v| v start charwise Visual mode
|w| w 1 cursor N words forward
|x| ["x]x 2 delete N characters under and after the
cursor [into register x]
@@ -570,6 +572,8 @@ tag command action in Normal mode ~
following the file name.
|CTRL-W_gt| CTRL-W g t same as `gt`: go to next tab page
|CTRL-W_gT| CTRL-W g T same as `gT`: go to previous tab page
+|CTRL-W_g<Tab>| CTRL-W g <Tab> same as `g<Tab>` : go to last accessed tab
+ page
|CTRL-W_h| CTRL-W h go to Nth left window (stop at first window)
|CTRL-W_i| CTRL-W i split window and jump to declaration of
identifier under the cursor
@@ -767,6 +771,7 @@ tag char note action in Normal mode ~
|gn| gn 1,2 find the next match with the last used
search pattern and Visually select it
|gm| gm 1 go to character at middle of the screenline
+|gM| gM 1 go to character at middle of the text line
|go| go 1 cursor to byte N in the buffer
|gp| ["x]gp 2 put the text [from register x] after the
cursor N times, leave the cursor after it
@@ -787,6 +792,7 @@ tag char note action in Normal mode ~
|g<LeftMouse>| g<LeftMouse> same as <C-LeftMouse>
g<MiddleMouse> same as <C-MiddleMouse>
|g<RightMouse>| g<RightMouse> same as <C-RightMouse>
+|g<Tab>| g<Tab> go to last accessed tab page
|g<Up>| g<Up> 1 same as "gk"
==============================================================================
@@ -865,7 +871,7 @@ These can be used after an operator, but before a {motion} has been entered.
tag char action in Operator-pending mode ~
-----------------------------------------------------------------------
-|o_v| v force operator to work characterwise
+|o_v| v force operator to work charwise
|o_V| V force operator to work linewise
|o_CTRL-V| CTRL-V force operator to work blockwise
@@ -977,7 +983,7 @@ tag command note action in Visual mode ~
|v_r| r 2 replace highlighted area with a character
|v_s| s 2 delete highlighted area and start insert
|v_u| u 2 make highlighted area lowercase
-|v_v| v make Visual mode characterwise or stop
+|v_v| v make Visual mode charwise or stop
Visual mode
|v_x| x 2 delete the highlighted area
|v_y| y yank the highlighted area
@@ -1163,11 +1169,13 @@ tag command action ~
|:cNfile| :cNf[ile] go to last error in previous file
|:cabbrev| :ca[bbrev] like ":abbreviate" but for Command-line mode
|:cabclear| :cabc[lear] clear all abbreviations for Command-line mode
+|:cabove| :cabo[ve] go to error above current line
|:caddbuffer| :cad[dbuffer] add errors from buffer
|:caddexpr| :cadde[xpr] add errors from expr
|:caddfile| :caddf[ile] add error message to current quickfix list
|:call| :cal[l] call a function
|:catch| :cat[ch] part of a :try command
+|:cbelow| :cbe[low] go to error below current line
|:cbottom| :cbo[ttom] scroll to the bottom of the quickfix window
|:cbuffer| :cb[uffer] parse error messages and jump to first error
|:cc| :cc go to specific error
@@ -1324,12 +1332,14 @@ tag command action ~
|:lNext| :lN[ext] go to previous entry in location list
|:lNfile| :lNf[ile] go to last entry in previous file
|:list| :l[ist] print lines
+|:labove| :lab[ove] go to location above current line
|:laddexpr| :lad[dexpr] add locations from expr
|:laddbuffer| :laddb[uffer] add locations from buffer
|:laddfile| :laddf[ile] add locations to current location list
|:last| :la[st] go to the last file in the argument list
|:language| :lan[guage] set the language (locale)
|:later| :lat[er] go to newer change, redo
+|:lbelow| :lbe[low] go to location below current line
|:lbottom| :lbo[ttom] scroll to the bottom of the location window
|:lbuffer| :lb[uffer] parse locations and jump to first location
|:lcd| :lc[d] change directory locally
diff --git a/runtime/doc/insert.txt b/runtime/doc/insert.txt
index fce4d8628e..e53af5074b 100644
--- a/runtime/doc/insert.txt
+++ b/runtime/doc/insert.txt
@@ -1083,7 +1083,8 @@ items:
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
- available in |v:completed_item|
+ available in |v:completed_item|; it can be any type;
+ defaults to an empty string
All of these except "icase", "equal", "dup" and "empty" must be a string. If
an item does not meet these requirements then an error message is given and
diff --git a/runtime/doc/intro.txt b/runtime/doc/intro.txt
index 887ef764bd..3c3753df78 100644
--- a/runtime/doc/intro.txt
+++ b/runtime/doc/intro.txt
@@ -271,7 +271,7 @@ and <> are part of what you type, the context should make this clear.
operator is pending.
- Ex commands can be used to move the cursor. This can be
used to call a function that does some complicated motion.
- The motion is always characterwise exclusive, no matter
+ The motion is always charwise exclusive, no matter
what ":" command is used. This means it's impossible to
include the last character of a line without the line break
(unless 'virtualedit' is set).
@@ -339,6 +339,8 @@ notation meaning equivalent decimal value(s) ~
<EOL> end-of-line (can be <CR>, <LF> or <CR><LF>,
depends on system and 'fileformat') *<EOL>*
+<Ignore> cancel wait-for-character *<Ignore>*
+<NOP> no-op: do nothing (useful in mappings) *<Nop>*
<Up> cursor-up *cursor-up* *cursor_up*
<Down> cursor-down *cursor-down* *cursor_down*
@@ -378,11 +380,11 @@ notation meaning equivalent decimal value(s) ~
<kEqual> keypad = *keypad-equal*
<kEnter> keypad Enter *keypad-enter*
<k0> - <k9> keypad 0 to 9 *keypad-0* *keypad-9*
-<S-...> shift-key *shift* *<S-*
-<C-...> control-key *control* *ctrl* *<C-*
-<M-...> alt-key or meta-key *META* *ALT* *<M-*
-<A-...> same as <M-...> *<A-*
-<D-...> command-key or "super" key *<D-*
+<S-…> shift-key *shift* *<S-*
+<C-…> control-key *control* *ctrl* *<C-*
+<M-…> alt-key or meta-key *META* *ALT* *<M-*
+<A-…> same as <M-…> *<A-*
+<D-…> command-key or "super" key *<D-*
-----------------------------------------------------------------------
Note: The shifted cursor keys, the help key, and the undo key are only
diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt
new file mode 100644
index 0000000000..b934d2dfa0
--- /dev/null
+++ b/runtime/doc/lsp.txt
@@ -0,0 +1,1262 @@
+*lsp.txt* LSP
+
+
+ NVIM REFERENCE MANUAL
+
+
+LSP client/framework *lsp*
+
+Nvim supports the Language Server Protocol (LSP), which means it acts as
+a client to LSP servers and includes a Lua framework `vim.lsp` for building
+enhanced LSP tools.
+ https://microsoft.github.io/language-server-protocol/
+
+LSP facilitates features like go-to-definition, find-references, hover,
+completion, rename, format, refactor, etc., using semantic whole-project
+analysis (unlike |ctags|).
+
+ Type |gO| to see the table of contents.
+
+==============================================================================
+QUICKSTART *lsp-quickstart*
+
+Nvim provides a LSP client, but the servers are provided by third parties.
+Follow these steps to get LSP features:
+
+ 1. Install the nvim-lsp plugin. It provides common configuration for
+ various servers so you can get started quickly.
+ https://github.com/neovim/nvim-lsp
+ 2. Install a language server. Try ":LspInstall <tab>" or use your system
+ package manager to install the relevant language server:
+ https://microsoft.github.io/language-server-protocol/implementors/servers/
+ 3. Add `nvim_lsp.xx.setup{…}` to your vimrc, where "xx" is the name of the
+ relevant config. See the nvim-lsp README for details.
+
+To check LSP clients attached to the current buffer: >
+
+ :lua print(vim.inspect(vim.lsp.buf_get_clients()))
+<
+ *lsp-config*
+Inline diagnostics are enabled automatically, e.g. syntax errors will be
+annotated in the buffer. But you probably want to use other features like
+go-to-definition, hover, etc. Example config: >
+
+ nnoremap <silent> gd <cmd>lua vim.lsp.buf.declaration()<CR>
+ nnoremap <silent> <c-]> <cmd>lua vim.lsp.buf.definition()<CR>
+ nnoremap <silent> K <cmd>lua vim.lsp.buf.hover()<CR>
+ nnoremap <silent> gD <cmd>lua vim.lsp.buf.implementation()<CR>
+ nnoremap <silent> <c-k> <cmd>lua vim.lsp.buf.signature_help()<CR>
+ nnoremap <silent> 1gD <cmd>lua vim.lsp.buf.type_definition()<CR>
+ nnoremap <silent> gr <cmd>lua vim.lsp.buf.references()<CR>
+ nnoremap <silent> g0 <cmd>lua vim.lsp.buf.document_symbol()<CR>
+ nnoremap <silent> gW <cmd>lua vim.lsp.buf.workspace_symbol()<CR>
+
+Nvim provides the |vim.lsp.omnifunc| 'omnifunc' handler which allows
+|i_CTRL-X_CTRL-O| to consume LSP completion. Example config (note the use of
+|v:lua| to call Lua from Vimscript): >
+
+ " Use LSP omni-completion in Python files.
+ autocmd Filetype python setlocal omnifunc=v:lua.vim.lsp.omnifunc
+
+If a function has a `*_sync` variant, it's primarily intended for being run
+automatically on file save. E.g. code formatting: >
+
+ " Auto-format *.rs files prior to saving them
+ autocmd BufWritePre *.rs lua vim.lsp.buf.formatting_sync(nil, 1000)
+
+================================================================================
+FAQ *lsp-faq*
+
+- Q: How to force-reload LSP?
+ A: Stop all clients, then reload the buffer. >
+
+ :lua vim.lsp.stop_client(vim.lsp.get_active_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": >
+
+ :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".
+
+================================================================================
+LSP API *lsp-api*
+
+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: >
+ :lua print(vim.inspect(vim.lsp))
+< 3. See |lsp-extension-example| for a full example.
+
+LSP core API is described at |lsp-core|. Those are the core functions for
+creating and managing clients.
+
+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 callbacks.
+|lsp-callbacks| The `vim.lsp.callbacks` table defines default callbacks used
+when creating a new client. Keys are LSP method names: >
+
+ :lua print(vim.inspect(vim.tbl_keys(vim.lsp.callbacks)))
+
+These LSP requests/notifications are defined by default:
+
+ textDocument/publishDiagnostics
+ window/logMessage
+ window/showMessage
+
+You can check these via `vim.tbl_keys(vim.lsp.callbacks)`.
+
+These will be used preferentially in `vim.lsp.buf_…` methods for handling
+requests. They will also be used when responding to server requests and
+notifications.
+
+Use cases:
+- Users can modify this to customize to their preferences.
+- UI plugins can modify this by assigning to
+ `vim.lsp.callbacks[method]` so as to provide more specialized
+ handling, allowing you to leverage the UI capabilities available. UIs should
+ try to be conscientious of any existing changes the user may have set
+ already by checking for existing values.
+
+Any callbacks passed directly to `request` methods on a server client will
+have the highest precedence, followed by the `callbacks`.
+
+You can override the default handlers,
+- globally: by modifying the `vim.lsp.callbacks` table
+- per-client: by passing the {callbacks} table parameter to
+ |vim.lsp.start_client|
+
+Each handler has this signature: >
+
+ function(err, method, params, client_id)
+
+Callbacks are functions which are called in a variety of situations by the
+client. Their signature is `function(err, method, params, client_id)` They can
+be set by the {callbacks} parameter for |vim.lsp.start_client| or via the
+|vim.lsp.callbacks|.
+
+Handlers are called for:
+- Notifications from the server (`err` is always `nil`).
+- Requests initiated by the server (`err` is always `nil`).
+ The handler can respond by returning two values: `result, err`
+ where `err` must be shaped like an RPC error:
+ `{ code, message, data? }`
+ You can use |vim.lsp.rpc_response_error()| to create this object.
+- Handling requests initiated by the client if the request doesn't explicitly
+ specify a callback (such as in |vim.lsp.buf_request|).
+
+
+VIM.LSP.PROTOCOL *vim.lsp.protocol*
+
+Module `vim.lsp.protocol` defines constants dictated by the LSP specification,
+and helper functions for creating protocol-related objects.
+https://github.com/microsoft/language-server-protocol/raw/gh-pages/_specifications/specification-3-14.md
+
+For example `vim.lsp.protocol.ErrorCodes` allows reverse lookup by number or
+name: >
+
+ vim.lsp.protocol.TextDocumentSyncKind.Full == 1
+ vim.lsp.protocol.TextDocumentSyncKind[1] == "Full"
+
+================================================================================
+LSP HIGHLIGHT *lsp-highlight*
+
+ *hl-LspDiagnosticsError*
+LspDiagnosticsError used for "Error" diagnostic virtual text
+ *hl-LspDiagnosticsErrorSign*
+LspDiagnosticsErrorSign used for "Error" diagnostic signs in sign
+ column
+ *hl-LspDiagnosticsErrorFloating*
+LspDiagnosticsErrorFloating used for "Error" diagnostic messages in the
+ diagnostics float
+ *hl-LspDiagnosticsWarning*
+LspDiagnosticsWarning used for "Warning" diagnostic virtual text
+ *hl-LspDiagnosticsWarningSign*
+LspDiagnosticsWarningSign used for "Warning" diagnostic signs in sign
+ column
+ *hl-LspDiagnosticsWarningFloating*
+LspDiagnosticsWarningFloating used for "Warning" diagnostic messages in the
+ diagnostics float
+ *hl-LspDiagnosticsInformation*
+LspDiagnosticsInformation used for "Information" diagnostic virtual text
+ *hl-LspDiagnosticsInformationSign*
+LspDiagnosticsInformationSign used for "Information" signs in sign column
+ *hl-LspDiagnosticsInformationFloating*
+LspDiagnosticsInformationFloating used for "Information" diagnostic messages in
+ the diagnostics float
+ *hl-LspDiagnosticsHint*
+LspDiagnosticsHint used for "Hint" diagnostic virtual text
+ *hl-LspDiagnosticsHintSign*
+LspDiagnosticsHintSign used for "Hint" diagnostic signs in sign
+ column
+ *hl-LspDiagnosticsHintFloating*
+LspDiagnosticsHintFloating used for "Hint" diagnostic messages in the
+ diagnostics float
+ *hl-LspReferenceText*
+LspReferenceText used for highlighting "text" references
+ *hl-LspReferenceRead*
+LspReferenceRead used for highlighting "read" references
+ *hl-LspReferenceWrite*
+LspReferenceWrite used for highlighting "write" references
+
+
+================================================================================
+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 if you plan to work
+with multiple projects in a single session. Below is a fully working Lua
+example which can do exactly that.
+
+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.
+
+>
+ -- 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 "/"
+ -- Asumes 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 algo 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 alredy 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()]]
+<
+
+
+==============================================================================
+AUTOCOMMANDS *lsp-autocommands*
+
+ *LspDiagnosticsChanged*
+LspDiagnosticsChanged After receiving publishDiagnostics server response
+
+
+==============================================================================
+Lua module: vim.lsp *lsp-core*
+
+buf_attach_client({bufnr}, {client_id}) *vim.lsp.buf_attach_client()*
+ Implements the `textDocument/did…` notifications required to
+ track a buffer for any language server.
+
+ 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
+
+buf_get_clients({bufnr}) *vim.lsp.buf_get_clients()*
+ Gets a map of client_id:client pairs for the given buffer,
+ where each value is a |vim.lsp.client| object.
+
+ Parameters: ~
+ {bufnr} (optional, number): Buffer handle, or 0 for
+ current
+
+buf_get_full_text({bufnr}) *vim.lsp.buf_get_full_text()*
+ TODO: Documentation
+
+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
+
+buf_notify({bufnr}, {method}, {params}) *vim.lsp.buf_notify()*
+ Send a notification to a server
+
+ Parameters: ~
+ {bufnr} [number] (optional): The number of the buffer
+ {method} [string]: Name of the request method
+ {params} [string]: Arguments to send to the server
+
+ Return: ~
+ true if any client returns true; false otherwise
+
+ *vim.lsp.buf_request()*
+buf_request({bufnr}, {method}, {params}, {callback})
+ Sends an async request for all active clients attached to the
+ buffer.
+
+ Parameters: ~
+ {bufnr} (number) Buffer handle, or 0 for current.
+ {method} (string) LSP method name
+ {params} (optional, table) Parameters to send to the
+ server
+ {callback} (optional, functionnil) Handler
+
+ Return: ~
+ 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.
+
+ *vim.lsp.buf_request_sync()*
+buf_request_sync({bufnr}, {method}, {params}, {timeout_ms})
+ Sends a request to a server and waits for the response.
+
+ Calls |vim.lsp.buf_request()| 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 100) ms.
+
+ Parameters: ~
+ {bufnr} (number) Buffer handle, or 0 for current.
+ {method} (string) LSP method name
+ {params} (optional, table) Parameters to send to the
+ server
+ {timeout_ms} (optional, number, default=100) Maximum time
+ in milliseconds to wait for a result.
+
+ Return: ~
+ Map of client_id:request_result. On timeout, cancel or
+ error, returns `(nil, err)` where `err` is a string
+ describing the failure reason.
+
+cancel_request({id}) *vim.lsp.cancel_request()*
+ TODO: Documentation
+
+client() *vim.lsp.client*
+ LSP client object.
+
+ • Methods:
+ • request(method, params, [callback]) Send a request to the
+ server. If callback is not specified, it will use
+ {client.callbacks} to try to find a callback. If one is
+ not found there, then an error will occur. This is a thin
+ wrapper around {client.rpc.request} with some additional
+ checking. Returns a boolean to indicate if the
+ notification was successful. If it is false, then it will
+ always be false (the client has shutdown). If it was
+ successful, then it will return the request id as the
+ second result. You can use this with `notify("$/cancel", {
+ id = request_id })` to cancel the request. This helper is
+ made automatically with |vim.lsp.buf_request()| Returns:
+ status, [client_id]
+ • notify(method, params) This is just {client.rpc.notify}()
+ Returns a boolean to indicate if the notification was
+ successful. If it is false, then it will always be false
+ (the client has shutdown). Returns: status
+ • cancel_request(id) This is just
+ {client.rpc.notify}("$/cancelRequest", { id = id })
+ Returns the same as `notify()` .
+ • stop([force]) Stop a client, optionally with force. By
+ default, it will just ask the server to shutdown without
+ force. If you request to stop a client which has
+ previously been requested to shutdown, it will
+ automatically escalate and force shutdown.
+ • is_stopped() Returns true if the client is fully stopped.
+
+ • Members
+ • id (number): The id allocated to the client.
+ • name (string): If a name is specified on creation, that
+ will be used. Otherwise it is just the client id. This is
+ used for logs and messages.
+ • offset_encoding (string): The encoding used for
+ communicating with the server. You can modify this in the
+ `on_init` method before text is sent to the server.
+ • callbacks (table): The callbacks used by the client as
+ described in |lsp-callbacks|.
+ • 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.
+ • resolved_capabilities (table): Normalized table of
+ capabilities that we have detected based on the initialize
+ response from the server in `server_capabilities` .
+
+client_is_stopped({client_id}) *vim.lsp.client_is_stopped()*
+ TODO: Documentation
+
+ *vim.lsp.define_default_sign()*
+define_default_sign({name}, {properties})
+ TODO: Documentation
+
+err_message({...}) *vim.lsp.err_message()*
+ TODO: Documentation
+
+ *vim.lsp.for_each_buffer_client()*
+for_each_buffer_client({bufnr}, {callback})
+ TODO: Documentation
+
+get_active_clients() *vim.lsp.get_active_clients()*
+ Gets all active clients.
+
+ Return: ~
+ Table of |vim.lsp.client| objects
+
+get_client_by_id({client_id}) *vim.lsp.get_client_by_id()*
+ Gets an active client by id, or nil if the id is invalid or
+ the client is not yet initialized.
+
+ Parameters: ~
+ {client_id} client id number
+
+ Return: ~
+ |vim.lsp.client| object, or nil
+
+get_log_path() *vim.lsp.get_log_path()*
+ TODO: Documentation
+
+initialize() *vim.lsp.initialize()*
+ TODO: Documentation
+
+is_dir({filename}) *vim.lsp.is_dir()*
+ TODO: Documentation
+
+is_stopped() *vim.lsp.is_stopped()*
+ TODO: Documentation
+
+next_client_id() *vim.lsp.next_client_id()*
+ TODO: Documentation
+
+notification({method}, {params}) *vim.lsp.notification()*
+ TODO: Documentation
+
+notify({...}) *vim.lsp.notify()*
+ TODO: Documentation
+
+omnifunc({findstart}, {base}) *vim.lsp.omnifunc()*
+ Implements 'omnifunc' compatible LSP completion.
+
+ Parameters: ~
+ {findstart} 0 or 1, decides behavior
+ {base} If findstart=0, text to match against
+
+ Return: ~
+ (number) 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|
+
+on_error({code}, {err}) *vim.lsp.on_error()*
+ TODO: Documentation
+
+on_exit({code}, {signal}) *vim.lsp.on_exit()*
+ TODO: Documentation
+
+once({fn}) *vim.lsp.once()*
+ TODO: Documentation
+
+optional_validator({fn}) *vim.lsp.optional_validator()*
+ TODO: Documentation
+
+request({method}, {params}, {callback}, {bufnr}) *vim.lsp.request()*
+ TODO: Documentation
+
+resolve_bufnr({bufnr}) *vim.lsp.resolve_bufnr()*
+ TODO: Documentation
+
+resolve_callback({method}) *vim.lsp.resolve_callback()*
+ TODO: Documentation
+
+server_request({method}, {params}) *vim.lsp.server_request()*
+ TODO: Documentation
+
+set_log_level({level}) *vim.lsp.set_log_level()*
+ Sets the global log level for LSP logging.
+
+ Levels by name: "trace", "debug", "info", "warn", "error"
+ Level numbers begin with "trace" at 0
+
+ Use `lsp.log_levels` for reverse lookup.
+
+ Parameters: ~
+ {level} [number|string] the case insensitive level name
+ or number
+
+ See also: ~
+ |vim.lsp.log_levels|
+
+start_client({config}) *vim.lsp.start_client()*
+ Starts and initializes a client with the given configuration.
+
+ Parameters `cmd` and `root_dir` are required.
+
+ Parameters: ~
+ {root_dir} (required, string) Directory where the
+ LSP server will base its rootUri on
+ initialization.
+ {cmd} (required, string or list treated like
+ |jobstart()|) Base command that
+ initiates the LSP client.
+ {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: >
+
+ { "PRODUCTION=true"; "TEST=123"; PORT = 8080; HOST = "0.0.0.0"; }
+<
+ {capabilities} Map overriding the default capabilities
+ defined by
+ |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
+ array.
+ {callbacks} Map of language server method names to `function(err, method, params,
+ client_id)` handler. Invoked for:
+ • Notifications from the server, where
+ `err` will always be `nil` .
+ • Requests initiated by the server. For
+ these you can respond by returning
+ two values: `result, err` where err
+ must be shaped like a RPC error, i.e.
+ `{ code, message, data? }` . Use
+ |vim.lsp.rpc_response_error()| to
+ help with this.
+ • Default callback for client requests
+ not explicitly specifying a callback.
+ {init_options} Values to pass in the initialization
+ request as `initializationOptions` .
+ See `initialize` in the LSP spec.
+ {name} (string, default=client-id) Name in log
+ messages.
+ {offset_encoding} (default="utf-16") One of "utf-8",
+ "utf-16", or "utf-32" which is the
+ encoding that the LSP server expects.
+ Client does not verify this is correct.
+ {on_error} Callback with parameters (code, ...),
+ invoked when the client operation
+ throws an error. `code` is a number
+ describing the error. Other arguments
+ may be passed depending on the error
+ kind. See |vim.lsp.client_errors| for
+ possible errors. Use
+ `vim.lsp.client_errors[code]` to get
+ human-friendly name.
+ {before_init} Callback with parameters
+ (initialize_params, config) invoked
+ before the LSP "initialize" phase,
+ where `params` contains the parameters
+ being sent to the server and `config`
+ is the config that was passed to
+ `start_client()` . You can use this to
+ modify parameters before they are sent.
+ {on_init} Callback (client, initialize_result)
+ invoked after LSP "initialize", where
+ `result` is a table of `capabilities`
+ and anything else the server may send.
+ For example, clangd sends
+ `initialize_result.offsetEncoding` if
+ `capabilities.offsetEncoding` was sent
+ to it. You can only modify the
+ `client.offset_encoding` here before
+ any notifications are sent.
+ {on_exit} Callback (code, signal, client_id)
+ invoked on client exit.
+ • code: exit code of the process
+ • signal: number describing the signal
+ used to terminate (if any)
+ • client_id: client handle
+ {on_attach} Callback (client, bufnr) invoked when
+ client attaches to a buffer.
+ {trace} "off" | "messages" | "verbose" | nil
+ passed directly to the language server
+ in the initialize request.
+ Invalid/empty values will default to
+ "off"
+
+ Return: ~
+ Client id. |vim.lsp.get_client_by_id()| Note: client is
+ only available after it has been initialized, which may
+ happen after a small delay (or never if there is an
+ error). Use `on_init` to do any actions once the client
+ has been initialized.
+
+stop({force}) *vim.lsp.stop()*
+ TODO: Documentation
+
+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:
+>
+
+ vim.lsp.stop_client(lsp.get_active_clients())
+<
+
+ By default asks the server to shutdown, unless stop was
+ requested already for this client, then force-shutdown is
+ attempted.
+
+ Parameters: ~
+ {client_id} client id or |vim.lsp.client| object, or list
+ thereof
+ {force} boolean (optional) shutdown forcefully
+
+ *vim.lsp.text_document_did_open_handler()*
+text_document_did_open_handler({bufnr}, {client})
+ TODO: Documentation
+
+unsupported_method({method}) *vim.lsp.unsupported_method()*
+ TODO: Documentation
+
+validate_client_config({config}) *vim.lsp.validate_client_config()*
+ TODO: Documentation
+
+validate_encoding({encoding}) *vim.lsp.validate_encoding()*
+ TODO: Documentation
+
+
+==============================================================================
+Lua module: vim.lsp.protocol *lsp-protocol*
+
+ifnil({a}, {b}) *vim.lsp.protocol.ifnil()*
+ TODO: Documentation
+
+ *vim.lsp.protocol.make_client_capabilities()*
+make_client_capabilities()
+ Gets a new ClientCapabilities object describing the LSP client
+ capabilities.
+
+ *vim.lsp.protocol.resolve_capabilities()*
+resolve_capabilities({server_capabilities})
+ `*` 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` )
+
+ *vim.lsp.protocol.transform_schema_comments()*
+transform_schema_comments()
+ TODO: Documentation
+
+ *vim.lsp.protocol.transform_schema_to_table()*
+transform_schema_to_table()
+ TODO: Documentation
+
+
+==============================================================================
+Lua module: vim.lsp.buf *lsp-buf*
+
+clear_references() *vim.lsp.buf.clear_references()*
+ TODO: Documentation
+
+code_action({context}) *vim.lsp.buf.code_action()*
+ TODO: Documentation
+
+completion({context}) *vim.lsp.buf.completion()*
+ Retrieves the completion items at the current cursor position.
+ Can only be called in Insert mode.
+
+declaration() *vim.lsp.buf.declaration()*
+ Jumps to the declaration of the symbol under the cursor.
+
+definition() *vim.lsp.buf.definition()*
+ Jumps to the definition of the symbol under the cursor.
+
+document_highlight() *vim.lsp.buf.document_highlight()*
+ Send request to server to resolve document highlights for the
+ current text document position. This request can be associated
+ to key mapping or to events such as `CursorHold` , eg:
+>
+ vim.api.nvim_command [[autocmd CursorHold <buffer> lua vim.lsp.buf.document_highlight()]]
+ vim.api.nvim_command [[autocmd CursorHoldI <buffer> lua vim.lsp.buf.document_highlight()]]
+ vim.api.nvim_command [[autocmd CursorMoved <buffer> lua vim.lsp.buf.clear_references()]]
+<
+
+document_symbol() *vim.lsp.buf.document_symbol()*
+ Lists all symbols in the current buffer in the quickfix
+ window.
+
+execute_command({command}) *vim.lsp.buf.execute_command()*
+ TODO: Documentation
+
+formatting({options}) *vim.lsp.buf.formatting()*
+ Formats the current buffer.
+
+ The optional {options} table can be used to specify
+ FormattingOptions, a list of which is available at https://microsoft.github.io/language-server-protocol/specification#textDocument_formatting . Some unspecified options will be automatically derived from
+ the current Neovim options.
+
+ *vim.lsp.buf.formatting_sync()*
+formatting_sync({options}, {timeout_ms})
+ Perform |vim.lsp.buf.formatting()| synchronously.
+
+ Useful for running on save, to make sure buffer is formatted
+ prior to being saved. {timeout_ms} is passed on to
+ |vim.lsp.buf_request_sync()|.
+
+hover() *vim.lsp.buf.hover()*
+ Displays hover information about the symbol under the cursor
+ in a floating window. Calling the function twice will jump
+ into the floating window.
+
+implementation() *vim.lsp.buf.implementation()*
+ Lists all the implementations for the symbol under the cursor
+ in the quickfix window.
+
+npcall({fn}, {...}) *vim.lsp.buf.npcall()*
+ TODO: Documentation
+
+ok_or_nil({status}, {...}) *vim.lsp.buf.ok_or_nil()*
+ TODO: Documentation
+
+ *vim.lsp.buf.range_formatting()*
+range_formatting({options}, {start_pos}, {end_pos})
+ TODO: Documentation
+
+references({context}) *vim.lsp.buf.references()*
+ Lists all the references to the symbol under the cursor in the
+ quickfix window.
+
+rename({new_name}) *vim.lsp.buf.rename()*
+ Renames all references to the symbol under the cursor. If
+ {new_name} is not provided, the user will be prompted for a
+ new name using |input()|.
+
+request({method}, {params}, {callback}) *vim.lsp.buf.request()*
+ TODO: Documentation
+
+server_ready() *vim.lsp.buf.server_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.
+
+type_definition() *vim.lsp.buf.type_definition()*
+ Jumps to the definition of the type of the symbol under the
+ cursor.
+
+workspace_symbol({query}) *vim.lsp.buf.workspace_symbol()*
+ Lists all symbols in the current workspace in the quickfix
+ window.
+
+ The list is filtered against the optional argument {query}; if
+ the argument is omitted from the call, the user is prompted to
+ enter a string on the command line. An empty string means no
+ filtering is done.
+
+incoming_calls() *vim.lsp.buf.incoming_calls()*
+ Lists all the call sites of 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|.
+
+outgoing_calls() *vim.lsp.buf.outgoing_calls()*
+ 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|.
+
+
+==============================================================================
+Lua module: vim.lsp.callbacks *lsp-callbacks*
+
+err_message({...}) *vim.lsp.callbacks.err_message()*
+ TODO: Documentation
+
+ *vim.lsp.callbacks.location_callback()*
+location_callback({_}, {method}, {result})
+ TODO: Documentation
+
+
+==============================================================================
+Lua module: vim.lsp.log *lsp-log*
+
+get_filename() *vim.lsp.log.get_filename()*
+ TODO: Documentation
+
+path_join({...}) *vim.lsp.log.path_join()*
+ TODO: Documentation
+
+set_level({level}) *vim.lsp.log.set_level()*
+ TODO: Documentation
+
+should_log({level}) *vim.lsp.log.should_log()*
+ TODO: Documentation
+
+
+==============================================================================
+Lua module: vim.lsp.rpc *lsp-rpc*
+
+convert_NIL({v}) *vim.lsp.rpc.convert_NIL()*
+ TODO: Documentation
+
+ *vim.lsp.rpc.create_and_start_client()*
+create_and_start_client({cmd}, {cmd_args}, {handlers},
+ {extra_spawn_params})
+ TODO: Documentation
+
+encode_and_send({payload}) *vim.lsp.rpc.encode_and_send()*
+ TODO: Documentation
+
+env_merge({env}) *vim.lsp.rpc.env_merge()*
+ Merges current process env with the given env and returns the
+ result as a list of "k=v" strings.
+>
+
+ 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", }
+<
+
+ *vim.lsp.rpc.format_message_with_content_length()*
+format_message_with_content_length({encoded_message})
+ TODO: Documentation
+
+format_rpc_error({err}) *vim.lsp.rpc.format_rpc_error()*
+ TODO: Documentation
+
+handle_body({body}) *vim.lsp.rpc.handle_body()*
+ TODO: Documentation
+
+is_dir({filename}) *vim.lsp.rpc.is_dir()*
+ TODO: Documentation
+
+json_decode({data}) *vim.lsp.rpc.json_decode()*
+ TODO: Documentation
+
+json_encode({data}) *vim.lsp.rpc.json_encode()*
+ TODO: Documentation
+
+notification({method}, {params}) *vim.lsp.rpc.notification()*
+ TODO: Documentation
+
+on_error({errkind}, {...}) *vim.lsp.rpc.on_error()*
+ TODO: Documentation
+
+on_exit({code}, {signal}) *vim.lsp.rpc.on_exit()*
+ TODO: Documentation
+
+onexit({code}, {signal}) *vim.lsp.rpc.onexit()*
+ TODO: Documentation
+
+parse_headers({header}) *vim.lsp.rpc.parse_headers()*
+ TODO: Documentation
+
+ *vim.lsp.rpc.pcall_handler()*
+pcall_handler({errkind}, {status}, {head}, {...})
+ TODO: Documentation
+
+request_parser_loop() *vim.lsp.rpc.request_parser_loop()*
+ TODO: Documentation
+
+ *vim.lsp.rpc.rpc_response_error()*
+rpc_response_error({code}, {message}, {data})
+ Creates an RPC response object/table.
+
+ Parameters: ~
+ {code} RPC error code defined in
+ `vim.lsp.protocol.ErrorCodes`
+ {message} (optional) arbitrary message to send to server
+ {data} (optional) arbitrary data to send to server
+
+send_notification({method}, {params}) *vim.lsp.rpc.send_notification()*
+ TODO: Documentation
+
+ *vim.lsp.rpc.send_request()*
+send_request({method}, {params}, {callback})
+ TODO: Documentation
+
+ *vim.lsp.rpc.send_response()*
+send_response({request_id}, {err}, {result})
+ TODO: Documentation
+
+server_request({method}, {params}) *vim.lsp.rpc.server_request()*
+ TODO: Documentation
+
+try_call({errkind}, {fn}, {...}) *vim.lsp.rpc.try_call()*
+ TODO: Documentation
+
+
+==============================================================================
+Lua module: vim.lsp.util *lsp-util*
+
+ *vim.lsp.util.apply_syntax_to_region()*
+apply_syntax_to_region({ft}, {start}, {finish})
+ TODO: Documentation
+
+ *vim.lsp.util.apply_text_document_edit()*
+apply_text_document_edit({text_document_edit})
+ TODO: Documentation
+
+ *vim.lsp.util.apply_text_edits()*
+apply_text_edits({text_edits}, {bufnr})
+ TODO: Documentation
+
+ *vim.lsp.util.apply_workspace_edit()*
+apply_workspace_edit({workspace_edit})
+ TODO: Documentation
+
+buf_clear_diagnostics({bufnr}) *vim.lsp.util.buf_clear_diagnostics()*
+ TODO: Documentation
+
+buf_clear_references({bufnr}) *vim.lsp.util.buf_clear_references()*
+ TODO: Documentation
+
+buf_diagnostics_count({kind}) *vim.lsp.util.buf_diagnostics_count()*
+ Returns the number of diagnostics of given kind for current
+ buffer.
+
+ Useful for showing diagnostic counts in statusline. eg:
+>
+
+ function! LspStatus() abort
+ let sl = ''
+ if luaeval('not vim.tbl_isempty(vim.lsp.buf_get_clients(0))')
+ let sl.='%#MyStatuslineLSP#E:'
+ let sl.='%#MyStatuslineLSPErrors#%{luaeval("vim.lsp.util.buf_diagnostics_count([[Error]])")}'
+ let sl.='%#MyStatuslineLSP# W:'
+ let sl.='%#MyStatuslineLSPWarnings#%{luaeval("vim.lsp.util.buf_diagnostics_count([[Warning]])")}'
+ else
+ let sl.='%#MyStatuslineLSPErrors#off'
+ endif
+ return sl
+ endfunction
+ let &l:statusline = '%#MyStatuslineLSP#LSP '.LspStatus()
+<
+
+ Parameters: ~
+ {kind} Diagnostic severity kind: See
+ |vim.lsp.protocol.DiagnosticSeverity|
+
+ Return: ~
+ Count of diagnostics
+
+ *vim.lsp.util.buf_diagnostics_save_positions()*
+buf_diagnostics_save_positions({bufnr}, {diagnostics})
+ Saves the diagnostics (Diagnostic[]) into diagnostics_by_buf
+
+ Parameters: ~
+ {bufnr} bufnr for which the diagnostics are for.
+ {diagnostics} Diagnostics[] received from the language
+ server.
+
+ *vim.lsp.util.buf_diagnostics_signs()*
+buf_diagnostics_signs({bufnr}, {diagnostics})
+ Place signs for each diagnostic in the sign column.
+
+ Sign characters can be customized with the following commands:
+>
+ sign define LspDiagnosticsErrorSign text=E texthl=LspDiagnosticsError linehl= numhl=
+ sign define LspDiagnosticsWarningSign text=W texthl=LspDiagnosticsWarning linehl= numhl=
+ sign define LspDiagnosticsInformationSign text=I texthl=LspDiagnosticsInformation linehl= numhl=
+ sign define LspDiagnosticsHintSign text=H texthl=LspDiagnosticsHint linehl= numhl=
+<
+
+ *vim.lsp.util.buf_diagnostics_underline()*
+buf_diagnostics_underline({bufnr}, {diagnostics})
+ TODO: Documentation
+
+ *vim.lsp.util.buf_diagnostics_virtual_text()*
+buf_diagnostics_virtual_text({bufnr}, {diagnostics})
+ TODO: Documentation
+
+ *vim.lsp.util.buf_highlight_references()*
+buf_highlight_references({bufnr}, {references})
+ TODO: Documentation
+
+character_offset({buf}, {row}, {col}) *vim.lsp.util.character_offset()*
+ TODO: Documentation
+
+ *vim.lsp.util.close_preview_autocmd()*
+close_preview_autocmd({events}, {winnr})
+ TODO: Documentation
+
+ *vim.lsp.util.convert_input_to_markdown_lines()*
+convert_input_to_markdown_lines({input}, {contents})
+ TODO: Documentation
+
+ *vim.lsp.util.convert_signature_help_to_markdown_lines()*
+convert_signature_help_to_markdown_lines({signature_help})
+ TODO: Documentation
+
+ *vim.lsp.util.diagnostics_group_by_line()*
+diagnostics_group_by_line({diagnostics})
+ TODO: Documentation
+
+ *vim.lsp.util.extract_completion_items()*
+extract_completion_items({result})
+ TODO: Documentation
+
+ *vim.lsp.util.fancy_floating_markdown()*
+fancy_floating_markdown({contents}, {opts})
+ Convert markdown into syntax highlighted regions by stripping
+ the code blocks and converting them into highlighted code.
+ This will by default insert a blank line separator after those
+ code block regions to improve readability. The result is shown
+ in a floating preview TODO: refactor to separate
+ stripping/converting and make use of open_floating_preview
+
+ Parameters: ~
+ {contents} table of lines to show in window
+ {opts} dictionary with optional fields
+
+ Return: ~
+ width,height size of float
+
+find_window_by_var({name}, {value}) *vim.lsp.util.find_window_by_var()*
+ TODO: Documentation
+
+focusable_float({unique_name}, {fn}) *vim.lsp.util.focusable_float()*
+ TODO: Documentation
+
+ *vim.lsp.util.focusable_preview()*
+focusable_preview({unique_name}, {fn})
+ TODO: Documentation
+
+get_completion_word({item}) *vim.lsp.util.get_completion_word()*
+ TODO: Documentation
+
+ *vim.lsp.util.get_current_line_to_cursor()*
+get_current_line_to_cursor()
+ TODO: Documentation
+
+get_effective_tabstop({bufnr}) *vim.lsp.util.get_effective_tabstop()*
+ Get visual width of tabstop.
+
+ Parameters: ~
+ {bufnr} (optional, number): Buffer handle, defaults to
+ current
+
+ Return: ~
+ (number) tabstop visual width
+
+ See also: ~
+ |softtabstop|
+
+ *vim.lsp.util.get_line_byte_from_position()*
+get_line_byte_from_position({bufnr}, {position})
+ TODO: Documentation
+
+get_line_diagnostics() *vim.lsp.util.get_line_diagnostics()*
+ TODO: Documentation
+
+ *vim.lsp.util.get_severity_highlight_name()*
+get_severity_highlight_name({severity})
+ TODO: Documentation
+
+jump_to_location({location}) *vim.lsp.util.jump_to_location()*
+ TODO: Documentation
+
+locations_to_items({locations}) *vim.lsp.util.locations_to_items()*
+ TODO: Documentation
+
+ *vim.lsp.util.make_floating_popup_options()*
+make_floating_popup_options({width}, {height}, {opts})
+ TODO: Documentation
+
+ *vim.lsp.util.make_formatting_params()*
+make_formatting_params({options})
+ TODO: Documentation
+
+make_position_param() *vim.lsp.util.make_position_param()*
+ TODO: Documentation
+
+make_position_params() *vim.lsp.util.make_position_params()*
+ TODO: Documentation
+
+make_range_params() *vim.lsp.util.make_range_params()*
+ TODO: Documentation
+
+make_text_document_params() *vim.lsp.util.make_text_document_params()*
+ TODO: Documentation
+
+npcall({fn}, {...}) *vim.lsp.util.npcall()*
+ TODO: Documentation
+
+ok_or_nil({status}, {...}) *vim.lsp.util.ok_or_nil()*
+ TODO: Documentation
+
+ *vim.lsp.util.open_floating_preview()*
+open_floating_preview({contents}, {filetype}, {opts})
+ Show contents in a floating window
+
+ Parameters: ~
+ {contents} table of lines to show in window
+ {filetype} string of filetype to set for opened buffer
+ {opts} dictionary with optional fields
+
+ Return: ~
+ bufnr,winnr buffer and window number of floating window or
+ nil
+
+parse_snippet({input}) *vim.lsp.util.parse_snippet()*
+ TODO: Documentation
+
+parse_snippet_rec({input}, {inner}) *vim.lsp.util.parse_snippet_rec()*
+ TODO: Documentation
+
+preview_location({location}) *vim.lsp.util.preview_location()*
+ Preview a location in a floating windows
+
+ behavior depends on type of location:
+ • for Location, range is shown (e.g., function definition)
+ • for LocationLink, targetRange is shown (e.g., body of
+ function definition)
+
+ Parameters: ~
+ {location} a single Location or LocationLink
+
+ Return: ~
+ bufnr,winnr buffer and window number of floating window or
+ nil
+
+ *vim.lsp.util.remove_unmatch_completion_items()*
+remove_unmatch_completion_items({items}, {prefix})
+ TODO: Documentation
+
+set_lines({lines}, {A}, {B}, {new_lines}) *vim.lsp.util.set_lines()*
+ TODO: Documentation
+
+set_loclist({items}) *vim.lsp.util.set_loclist()*
+ TODO: Documentation
+
+set_qflist({items}) *vim.lsp.util.set_qflist()*
+ TODO: Documentation
+
+show_line_diagnostics() *vim.lsp.util.show_line_diagnostics()*
+ TODO: Documentation
+
+sort_by_key({fn}) *vim.lsp.util.sort_by_key()*
+ TODO: Documentation
+
+sort_completion_items({items}) *vim.lsp.util.sort_completion_items()*
+ TODO: Documentation
+
+split_lines({value}) *vim.lsp.util.split_lines()*
+ TODO: Documentation
+
+symbols_to_items({symbols}, {bufnr}) *vim.lsp.util.symbols_to_items()*
+ Convert 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})
+ TODO: Documentation
+
+trim_empty_lines({lines}) *vim.lsp.util.trim_empty_lines()*
+ TODO: Documentation
+
+ *vim.lsp.util.try_trim_markdown_code_blocks()*
+try_trim_markdown_code_blocks({lines})
+ TODO: Documentation
+
+ vim:tw=78:ts=8:ft=help:norl:
diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt
new file mode 100644
index 0000000000..60c7a60d25
--- /dev/null
+++ b/runtime/doc/lua.txt
@@ -0,0 +1,1453 @@
+*lua.txt* Nvim
+
+
+ NVIM REFERENCE MANUAL
+
+
+Lua engine *lua* *Lua*
+
+ Type |gO| to see the table of contents.
+
+==============================================================================
+Introduction *lua-intro*
+
+The Lua 5.1 language is builtin and always available. Try this command to get
+an idea of what lurks beneath: >
+
+ :lua print(vim.inspect(package.loaded))
+
+Nvim includes a "standard library" |lua-stdlib| for Lua. It complements the
+"editor stdlib" (|functions| and Ex commands) and the |API|, all of which can
+be used from Lua code.
+
+Module conflicts are resolved by "last wins". For example if both of these
+are on 'runtimepath':
+ runtime/lua/foo.lua
+ ~/.config/nvim/lua/foo.lua
+then `require('foo')` loads "~/.config/nvim/lua/foo.lua", and
+"runtime/lua/foo.lua" is not used. See |lua-require| to understand how Nvim
+finds and loads Lua modules. The conventions are similar to VimL plugins,
+with some extra features. See |lua-require-example| for a walkthrough.
+
+==============================================================================
+Importing Lua modules *lua-require*
+
+ *lua-package-path*
+Nvim automatically adjusts `package.path` and `package.cpath` according to
+effective 'runtimepath' value. Adjustment happens whenever 'runtimepath' is
+changed. `package.path` is adjusted by simply appending `/lua/?.lua` and
+`/lua/?/init.lua` to each directory from 'runtimepath' (`/` is actually the
+first character of `package.config`).
+
+Similarly to `package.path`, modified directories from 'runtimepath' are also
+added to `package.cpath`. In this case, instead of appending `/lua/?.lua` and
+`/lua/?/init.lua` to each runtimepath, all unique `?`-containing suffixes of
+the existing `package.cpath` are used. Example:
+
+1. Given that
+ - 'runtimepath' contains `/foo/bar,/xxx;yyy/baz,/abc`;
+ - initial (defined at compile-time or derived from
+ `$LUA_CPATH`/`$LUA_INIT`) `package.cpath` contains
+ `./?.so;/def/ghi/a?d/j/g.elf;/def/?.so`.
+2. It finds `?`-containing suffixes `/?.so`, `/a?d/j/g.elf` and `/?.so`, in
+ order: parts of the path starting from the first path component containing
+ question mark and preceding path separator.
+3. The suffix of `/def/?.so`, namely `/?.so` is not unique, as it’s the same
+ as the suffix of the first path from `package.path` (i.e. `./?.so`). Which
+ leaves `/?.so` and `/a?d/j/g.elf`, in this order.
+4. 'runtimepath' has three paths: `/foo/bar`, `/xxx;yyy/baz` and `/abc`. The
+ second one contains semicolon which is a paths separator so it is out,
+ leaving only `/foo/bar` and `/abc`, in order.
+5. The cartesian product of paths from 4. and suffixes from 3. is taken,
+ giving four variants. In each variant `/lua` path segment is inserted
+ between path and suffix, leaving
+
+ - `/foo/bar/lua/?.so`
+ - `/foo/bar/lua/a?d/j/g.elf`
+ - `/abc/lua/?.so`
+ - `/abc/lua/a?d/j/g.elf`
+
+6. New paths are prepended to the original `package.cpath`.
+
+The result will look like this:
+
+ `/foo/bar,/xxx;yyy/baz,/abc` ('runtimepath')
+ × `./?.so;/def/ghi/a?d/j/g.elf;/def/?.so` (`package.cpath`)
+
+ = `/foo/bar/lua/?.so;/foo/bar/lua/a?d/j/g.elf;/abc/lua/?.so;/abc/lua/a?d/j/g.elf;./?.so;/def/ghi/a?d/j/g.elf;/def/?.so`
+
+Note:
+
+- To track 'runtimepath' updates, paths added at previous update are
+ remembered and removed at the next update, while all paths derived from the
+ new 'runtimepath' are prepended as described above. This allows removing
+ paths when path is removed from 'runtimepath', adding paths when they are
+ added and reordering `package.path`/`package.cpath` content if 'runtimepath'
+ was reordered.
+
+- Although adjustments happen automatically, Nvim does not track current
+ values of `package.path` or `package.cpath`. If you happen to delete some
+ paths from there you can set 'runtimepath' to trigger an update: >
+ let &runtimepath = &runtimepath
+
+- Skipping paths from 'runtimepath' which contain semicolons applies both to
+ `package.path` and `package.cpath`. Given that there are some badly written
+ plugins using shell which will not work with paths containing semicolons it
+ is better to not have them in 'runtimepath' at all.
+
+==============================================================================
+Lua Syntax Information *lua-syntax-help*
+
+While Lua has a simple syntax, there are a few things to understand,
+particularly when looking at the documentation above.
+
+ *lua-syntax-call-function*
+
+Lua functions can be called in multiple ways. Consider the function: >
+
+ local example_func = function(a, b)
+ print("A is: ", a)
+ print("B is: ", b)
+ end
+
+
+The first way to call a function is: >
+
+ example_func(1, 2)
+ -- ==== Result ====
+ -- A is: 1
+ -- B is: 2
+<
+ This way of calling a function is familiar to most scripting languages.
+ In Lua, it's important to understand that any function arguments that are
+ not supplied are automatically set to `nil`. For example: >
+
+ example_func(1)
+ -- ==== Result ====
+ -- A is: 1
+ -- B is: nil
+<
+
+ Additionally, if any extra parameters are passed, they are discarded
+ completely.
+
+In Lua, it is also possible (when only one argument is passed) to call the
+function without any parentheses. This is most often used to approximate
+"keyword"-style arguments with a single dictionary. For example: >
+
+ local func_with_opts = function(opts)
+ local will_do_foo = opts.foo
+ local filename = opts.filename
+
+ ...
+ end
+
+ func_with_opts { foo = true, filename = "hello.world" }
+<
+
+ In this style, each "parameter" is passed via keyword. It is still valid
+ to call the function in this style: >
+
+ func_with_opts({ foo = true, filename = "hello.world" })
+<
+
+ But often in the documentation, you will see the former rather than the
+ latter style, due to its brevity (this is vim after all!).
+
+
+------------------------------------------------------------------------------
+LUA PLUGIN EXAMPLE *lua-require-example*
+
+The following example plugin adds a command `:MakeCharBlob` which transforms
+current buffer into a long `unsigned char` array. Lua contains transformation
+function in a module `lua/charblob.lua` which is imported in
+`autoload/charblob.vim` (`require("charblob")`). Example plugin is supposed
+to be put into any directory from 'runtimepath', e.g. `~/.config/nvim` (in
+this case `lua/charblob.lua` means `~/.config/nvim/lua/charblob.lua`).
+
+autoload/charblob.vim: >
+
+ function charblob#encode_buffer()
+ call setline(1, luaeval(
+ \ 'require("charblob").encode(unpack(_A))',
+ \ [getline(1, '$'), &textwidth, ' ']))
+ endfunction
+
+plugin/charblob.vim: >
+
+ if exists('g:charblob_loaded')
+ finish
+ endif
+ let g:charblob_loaded = 1
+
+ command MakeCharBlob :call charblob#encode_buffer()
+
+lua/charblob.lua: >
+
+ local function charblob_bytes_iter(lines)
+ local init_s = {
+ next_line_idx = 1,
+ next_byte_idx = 1,
+ lines = lines,
+ }
+ local function next(s, _)
+ if lines[s.next_line_idx] == nil then
+ return nil
+ end
+ if s.next_byte_idx > #(lines[s.next_line_idx]) then
+ s.next_line_idx = s.next_line_idx + 1
+ s.next_byte_idx = 1
+ return ('\n'):byte()
+ end
+ local ret = lines[s.next_line_idx]:byte(s.next_byte_idx)
+ if ret == ('\n'):byte() then
+ ret = 0 -- See :h NL-used-for-NUL.
+ end
+ s.next_byte_idx = s.next_byte_idx + 1
+ return ret
+ end
+ return next, init_s, nil
+ end
+
+ local function charblob_encode(lines, textwidth, indent)
+ local ret = {
+ 'const unsigned char blob[] = {',
+ indent,
+ }
+ for byte in charblob_bytes_iter(lines) do
+ -- .- space + number (width 3) + comma
+ if #(ret[#ret]) + 5 > textwidth then
+ ret[#ret + 1] = indent
+ else
+ ret[#ret] = ret[#ret] .. ' '
+ end
+ ret[#ret] = ret[#ret] .. (('%3u,'):format(byte))
+ end
+ ret[#ret + 1] = '};'
+ return ret
+ end
+
+ return {
+ bytes_iter = charblob_bytes_iter,
+ encode = charblob_encode,
+ }
+
+==============================================================================
+Commands *lua-commands*
+
+These commands execute a Lua chunk from either the command line (:lua, :luado)
+or a file (:luafile) on the given line [range]. As always in Lua, each chunk
+has its own scope (closure), so only global variables are shared between
+command calls. The |lua-stdlib| modules, user modules, and anything else on
+|lua-package-path| are available.
+
+The Lua print() function redirects its output to the Nvim message area, with
+arguments separated by " " (space) instead of "\t" (tab).
+
+ *:lua*
+:[range]lua {chunk}
+ Executes Lua chunk {chunk}.
+
+ Examples: >
+ :lua vim.api.nvim_command('echo "Hello, Nvim!"')
+< To see the Lua version: >
+ :lua print(_VERSION)
+< To see the LuaJIT version: >
+ :lua print(jit.version)
+<
+ *:lua-heredoc*
+:[range]lua << [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|).
+
+ Example:
+ >
+ function! CurrentLineInfo()
+ lua << EOF
+ local linenr = vim.api.nvim_win_get_cursor(0)[1]
+ local curline = vim.api.nvim_buf_get_lines(
+ 0, linenr, linenr + 1, false)[1]
+ print(string.format("Current line [%d] has %d bytes",
+ linenr, #curline))
+ EOF
+ endfunction
+
+< Note that the `local` variables will disappear when
+ the block finishes. But not globals.
+
+ *:luado*
+:[range]luado {body} Executes Lua chunk "function(line, linenr) {body} end"
+ for each buffer line in [range], where `line` is the
+ current line text (without <EOL>), and `linenr` is the
+ current line number. If the function returns a string
+ that becomes the text of the corresponding buffer
+ line. Default [range] is the whole file: "1,$".
+
+ Examples:
+ >
+ :luado return string.format("%s\t%d", line:reverse(), #line)
+
+ :lua require"lpeg"
+ :lua -- balanced parenthesis grammar:
+ :lua bp = lpeg.P{ "(" * ((1 - lpeg.S"()") + lpeg.V(1))^0 * ")" }
+ :luado if bp:match(line) then return "-->\t" .. line end
+<
+
+ *:luafile*
+:[range]luafile {file}
+ Execute Lua script in {file}.
+ The whole argument is used as a single file name.
+
+ Examples:
+ >
+ :luafile script.lua
+ :luafile %
+<
+
+==============================================================================
+luaeval() *lua-eval* *luaeval()*
+
+The (dual) equivalent of "vim.eval" for passing Lua values to Nvim is
+"luaeval". "luaeval" takes an expression string and an optional argument used
+for _A inside expression and returns the result of the expression. It is
+semantically equivalent in Lua to:
+>
+ local chunkheader = "local _A = select(1, ...) return "
+ function luaeval (expstr, arg)
+ local chunk = assert(loadstring(chunkheader .. expstr, "luaeval"))
+ return chunk(arg) -- return typval
+ end
+
+Lua nils, numbers, strings, tables and booleans are converted to their
+respective VimL types. An error is thrown if conversion of any other Lua types
+is attempted.
+
+The magic global "_A" contains the second argument to luaeval().
+
+Example: >
+ :echo luaeval('_A[1] + _A[2]', [40, 2])
+ 42
+ :echo luaeval('string.match(_A, "[a-z]+")', 'XYXfoo123')
+ foo
+
+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:
+
+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.
+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
+ considered to be a dictionary, but this time it is converted to
+ a |msgpack-special-map|.
+ *lua-special-tbl*
+4. Table with `vim.type_idx` key may be a dictionary, a list or floating-point
+ value:
+ - `{[vim.type_idx]=vim.types.float, [vim.val_idx]=1}` is converted to
+ a floating-point 1.0. Note that by default integral Lua numbers are
+ converted to |Number|s, non-integral are converted to |Float|s. This
+ variant allows integral |Float|s.
+ - `{[vim.type_idx]=vim.types.dictionary}` is converted to an empty
+ dictionary, `{[vim.type_idx]=vim.types.dictionary, [42]=1, a=2}` is
+ converted to a dictionary `{'a': 42}`: non-string keys are ignored.
+ Without `vim.type_idx` key tables with keys not fitting in 1., 2. or 3.
+ are errors.
+ - `{[vim.type_idx]=vim.types.list}` is converted to an empty list. As well
+ as `{[vim.type_idx]=vim.types.list, [42]=1}`: integral keys that do not
+ form a 1-step sequence from 1 to N are ignored, as well as all
+ non-integral keys.
+
+Examples: >
+
+ :echo luaeval('math.pi')
+ :function Rand(x,y) " random uniform between x and y
+ : return luaeval('(_A.y-_A.x)*math.random()+_A.x', {'x':a:x,'y':a:y})
+ : endfunction
+ :echo Rand(1,10)
+
+Note: second argument to `luaeval` undergoes VimL to Lua conversion
+("marshalled"), so changes to Lua containers do not affect values in VimL.
+Return value is also always converted. When converting,
+|msgpack-special-dict|s are treated specially.
+
+==============================================================================
+Vimscript v:lua interface *v:lua-call*
+
+From Vimscript the special `v:lua` prefix can be used to call Lua functions
+which are global or accessible from global tables. The expression >
+ v:lua.func(arg1, arg2)
+is equivalent to the Lua chunk >
+ return func(...)
+where the args are converted to Lua values. The expression >
+ v:lua.somemod.func(args)
+is equivalent to the Lua chunk >
+ return somemod.func(...)
+
+You can use `v:lua` in "func" options like 'tagfunc', 'omnifunc', etc.
+For example consider the following Lua omnifunc handler: >
+
+ function mymod.omnifunc(findstart, base)
+ if findstart == 1 then
+ return 0
+ else
+ return {'stuff', 'steam', 'strange things'}
+ end
+ end
+ vim.api.nvim_buf_set_option(0, 'omnifunc', 'v:lua.mymod.omnifunc')
+
+Note: the module ("mymod" in the above example) must be a Lua global.
+
+Note: `v:lua` without a call is not allowed in a Vimscript expression:
+|Funcref|s cannot represent Lua functions. The following are errors: >
+
+ let g:Myvar = v:lua.myfunc " Error
+ call SomeFunc(v:lua.mycallback) " Error
+ let g:foo = v:lua " Error
+ let g:foo = v:['lua'] " Error
+
+
+==============================================================================
+Lua standard modules *lua-stdlib*
+
+The Nvim Lua "standard library" (stdlib) is the `vim` module, which exposes
+various functions and sub-modules. It is always loaded, thus require("vim")
+is unnecessary.
+
+You can peek at the module properties: >
+
+ :lua print(vim.inspect(vim))
+
+Result is something like this: >
+
+ {
+ _os_proc_children = <function 1>,
+ _os_proc_info = <function 2>,
+ ...
+ api = {
+ nvim__id = <function 5>,
+ nvim__id_array = <function 6>,
+ ...
+ },
+ deepcopy = <function 106>,
+ gsplit = <function 107>,
+ ...
+ }
+
+To find documentation on e.g. the "deepcopy" function: >
+
+ :help vim.deepcopy()
+
+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.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: >
+
+ :lua print(vim.inspect(vim.loop))
+
+Reference: http://docs.libuv.org
+Examples: https://github.com/luvit/luv/tree/master/examples
+
+ *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: >
+
+ local timer = vim.loop.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: >
+
+ local timer = vim.loop.new_timer()
+ timer:start(1000, 0, vim.schedule_wrap(function()
+ vim.api.nvim_command('echomsg "test"')
+ end))
+
+(For one-shot timers, see |vim.defer_fn()|, which automatically adds the wrapping.)
+
+Example: repeating timer
+ 1. Save this code to a file.
+ 2. Execute it with ":luafile %". >
+
+ -- Create a timer handle (implementation detail: uv_timer_t).
+ local timer = vim.loop.new_timer()
+ local i = 0
+ -- Waits 1000ms, then repeats every 750ms until timer:close().
+ timer:start(1000, 750, function()
+ print('timer invoked! i='..tostring(i))
+ if i > 4 then
+ timer:close() -- Always close handles to avoid leaks.
+ end
+ i = i + 1
+ end)
+ print('sleeping');
+
+
+Example: File-change detection *watch-file*
+ 1. Save this code to a file.
+ 2. Execute it with ":luafile %".
+ 3. Use ":Watch %" to watch any file.
+ 4. Try editing the file from another text editor.
+ 5. Observe that the file reloads in Nvim (because on_change() calls
+ |:checktime|). >
+
+ local w = vim.loop.new_fs_event()
+ local function on_change(err, fname, status)
+ -- Do work...
+ vim.api.nvim_command('checktime')
+ -- Debounce: stop/start.
+ w:stop()
+ watch_file(fname)
+ end
+ function watch_file(fname)
+ local fullpath = vim.api.nvim_call_function(
+ 'fnamemodify', {fname, ':p'})
+ w:start(fullpath, {}, vim.schedule_wrap(function(...)
+ on_change(...) end))
+ end
+ vim.api.nvim_command(
+ "command! -nargs=1 Watch call luaeval('watch_file(_A)', expand('<args>'))")
+
+
+Example: TCP echo-server *tcp-server*
+ 1. Save this code to a file.
+ 2. Execute it with ":luafile %".
+ 3. Note the port number.
+ 4. Connect from any TCP client (e.g. "nc 0.0.0.0 36795"): >
+
+ local function create_server(host, port, on_connect)
+ local server = vim.loop.new_tcp()
+ server:bind(host, port)
+ server:listen(128, function(err)
+ assert(not err, err) -- Check for errors.
+ local sock = vim.loop.new_tcp()
+ server:accept(sock) -- Accept client connection.
+ on_connect(sock) -- Start reading messages.
+ end)
+ return server
+ end
+ local server = create_server('0.0.0.0', 0, function(sock)
+ sock:read_start(function(err, chunk)
+ assert(not err, err) -- Check for errors.
+ if chunk then
+ sock:write(chunk) -- Echo received messages to the channel.
+ else -- EOF (stream closed).
+ sock:close() -- Always close handles to avoid leaks.
+ end
+ end)
+ end)
+ print('TCP echo-server listening on port: '..server:getsockname().port)
+
+------------------------------------------------------------------------------
+VIM.TREESITTER *lua-treesitter*
+
+Nvim integrates the tree-sitter library for incremental parsing of buffers.
+
+Currently Nvim does not provide the tree-sitter parsers, instead these must
+be built separately, for instance using the tree-sitter utility. The only
+exception is a C parser being included in official builds for testing
+purposes. Parsers are searched for as `parser/{lang}.*` in any 'runtimepath'
+directory. A parser can also be loaded manually using a full path: >
+
+ vim.treesitter.require_language("python", "/path/to/python.so")
+
+<Create a parser for a buffer and a given language (if another plugin uses the
+same buffer/language combination, it will be safely reused). Use >
+
+ parser = vim.treesitter.get_parser(bufnr, lang)
+
+<`bufnr=0` can be used for current buffer. `lang` will default to 'filetype' (this
+doesn't work yet for some filetypes like "cpp") 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.
+
+Parser methods *lua-treesitter-parser*
+
+tsparser:parse() *tsparser:parse()*
+Whenever you need to access the current syntax tree, parse the buffer: >
+
+ tstree = parser:parse()
+
+<This will return an immutable tree that represents 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.
+
+NB: to use the parser directly inside a |nvim_buf_attach| Lua callback, you must
+call `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.
+
+tsparser:set_included_ranges(ranges) *tsparser:set_included_ranges()*
+ Changes the ranges the parser should consider. This is used for
+ language injection. `ranges` should be of the form (all zero-based): >
+ {
+ {start_node, end_node},
+ ...
+ }
+<
+ NOTE: `start_node` and `end_node` are both inclusive.
+
+Tree methods *lua-treesitter-tree*
+
+tstree:root() *tstree:root()*
+ Return the root node of this tree.
+
+
+Node methods *lua-treesitter-node*
+
+tsnode:parent() *tsnode:parent()*
+ Get the node's immediate parent.
+
+tsnode:child_count() *tsnode:child_count()*
+ Get the node's number of children.
+
+tsnode:child(N) *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()*
+ Get the node's number of named children.
+
+tsnode:named_child(N) *tsnode:named_child()*
+ Get the node's named child at the given index, where zero represents
+ the first named child.
+
+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_()*
+ 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:type() *tsnode:type()*
+ Get the node's type as a string.
+
+tsnode:symbol() *tsnode:symbol()*
+ Get the node's type as a numerical id.
+
+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()*
+ 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()*
+ Check if the node is a syntax error or contains any syntax errors.
+
+tsnode:sexpr() *tsnode:sexpr()*
+ Get an S-expression representing the node as a string.
+
+tsnode:descendant_for_range(start_row, start_col, end_row, end_col)
+ *tsnode:descendant_for_range()*
+ Get the smallest node within this node that spans the given range of
+ (row, column) positions
+
+tsnode:named_descendant_for_range(start_row, start_col, end_row, end_col)
+ *tsnode:named_descendant_for_range()*
+ Get the smallest named node within this node that spans the given
+ range of (row, column) positions
+
+Query methods *lua-treesitter-query*
+
+Tree-sitter queries are supported, with some limitations. Currently, the only
+supported match predicate is `eq?` (both comparing a capture against a string
+and two captures against each other).
+
+vim.treesitter.parse_query(lang, query)
+ *vim.treesitter.parse_query(()*
+ Parse the query as a string. (If the query is in a file, the caller
+ should read the contents into a string before calling).
+
+query:iter_captures(node, bufnr, start_row, end_row)
+ *query:iter_captures()*
+ Iterate over all captures from all matches inside a `node`.
+ `bufnr` 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. `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)
+
+ The iterator returns two values, a numeric id identifying the capture
+ and the captured node. The following example shows how to get captures
+ by name:
+>
+ for id, node 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
+<
+query:iter_matches(node, bufnr, start_row, end_row)
+ *query:iter_matches()*
+ Iterate over all matches within a node. The arguments are the same as
+ for |query:iter_captures()| but the iterated values are different:
+ an (1-based) index of the pattern in the query, and a table mapping
+ capture indices to nodes. If the query has more than one pattern
+ the capture table might be sparse, and e.g. `pairs` should be used and not
+ `ipairs`. Here an example iterating over all captures in
+ every match:
+>
+ for pattern, match 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
+ ... use the info here ...
+ end
+ end
+>
+Treesitter syntax highlighting (WIP) *lua-treesitter-highlight*
+
+NOTE: This is a partially implemented feature, and not usable as a default
+solution yet. What is documented here is a temporary interface indented
+for those who want to experiment with this feature and contribute to
+its development.
+
+Highlights are defined in the same query format as in the tree-sitter highlight
+crate, which some limitations and additions. Set a highlight query for a
+buffer with this code: >
+
+ local query = [[
+ "for" @keyword
+ "if" @keyword
+ "return" @keyword
+
+ (string_literal) @string
+ (number_literal) @number
+ (comment) @comment
+
+ (preproc_function_def name: (identifier) @function)
+
+ ; ... more definitions
+ ]]
+
+ highlighter = vim.treesitter.TSHighlighter.new(query, bufnr, lang)
+ -- alternatively, to use the current buffer and its filetype:
+ -- highlighter = vim.treesitter.TSHighlighter.new(query)
+
+ -- Don't recreate the highlighter for the same buffer, instead
+ -- modify the query like this:
+ local query2 = [[ ... ]]
+ highlighter:set_query(query2)
+
+As mentioned above the supported predicate is currently only `eq?`. `match?`
+predicates behave like matching always fails. As an addition a capture which
+begin with an upper-case letter like `@WarningMsg` will map directly to this
+highlight group, if defined. Also if the predicate begins with upper-case and
+contains a dot only the part before the first will be interpreted as the
+highlight group. As an example, this warns of a binary expression with two
+identical identifiers, highlighting both as |hl-WarningMsg|: >
+
+ ((binary_expression left: (identifier) @WarningMsg.left right: (identifier) @WarningMsg.right)
+ (eq? @WarningMsg.left @WarningMsg.right))
+
+------------------------------------------------------------------------------
+VIM.HIGHLIGHT *lua-highlight*
+
+Nvim includes a function for highlighting a selection on yank (see for example
+https://github.com/machakann/vim-highlightedyank). To enable it, add
+>
+ 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
+>
+ au TextYankPost * silent! lua vim.highlight.on_yank {higroup="IncSearch", timeout=150}
+<
+If you want to exclude visual selections from highlighting on yank, use
+>
+ 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 `"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`)
+
+vim.highlight.range({bufnr}, {ns}, {higroup}, {start}, {finish}, {rtype}, {inclusive})
+ *vim.highlight.range()*
+ Highlights the range between {start} and {finish} (tuples of {line,col})
+ in buffer {bufnr} with the highlight group {higroup} using the namespace
+ {ns}. Optional arguments are the type of range (characterwise, linewise,
+ or blockwise, see |setreg|; default to characterwise) and whether the
+ range is inclusive (default false).
+
+------------------------------------------------------------------------------
+VIM.REGEX *lua-regex*
+
+Vim regexes can be used directly from lua. Currently they only allow
+matching within a single line.
+
+vim.regex({re}) *vim.regex()*
+
+ Parse the regex {re} and return a regex object. 'magic' and
+ 'ignorecase' options are ignored, lua regexes always defaults to magic
+ and ignoring case. The behavior can be changed with flags in
+ the beginning of the string |/magic|.
+
+Regex objects support the following methods:
+
+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.
+
+regex:match_line({bufnr}, {line_idx}[, {start}, {end}]) *regex:match_line()*
+ 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 *lua-builtin*
+
+vim.api.{func}({...}) *vim.api*
+ Invokes Nvim |API| function {func} with arguments {...}.
+ Example: call the "nvim_get_current_line()" API function: >
+ print(tostring(vim.api.nvim_get_current_line()))
+
+vim.call({func}, {...}) *vim.call()*
+ Invokes |vim-function| or |user-function| {func} with arguments {...}.
+ See also |vim.fn|. Equivalent to: >
+ vim.fn[func]({...})
+
+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*
+ Special value used to represent NIL in msgpack-rpc and |v:null| in
+ vimL interaction, and similar cases. Lua `nil` cannot be used as
+ part of a lua table representing a Dictionary or Array, as it
+ is equivalent to a missing value: `{"foo", nil}` is the same as
+ `{"foo"}`
+
+vim.empty_dict() *vim.empty_dict()*
+ Creates a special table which will be converted to an empty
+ dictionary when converting lua values to vimL or API types. The
+ table is empty, and this property is marked using a metatable. An
+ empty table `{}` without this metatable will default to convert to
+ an array/list.
+
+ Note: if numeric keys are added to the table, the metatable will be
+ ignored and the dict converted to a list/array anyway.
+
+vim.region({bufnr}, {pos1}, {pos2}, {type}, {inclusive}) *vim.region()*
+ Converts a selection specified by the buffer ({bufnr}), starting
+ position ({pos1}, a zero-indexed pair `{line1,column1}`), ending
+ position ({pos2}, same format as {pos1}), the type of the register
+ for the selection ({type}, see |regtype|), and a boolean indicating
+ whether the selection is inclusive or not, into a zero-indexed table
+ of linewise selections of the form `{linenr = {startcol, endcol}}` .
+
+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()*
+ 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.
+
+vim.str_utfindex({str}[, {index}]) *vim.str_utfindex()*
+ Convert byte index to UTF-32 and UTF-16 indicies. If {index} is not
+ supplied, the length of the string is used. All indicies are zero-based.
+ Returns two values: the UTF-32 and UTF-16 indicies respectively.
+
+ 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.
+
+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.
+
+vim.schedule({callback}) *vim.schedule()*
+ Schedules {callback} to be invoked soon by the main event-loop. Useful
+ to avoid |textlock| or other temporary restrictions.
+
+
+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 |schedule_wrap|ped automatically, so API functions are
+ safe to call.
+
+ Parameters: ~
+ {fn} Callback to call once {timeout} expires
+ {timeout} Time in ms to wait before calling {fn}
+
+ Returns: ~
+ |vim.loop|.new_timer() object
+
+vim.wait({time}, {callback} [, {interval}]) *vim.wait()*
+ 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.
+
+
+ Returns: ~
+ 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.
+
+ Examples: >
+
+ ---
+ -- 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
+<
+
+vim.fn.{func}({...}) *vim.fn*
+ Invokes |vim-function| or |user-function| {func} with arguments {...}.
+ To call autoload functions, use the syntax: >
+ 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.
+
+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: >
+ {
+ [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.
+
+==============================================================================
+Vim Internal Variables *lua-vim-internal-variables*
+
+Built-in Vim dictionaries can be accessed and set idiomatically in Lua by each
+of the following tables.
+
+To set a value: >
+
+ vim.g.my_global_variable = 5
+<
+
+To read a value: >
+
+ print(vim.g.my_global_variable)
+<
+
+To delete a value: >
+
+ vim.g.my_global_variable = nil
+<
+
+vim.g *vim.g*
+ Table with values from |g:|
+ Keys with no values set will result in `nil`.
+
+vim.b *vim.b*
+ Gets a buffer-scoped (b:) variable for the current buffer.
+ Keys with no values set will result in `nil`.
+
+vim.w *vim.w*
+ Gets a window-scoped (w:) variable for the current window.
+ Keys with no values set will result in `nil`.
+
+vim.t *vim.t*
+ Gets a tabpage-scoped (t:) variable for the current table.
+ Keys with no values set will result in `nil`.
+
+vim.v *vim.v*
+ Gets a v: variable.
+ Keys with no values set will result in `nil`.
+
+
+Vim Internal Options *lua-vim-internal-options*
+
+Read, set and clear vim |options| in Lua by each of the following tables.
+
+
+vim.o *vim.o*
+ Table with values from |options|
+ Invalid keys will result in an error.
+
+vim.bo *vim.bo*
+ Gets a buffer-scoped option for the current buffer.
+ Invalid keys will result in an error.
+
+vim.wo *vim.wo*
+ Gets a window-scoped option for the current window.
+ Invalid keys will result in an error.
+
+
+==============================================================================
+Lua module: vim *lua-vim*
+
+inspect({object}, {options}) *vim.inspect()*
+ Return a human-readable representation of the given object.
+
+ See also: ~
+ https://github.com/kikito/inspect.lua
+ https://github.com/mpeterv/vinspect
+
+make_meta_accessor({get}, {set}, {del}) *vim.make_meta_accessor()*
+ TODO: Documentation
+
+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: >
+
+ 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: ~
+ {lines} |readfile()|-style list of lines to paste.
+ |channel-lines|
+ {phase} -1: "non-streaming" paste: the call contains all
+ lines. If paste is "streamed", `phase` indicates the stream state:
+ • 1: starts the paste (exactly once)
+ • 2: continues the paste (zero or more times)
+ • 3: ends the paste (exactly once)
+
+ Return: ~
+ false if client should cancel the paste.
+
+ See also: ~
+ |paste|
+
+schedule_wrap({cb}) *vim.schedule_wrap()*
+ Defers callback `cb` until the Nvim API is safe to call.
+
+ See also: ~
+ |lua-loop-callbacks|
+ |vim.schedule()|
+ |vim.in_fast_event()|
+
+
+
+
+deep_equal({a}, {b}) *vim.deep_equal()*
+ TODO: Documentation
+
+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 the same functions as
+ those in the input table. Userdata and threads are not copied
+ and will throw an error.
+
+ Parameters: ~
+ {orig} Table to copy
+
+ Return: ~
+ New table of copied keys and (nested) values.
+
+endswith({s}, {suffix}) *vim.endswith()*
+ Tests if `s` ends with `suffix` .
+
+ Parameters: ~
+ {s} (string) a string
+ {suffix} (string) a suffix
+
+ 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.
+
+ Parameters: ~
+ {s} String to split
+ {sep} Separator string or pattern
+ {plain} If `true` use `sep` literally (passed to
+ String.find)
+
+ Return: ~
+ Iterator over the split components
+
+ See also: ~
+ |vim.split()|
+ https://www.lua.org/pil/20.2.html
+ http://lua-users.org/wiki/StringLibraryTutorial
+
+is_callable({f}) *vim.is_callable()*
+ Returns true if object `f` can be called as a function.
+
+ Parameters: ~
+ {f} Any object
+
+ Return: ~
+ true if `f` is callable, else false
+
+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!
+
+ Parameters: ~
+ {dst} list which will be modified and appended to.
+ {src} list from which values will be inserted.
+ {start} Start index on src. defaults to 1
+ {finish} Final index on src. defaults to #src
+
+ Return: ~
+ dst
+
+ See also: ~
+ |vim.tbl_extend()|
+
+pesc({s}) *vim.pesc()*
+ Escapes magic chars in a Lua pattern.
+
+ Parameters: ~
+ {s} String to escape
+
+ Return: ~
+ %-escaped pattern string
+
+ See also: ~
+ https://github.com/rxi/lume
+
+split({s}, {sep}, {plain}) *vim.split()*
+ Splits a string at each instance of a separator.
+
+ Examples: >
+ split(":aa::b:", ":") --> {'','aa','','bb',''}
+ split("axaby", "ab?") --> {'','x','y'}
+ split(x*yz*o, "*", true) --> {'x','yz','o'}
+<
+
+ Parameters: ~
+ {s} String to split
+ {sep} Separator string or pattern
+ {plain} If `true` use `sep` literally (passed to
+ String.find)
+
+ Return: ~
+ List-like table of the split components.
+
+ See also: ~
+ |vim.gsplit()|
+
+startswith({s}, {prefix}) *vim.startswith()*
+ Tests if `s` starts with `prefix` .
+
+ Parameters: ~
+ {s} (string) a string
+ {prefix} (string) a prefix
+
+ Return: ~
+ (boolean) true if `prefix` is a prefix of s
+
+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 }`
+
+ Parameters: ~
+ {o} table The table to add the reverse to.
+
+tbl_contains({t}, {value}) *vim.tbl_contains()*
+ Checks if a list-like (vector) table contains `value` .
+
+ Parameters: ~
+ {t} Table to check
+ {value} Value to compare
+
+ Return: ~
+ true if `t` contains `value`
+
+tbl_count({t}) *vim.tbl_count()*
+ Counts the number of non-nil values in table `t` .
+>
+
+ vim.tbl_count({ a=1, b=2 }) => 2
+ vim.tbl_count({ 1, 2 }) => 2
+<
+
+ Parameters: ~
+ {t} Table
+
+ Return: ~
+ Number that is the number of the value in table
+
+ See also: ~
+ 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.
+
+ Parameters: ~
+ {behavior} Decides what to do if a key is found in more
+ than one map:
+ • "error": raise an error
+ • "keep": use value from the leftmost map
+ • "force": use value from the rightmost map
+ {...} Two or more map-like tables.
+
+ See also: ~
+ |tbl_extend()|
+
+tbl_extend({behavior}, {...}) *vim.tbl_extend()*
+ Merges two or more map-like tables.
+
+ Parameters: ~
+ {behavior} Decides what to do if a key is found in more
+ than one map:
+ • "error": raise an error
+ • "keep": use value from the leftmost map
+ • "force": use value from the rightmost map
+ {...} Two or more map-like tables.
+
+ See also: ~
+ |extend()|
+
+tbl_filter({func}, {t}) *vim.tbl_filter()*
+ Filter a table using a predicate function
+
+ Parameters: ~
+ {func} function or callable table
+ {t} table
+
+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.
+
+ Parameters: ~
+ {t} List-like table
+
+ Return: ~
+ Flattened copy of the given list-like table.
+
+ See also: ~
+ Fromhttps://github.com/premake/premake-core/blob/master/src/base/table.lua
+
+tbl_isempty({t}) *vim.tbl_isempty()*
+ See also: ~
+ Fromhttps://github.com/premake/premake-core/blob/master/src/base/table.lua@paramt Table to check
+
+tbl_islist({t}) *vim.tbl_islist()*
+ Determine whether a Lua table can be treated as an array.
+
+ An empty table `{}` will default to being treated as an array.
+ Use `vim.emtpy_dict()` to create a table treated as an empty
+ dict. Empty tables returned by `rpcrequest()` and `vim.fn`
+ functions can be checked using this function whether they
+ represent empty API arrays and vimL lists.
+
+ Parameters: ~
+ {t} Table
+
+ Return: ~
+ `true` if array-like table, else `false` .
+
+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.
+
+ Parameters: ~
+ {t} Table
+
+ Return: ~
+ list of keys
+
+ See also: ~
+ Fromhttps://github.com/premake/premake-core/blob/master/src/base/table.lua
+
+tbl_map({func}, {t}) *vim.tbl_map()*
+ Apply a function to all values of a table.
+
+ Parameters: ~
+ {func} function or callable table
+ {t} table
+
+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.
+
+ Parameters: ~
+ {t} Table
+
+ Return: ~
+ list of values
+
+trim({s}) *vim.trim()*
+ Trim whitespace (Lua pattern "%s") from both sides of a
+ string.
+
+ Parameters: ~
+ {s} String to trim
+
+ Return: ~
+ String with whitespace removed from its beginning and end
+
+ See also: ~
+ https://www.lua.org/pil/20.2.html
+
+validate({opt}) *vim.validate()*
+ Validates a parameter specification (types and values).
+
+ Usage example: >
+
+ 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): >
+
+ 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')
+<
+
+ Parameters: ~
+ {opt} Map of parameter names to validations. 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 type name, one of: ("table",
+ "t", "string", "s", "number", "n", "boolean",
+ "b", "function", "f", "nil", "thread",
+ "userdata")
+ • 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
+ • msg: (optional) error string if validation
+ fails
+
+ vim:tw=78:ts=8:ft=help:norl:
diff --git a/runtime/doc/map.txt b/runtime/doc/map.txt
index abe86749c4..ed31ecc42e 100644
--- a/runtime/doc/map.txt
+++ b/runtime/doc/map.txt
@@ -522,10 +522,9 @@ single CTRL-V (you have to type CTRL-V two times).
You can create an empty {rhs} by typing nothing after a single CTRL-V (you
have to type CTRL-V two times). Unfortunately, you cannot do this in a vimrc
file.
- *<Nop>*
+ |<Nop>|
An easier way to get a mapping that doesn't produce anything, is to use
-"<Nop>" for the {rhs}. This only works when the |<>| notation is enabled.
-For example, to make sure that function key 8 does nothing at all: >
+"<Nop>" for the {rhs}. For example, to disable function key 8: >
:map <F8> <Nop>
:map! <F8> <Nop>
<
@@ -786,7 +785,7 @@ g@{motion} Call the function set by the 'operatorfunc' option.
character of the text.
The function is called with one String argument:
"line" {motion} was |linewise|
- "char" {motion} was |characterwise|
+ "char" {motion} was |charwise|
"block" {motion} was |blockwise-visual|
Although "block" would rarely appear, since it can
only result from Visual mode where "g@" is not useful.
diff --git a/runtime/doc/message.txt b/runtime/doc/message.txt
index e8c76adad4..43b1eb5e0c 100644
--- a/runtime/doc/message.txt
+++ b/runtime/doc/message.txt
@@ -69,7 +69,7 @@ See `:messages` above.
LIST OF MESSAGES
*E222* *E228* *E232* *E256* *E293* *E298* *E304* *E317*
*E318* *E356* *E438* *E439* *E440* *E316* *E320* *E322*
- *E323* *E341* *E473* *E570* *E685* *E950* >
+ *E323* *E341* *E473* *E570* *E685* *E292* >
Add to read buffer
makemap: Illegal mode
Cannot create BalloonEval with both message and callback
@@ -556,7 +556,8 @@ allowed for the command that was used.
Vim was not able to create a swap file. You can still edit the file, but if
Vim unexpectedly exits the changes will be lost. And Vim may consume a lot of
memory when editing a big file. You may want to change the 'directory' option
-to avoid this error. See |swap-file|.
+to avoid this error. This error is not given when 'directory' is empty. See
+|swap-file|.
*E140* >
Use ! to write partial buffer
@@ -670,21 +671,20 @@ being disabled. Remove the 'C' flag from the 'cpoptions' option to enable it.
*E471* >
Argument required
-This happens when an Ex command with mandatory argument(s) was executed, but
-no argument has been specified.
+Ex command was executed without a mandatory argument(s).
*E474* *E475* *E983* >
Invalid argument
Invalid argument: {arg}
Duplicate argument: {arg}
-An Ex command or function has been executed, but an invalid argument has been
-specified.
+Ex command or function was given an invalid argument. Or |jobstart()| or
+|system()| was given a non-executable command.
*E488* >
Trailing characters
-An argument has been added to an Ex command that does not permit one.
+An argument was given to an Ex command that does not permit one.
*E477* *E478* >
No ! allowed
diff --git a/runtime/doc/mlang.txt b/runtime/doc/mlang.txt
index 03c48b962d..2a10a7051d 100644
--- a/runtime/doc/mlang.txt
+++ b/runtime/doc/mlang.txt
@@ -185,8 +185,8 @@ you can do it without restarting Vim: >
:source $VIMRUNTIME/menu.vim
Each part of a menu path is translated separately. The result is that when
-"Help" is translated to "Hilfe" and "Overview" to "Überblick" then
-"Help.Overview" will be translated to "Hilfe.Überblick".
+"Help" is translated to "Hilfe" and "Overview" to "Überblick" then
+"Help.Overview" will be translated to "Hilfe.Überblick".
==============================================================================
3. Scripts *multilang-scripts*
diff --git a/runtime/doc/motion.txt b/runtime/doc/motion.txt
index 97c7d1cc43..a6c072e489 100644
--- a/runtime/doc/motion.txt
+++ b/runtime/doc/motion.txt
@@ -60,11 +60,11 @@ After applying the operator the cursor is mostly left at the start of the text
that was operated upon. For example, "yfe" doesn't move the cursor, but "yFe"
moves the cursor leftwards to the "e" where the yank started.
- *linewise* *characterwise*
+ *linewise* *charwise* *characterwise*
The operator either affects whole lines, or the characters between the start
and end position. Generally, motions that move between lines affect lines
(are linewise), and motions that move within a line affect characters (are
-characterwise). However, there are some exceptions.
+charwise). However, there are some exceptions.
*exclusive* *inclusive*
Character motion is either inclusive or exclusive. When inclusive, the
@@ -106,10 +106,10 @@ This cannot be repeated: >
d:if 1<CR>
call search("f")<CR>
endif<CR>
-Note that when using ":" any motion becomes characterwise exclusive.
+Note that when using ":" any motion becomes charwise exclusive.
*forced-motion*
-FORCING A MOTION TO BE LINEWISE, CHARACTERWISE OR BLOCKWISE
+FORCING A MOTION TO BE LINEWISE, CHARWISE OR BLOCKWISE
When a motion is not of the type you would like to use, you can force another
type by using "v", "V" or CTRL-V just after the operator.
@@ -121,22 +121,22 @@ deletes from the cursor position until the character below the cursor >
d<C-V>j
deletes the character under the cursor and the character below the cursor. >
-Be careful with forcing a linewise movement to be used characterwise or
-blockwise, the column may not always be defined.
+Be careful with forcing a linewise movement to be used charwise or blockwise,
+the column may not always be defined.
*o_v*
v When used after an operator, before the motion command: Force
- the operator to work characterwise, also when the motion is
+ the operator to work charwise, also when the motion is
linewise. If the motion was linewise, it will become
|exclusive|.
- If the motion already was characterwise, toggle
+ If the motion already was charwise, toggle
inclusive/exclusive. This can be used to make an exclusive
motion inclusive and an inclusive motion exclusive.
*o_V*
V When used after an operator, before the motion command: Force
the operator to work linewise, also when the motion is
- characterwise.
+ charwise.
*o_CTRL-V*
CTRL-V When used after an operator, before the motion command: Force
@@ -219,6 +219,12 @@ g^ When lines wrap ('wrap' on): To the first non-blank
gm Like "g0", but half a screenwidth to the right (or as
much as possible).
+ *gM*
+gM Like "g0", but to halfway the text of the line.
+ With a count: to this percentage of text in 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
the screen line and [count - 1] screen lines downward
@@ -412,35 +418,35 @@ between Vi and Vim.
5. Text object motions *object-motions*
*(*
-( [count] sentences backward. |exclusive| motion.
+( [count] |sentence|s backward. |exclusive| motion.
*)*
-) [count] sentences forward. |exclusive| motion.
+) [count] |sentence|s forward. |exclusive| motion.
*{*
-{ [count] paragraphs backward. |exclusive| motion.
+{ [count] |paragraph|s backward. |exclusive| motion.
*}*
-} [count] paragraphs forward. |exclusive| motion.
+} [count] |paragraph|s forward. |exclusive| motion.
*]]*
-]] [count] sections forward or to the next '{' in the
+]] [count] |section|s forward or to the next '{' in the
first column. When used after an operator, then also
stops below a '}' in the first column. |exclusive|
Note that |exclusive-linewise| often applies.
*][*
-][ [count] sections forward or to the next '}' in the
+][ [count] |section|s forward or to the next '}' in the
first column. |exclusive|
Note that |exclusive-linewise| often applies.
*[[*
-[[ [count] sections backward or to the previous '{' in
+[[ [count] |section|s backward or to the previous '{' in
the first column. |exclusive|
Note that |exclusive-linewise| often applies.
*[]*
-[] [count] sections backward or to the previous '}' in
+[] [count] |section|s backward or to the previous '}' in
the first column. |exclusive|
Note that |exclusive-linewise| often applies.
@@ -502,36 +508,36 @@ aw "a word", select [count] words (see |word|).
Leading or trailing white space is included, but not
counted.
When used in Visual linewise mode "aw" switches to
- Visual characterwise mode.
+ Visual charwise mode.
*v_iw* *iw*
iw "inner word", select [count] words (see |word|).
White space between words is counted too.
When used in Visual linewise mode "iw" switches to
- Visual characterwise mode.
+ Visual charwise mode.
*v_aW* *aW*
aW "a WORD", select [count] WORDs (see |WORD|).
Leading or trailing white space is included, but not
counted.
When used in Visual linewise mode "aW" switches to
- Visual characterwise mode.
+ Visual charwise mode.
*v_iW* *iW*
iW "inner WORD", select [count] WORDs (see |WORD|).
White space between words is counted too.
When used in Visual linewise mode "iW" switches to
- Visual characterwise mode.
+ Visual charwise mode.
*v_as* *as*
as "a sentence", select [count] sentences (see
|sentence|).
- When used in Visual mode it is made characterwise.
+ When used in Visual mode it is made charwise.
*v_is* *is*
is "inner sentence", select [count] sentences (see
|sentence|).
- When used in Visual mode it is made characterwise.
+ When used in Visual mode it is made charwise.
*v_ap* *ap*
ap "a paragraph", select [count] paragraphs (see
@@ -552,14 +558,14 @@ 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 ']'.
- When used in Visual mode it is made characterwise.
+ 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 ']'.
- When used in Visual mode it is made characterwise.
+ When used in Visual mode it is made charwise.
a) *v_a)* *a)* *a(*
a( *vab* *v_ab* *v_a(* *ab*
@@ -567,54 +573,54 @@ ab "a block", select [count] blocks, from "[count] [(" to
the matching ')', including the '(' and ')' (see
|[(|). Does not include white space outside of the
parenthesis.
- When used in Visual mode it is made characterwise.
+ When used in Visual mode it is made charwise.
i) *v_i)* *i)* *i(*
i( *vib* *v_ib* *v_i(* *ib*
ib "inner block", select [count] blocks, from "[count] [("
to the matching ')', excluding the '(' and ')' (see
|[(|).
- When used in Visual mode it is made characterwise.
+ 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 '>'.
- When used in Visual mode it is made characterwise.
+ 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 '>'.
- When used in Visual mode it is made characterwise.
+ When used in Visual mode it is made charwise.
*v_at* *at*
at "a tag block", select [count] tag blocks, from the
[count]'th unmatched "<aaa>" backwards to the matching
"</aaa>", including the "<aaa>" and "</aaa>".
See |tag-blocks| about the details.
- When used in Visual mode it is made characterwise.
+ When used in Visual mode it is made charwise.
*v_it* *it*
it "inner tag block", select [count] tag blocks, from the
[count]'th unmatched "<aaa>" backwards to the matching
"</aaa>", excluding the "<aaa>" and "</aaa>".
See |tag-blocks| about the details.
- When used in Visual mode it is made characterwise.
+ When used in Visual mode it is made charwise.
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
|[{|).
- When used in Visual mode it is made characterwise.
+ 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
|[{|).
- When used in Visual mode it is made characterwise.
+ When used in Visual mode it is made charwise.
a" *v_aquote* *aquote*
a' *v_a'* *a'*
@@ -628,7 +634,7 @@ a` *v_a`* *a`*
start of the line.
Any trailing white space is included, unless there is
none, then leading white space is included.
- When used in Visual mode it is made characterwise.
+ When used in Visual mode it is made charwise.
Repeating this object in Visual mode another string is
included. A count is currently not used.
@@ -1077,6 +1083,60 @@ When you split a window, the jumplist will be copied to the new window.
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
+
+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
+>
+
+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
+>
CHANGE LIST JUMPS *changelist* *change-list-jumps* *E664*
diff --git a/runtime/doc/msgpack_rpc.txt b/runtime/doc/msgpack_rpc.txt
index f5d42dfeb2..5368cf0f4f 100644
--- a/runtime/doc/msgpack_rpc.txt
+++ b/runtime/doc/msgpack_rpc.txt
@@ -1,7 +1,8 @@
- NVIM REFERENCE MANUAL by Thiago de Arruda
-
-
+ NVIM REFERENCE MANUAL
This document was merged into |api.txt| and |develop.txt|.
+
+==============================================================================
+ vim:tw=78:ts=8:noet:ft=help:norl:
diff --git a/runtime/doc/nvim_terminal_emulator.txt b/runtime/doc/nvim_terminal_emulator.txt
index 1a5e6421e7..a96d118667 100644
--- a/runtime/doc/nvim_terminal_emulator.txt
+++ b/runtime/doc/nvim_terminal_emulator.txt
@@ -50,6 +50,13 @@ mode" in a normal buffer, such as |i| or |:startinsert|. In this mode all keys
except <C-\><C-N> are sent to the underlying program. Use <C-\><C-N> to return
to normal-mode. |CTRL-\_CTRL-N|
+Terminal-mode forces these local options:
+
+ 'nocursorline'
+ 'nocursorcolumn'
+ 'scrolloff' = 0
+ 'sidescrolloff' = 0
+
Terminal-mode has its own |:tnoremap| namespace for mappings, this can be used
to automate any terminal interaction.
@@ -307,6 +314,23 @@ Other commands ~
isn't one
+Prompt mode ~
+ *termdebug-prompt*
+When on MS-Windows, gdb will run in a buffer with 'buftype' set to "prompt".
+This works slightly differently:
+- The gdb window will be in Insert mode while typing commands. Go to Normal
+ mode with <Esc>, then you can move around in the buffer, copy/paste, etc.
+ Go back to editing the gdb command with any command that starts Insert mode,
+ such as `a` or `i`.
+- The program being debugged will run in a separate window. On MS-Windows
+ this is a new console window. On Unix, if the |+terminal| feature is
+ available a Terminal window will be opened to run the debugged program in.
+
+ *termdebug_use_prompt*
+Prompt mode can be used even when the |+terminal| feature is present with: >
+ let g:termdebug_use_prompt = 1
+
+
Communication ~
*termdebug-communication*
There is another, hidden, buffer, which is used for Vim to communicate with
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
index 188f7fc2e2..e1beea0fed 100644
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -843,6 +843,14 @@ A jump table for the options with a short description can be found at |Q_op|.
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: >
@@ -1085,6 +1093,8 @@ A jump table for the options with a short description can be found at |Q_op|.
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|.
@@ -1478,7 +1488,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 (Vim default: "aABceFs",
+'cpoptions' 'cpo' string (Vim default: "aABceFs_",
Vi default: all flags)
global
A sequence of single character flags. When a character is present
@@ -1875,7 +1885,7 @@ A jump table for the options with a short description can be found at |Q_op|.
security reasons.
*'dip'* *'diffopt'*
-'diffopt' 'dip' string (default "internal,filler")
+'diffopt' 'dip' string (default "internal,filler,closeoff")
global
Option settings for diff mode. It can consist of the following items.
All are optional. Items must be separated by a comma.
@@ -1932,6 +1942,12 @@ A jump table for the options with a short description can be found at |Q_op|.
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.
@@ -1978,7 +1994,7 @@ A jump table for the options with a short description can be found at |Q_op|.
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!).
+ 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"
@@ -1986,12 +2002,14 @@ A jump table for the options with a short description can be found at |Q_op|.
- A directory starting with "./" (or ".\" for 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 "//"
- or "\\", the swap file name will be built from the complete path to
- the file with all path separators substituted to percent '%' signs.
- This will ensure file name uniqueness in the preserve directory.
- On Win32, when a separating comma is following, you must use "//",
- since "\\" will include the comma in the file name.
+ - 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 substituted to percent '%' signs. 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.
@@ -2242,8 +2260,7 @@ A jump table for the options with a short description can be found at |Q_op|.
*'fileformat'* *'ff'*
'fileformat' 'ff' string (Windows default: "dos",
- Unix default: "unix",
- Macintosh default: "mac")
+ Unix default: "unix")
local to buffer
This gives the <EOL> of the current buffer, which is used for
reading/writing the buffer from/to a file:
@@ -2265,7 +2282,6 @@ A jump table for the options with a short description can be found at |Q_op|.
'fileformats' 'ffs' string (default:
Vim+Vi Win32: "dos,unix",
Vim Unix: "unix,dos",
- Vim Mac: "mac,unix,dos",
Vi others: "")
global
This gives the end-of-line (<EOL>) formats that will be tried when
@@ -2348,7 +2364,7 @@ A jump table for the options with a short description can be found at |Q_op|.
*'fillchars'* *'fcs'*
'fillchars' 'fcs' string (default "")
- local to window
+ global or local to window |global-local|
Characters to fill the statuslines and vertical separators.
It is a comma separated list of items:
@@ -2357,6 +2373,9 @@ A jump table for the options with a short description can be found at |Q_op|.
stlnc:c ' ' or '=' statusline of the non-current windows
vert:c '│' or '|' vertical separators |:vsplit|
fold:c '·' or '-' filling 'foldtext'
+ foldopen:c '-' mark the beginning of a fold
+ foldclose:c '+' show a closed fold
+ foldsep:c '│' or '|' open fold middle marker
diff:c '-' deleted lines of the 'diff' option
msgsep:c ' ' message separator 'display'
eob:c '~' empty lines at the end of a buffer
@@ -2365,7 +2384,7 @@ A jump table for the options with a short description can be found at |Q_op|.
"stlnc" the space will be used when there is highlighting, '^' or '='
otherwise.
- If 'ambiwidth' is "double" then "vert" and "fold" default to
+ If 'ambiwidth' is "double" then "vert", "foldsep" and "fold" default to
single-byte alternatives.
Example: >
@@ -2402,11 +2421,14 @@ A jump table for the options with a short description can be found at |Q_op|.
automatically close when moving out of them.
*'foldcolumn'* *'fdc'*
-'foldcolumn' 'fdc' number (default 0)
+'foldcolumn' 'fdc' string (default "0")
local to window
- When non-zero, a column with the specified width is shown at the side
- of the window which indicates open and closed folds. The maximum
- value is 12.
+ When and how to draw the foldcolumn. Valid values are:
+ "auto": resize to the maximum 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|.
*'foldenable'* *'fen'* *'nofoldenable'* *'nofen'*
@@ -3443,6 +3465,17 @@ 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.
+ *'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|
+
*'joinspaces'* *'js'* *'nojoinspaces'* *'nojs'*
'joinspaces' 'js' boolean (default on)
global
@@ -3657,7 +3690,7 @@ A jump table for the options with a short description can be found at |Q_op|.
*'listchars'* *'lcs'*
'listchars' 'lcs' string (default: "tab:> ,trail:-,nbsp:+"
Vi default: "eol:$")
- local to window
+ 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.
@@ -3698,9 +3731,9 @@ A jump table for the options with a short description can be found at |Q_op|.
off and the line continues beyond the right of the
screen.
*lcs-precedes*
- precedes:c Character to show in the first column, when 'wrap'
- is off and there is text preceding the character
- visible in the first column.
+ 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.
@@ -3914,6 +3947,8 @@ A jump table for the options with a short description can be found at |Q_op|.
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.
*'modelines'* *'mls'*
'modelines' 'mls' number (default 5)
@@ -4496,13 +4531,6 @@ A jump table for the options with a short description can be found at |Q_op|.
global
When on a ":" prompt is used in Ex mode.
- *'pumheight'* *'ph'*
-'pumheight' 'ph' number (default 0)
- global
- Determines the maximum number of items to show in the popup menu for
- Insert mode completion. When zero as much space as available is used.
- |ins-completion-menu|.
-
*'pumblend'* *'pb'*
'pumblend' 'pb' number (default 0)
global
@@ -4519,6 +4547,19 @@ A jump table for the options with a short description can be found at |Q_op|.
<
UI-dependent. Works best with RGB colors. 'termguicolors'
+ *'pumheight'* *'ph'*
+'pumheight' 'ph' number (default 0)
+ global
+ Maximum number of items to show in the popup menu
+ (|ins-completion-menu|). Zero means "use available screen space".
+
+ *'pumwidth'* *'pw'*
+'pumwidth' 'pw' number (default 15)
+ global
+ 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.
+
*'pyxversion'* *'pyx'*
'pyxversion' 'pyx' number (default depends on the build)
global
@@ -4583,6 +4624,16 @@ 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.
+ 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.
*'redrawtime'* *'rdt'*
'redrawtime' 'rdt' number (default 2000)
@@ -4851,13 +4902,17 @@ A jump table for the options with a short description can be found at |Q_op|.
*'scrolloff'* *'so'*
'scrolloff' 'so' number (default 0)
- global
+ global or local to window |global-local|
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).
- For scrolling horizontally see 'sidescrolloff'.
+ 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'.
*'scrollopt'* *'sbo'*
'scrollopt' 'sbo' string (default "ver,jump")
@@ -5158,8 +5213,9 @@ A jump table for the options with a short description can be found at |Q_op|.
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 (on Windows): >
- set shell=powershell shellquote=( shellpipe=\| shellxquote=
+ To use powershell: >
+ let &shell = has('win32') ? 'powershell' : 'pwsh'
+ set shellquote= shellpipe=\| shellxquote=
set shellcmdflag=-NoLogo\ -NoProfile\ -ExecutionPolicy\ RemoteSigned\ -Command
set shellredir=\|\ Out-File\ -Encoding\ UTF8
@@ -5465,7 +5521,7 @@ A jump table for the options with a short description can be found at |Q_op|.
*'sidescrolloff'* *'siso'*
'sidescrolloff' 'siso' number (default 0)
- global
+ 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
value greater than 0 while having |'sidescroll'| also at a non-zero
@@ -5474,7 +5530,11 @@ A jump table for the options with a short description can be found at |Q_op|.
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: >
@@ -5722,7 +5782,7 @@ A jump table for the options with a short description can be found at |Q_op|.
current one. |:vsplit|
*'startofline'* *'sol'* *'nostartofline'* *'nosol'*
-'startofline' 'sol' boolean (default on)
+'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
@@ -5749,7 +5809,9 @@ A jump table for the options with a short description can be found at |Q_op|.
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 result can contain %{} items that will be evaluated too.
+< 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.
@@ -5878,13 +5940,15 @@ A jump table for the options with a short description can be found at |Q_op|.
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]%)...
-< *g:actual_curbuf*
- Beware that an expression is evaluated each and every time the status
- line is displayed. 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.
+< Beware that an expression is evaluated each and every time the status
+ line is displayed.
+ *g:actual_curbuf* *g:actual_curwin*
+ 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|.
@@ -5990,6 +6054,8 @@ A jump table for the options with a short description can be found at |Q_op|.
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.
*'synmaxcol'* *'smc'*
'synmaxcol' 'smc' number (default 3000)
@@ -6149,6 +6215,14 @@ 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)
+ 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
+ tags. See |tag-function| for an explanation of how to write the
+ function and an example.
+
*'taglength'* *'tl'*
'taglength' 'tl' number (default 0)
global
@@ -6448,9 +6522,11 @@ A jump table for the options with a short description can be found at |Q_op|.
>= 1 When the shada file is read or written.
>= 2 When a file is ":source"'ed.
>= 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 Every executed autocommand.
+ >= 11 Finding items in a path
>= 12 Every executed function.
>= 13 When an exception is thrown, caught, finished, or discarded.
>= 14 Anything pending in a ":finally" clause.
@@ -6620,23 +6696,23 @@ A jump table for the options with a short description can be found at |Q_op|.
*'wildmenu'* *'wmnu'* *'nowildmenu'* *'nowmnu'*
'wildmenu' 'wmnu' boolean (default on)
global
- 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 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.
- When 'wildmode' is used, "wildmenu" mode is used where "full" is
- specified. "longest" and "list" do not start "wildmenu" mode.
- You can check the current mode with |wildmenumode()|.
- If there are more matches than can fit in the line, a ">" is shown on
- the right and/or a "<" is shown on the left. The status line scrolls
- as needed.
- The "wildmenu" mode is abandoned when a key is hit that is not used
- for selecting a completion.
- While the "wildmenu" is active the following keys have special
- meanings:
-
+ Enables "enhanced mode" of command-line completion. When user hits
+ <Tab> (or 'wildchar') to invoke completion, the possible matches are
+ shown in a menu just above the command-line (see 'wildoptions'), with
+ the first match highlighted (overwriting the statusline). Keys that
+ show the previous/next match (<Tab>/CTRL-P/CTRL-N) highlight the
+ match.
+ 'wildmode' must specify "full": "longest" and "list" do not start
+ 'wildmenu' mode. You can check the current mode with |wildmenumode()|.
+ The menu is canceled when a key is hit that is not used for selecting
+ 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.
<Left> <Right> - select previous/next match (like CTRL-P/CTRL-N)
<Down> - in filename/menu name completion: move into a
subdirectory or submenu.
@@ -6645,15 +6721,12 @@ A jump table for the options with a short description can be found at |Q_op|.
<Up> - in filename/menu name completion: move up into
parent directory or parent menu.
- This makes the menus accessible from the console |console-menus|.
-
- If you prefer the <Left> and <Right> keys to move the cursor instead
- of selecting a different match, use this: >
+ 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>
<
- The "WildMenu" highlighting is used for displaying the current match
- |hl-WildMenu|.
+ |hl-WildMenu| highlights the current match.
*'wildmode'* *'wim'*
'wildmode' 'wim' string (default: "full")
@@ -6677,6 +6750,8 @@ A jump table for the options with a short description can be found at |Q_op|.
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, 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: >
@@ -6908,7 +6983,6 @@ A jump table for the options with a short description can be found at |Q_op|.
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.
- When negative, all redrawn characters cause a delay, even if the
- character already was displayed by the UI. For debugging purposes.
+ See 'redrawdebug' for more options. For debugging purposes.
vim:tw=78:ts=8:noet:ft=help:norl:
diff --git a/runtime/doc/pattern.txt b/runtime/doc/pattern.txt
index adfab07758..7129c6cd58 100644
--- a/runtime/doc/pattern.txt
+++ b/runtime/doc/pattern.txt
@@ -1111,6 +1111,9 @@ x A single character, with no special meaning, matches itself
*[:tab:]* [:tab:] the <Tab> character
*[:escape:]* [:escape:] the <Esc> character
*[:backspace:]* [:backspace:] the <BS> character
+*[:ident:]* [:ident:] identifier character (same as "\i")
+*[:keyword:]* [:keyword:] keyword character (same as "\k")
+*[:fname:]* [:fname:] file name character (same as "\f")
The brackets in character class expressions are additional to the
brackets delimiting a collection. For example, the following is a
plausible pattern for a Unix filename: "[-./[:alnum:]_~]\+" That is,
diff --git a/runtime/doc/provider.txt b/runtime/doc/provider.txt
index 0083bb63a4..0a6cdc60e8 100644
--- a/runtime/doc/provider.txt
+++ b/runtime/doc/provider.txt
@@ -58,12 +58,14 @@ If you run into problems, uninstall _both_ then install "pynvim" again: >
PYTHON PROVIDER CONFIGURATION ~
*g:python_host_prog*
Command to start Python 2 (executable, not directory). Setting this makes
-startup faster. Useful for working with virtualenvs. >
+startup faster. Useful for working with virtualenvs. Must be set before any
+check for has("python2"). >
let g:python_host_prog = '/path/to/python'
<
*g:python3_host_prog*
Command to start Python 3 (executable, not directory). Setting this makes
-startup faster. Useful for working with virtualenvs. >
+startup faster. Useful for working with virtualenvs. Must be set before any
+check for has("python3"). >
let g:python3_host_prog = '/path/to/python3'
<
*g:loaded_python_provider*
@@ -125,6 +127,31 @@ To use the RVM "system" Ruby installation: >
let g:ruby_host_prog = 'rvm system do neovim-ruby-host'
==============================================================================
+Perl integration *provider-perl*
+
+Nvim supports Perl |remote-plugin|s.
+https://github.com/jacquesg/p5-Neovim-Ext
+
+
+PERL QUICKSTART~
+
+To use perl remote-plugins with Nvim, install the "Neovim::Ext" cpan package: >
+ cpanm -n Neovim::Ext
+
+Run |:checkhealth| to see if your system is up-to-date.
+
+
+PERL PROVIDER CONFIGURATION~
+ *g:loaded_perl_provider*
+To disable Perl support: >
+ :let g:loaded_perl_provider = 0
+<
+ *g:perl_host_prog*
+Command to start the Perl executable. Must be set before any
+check for has("perl"). >
+ let g:perl_host_prog = '/path/to/perl'
+<
+==============================================================================
Node.js integration *provider-nodejs*
Nvim supports Node.js |remote-plugin|s.
diff --git a/runtime/doc/quickfix.txt b/runtime/doc/quickfix.txt
index 3ae6d9461f..61e090cc78 100644
--- a/runtime/doc/quickfix.txt
+++ b/runtime/doc/quickfix.txt
@@ -109,6 +109,36 @@ processing a quickfix or location list command, it will be aborted.
list for the current window is used instead of the
quickfix list.
+ *:cabo* *:cabove*
+:[count]cabo[ve] Go to the [count] error above the current line in the
+ current buffer. If [count] is omitted, then 1 is
+ used. If there are no errors, then an error message
+ is displayed. Assumes that the entries in a quickfix
+ list are sorted by their buffer number and line
+ number. If there are multiple errors on the same line,
+ then only the first entry is used. If [count] exceeds
+ the number of entries above the current line, then the
+ first error in the file is selected.
+
+ *:lab* *:labove*
+:[count]lab[ove] Same as ":cabove", except the location list for the
+ current window is used instead of the quickfix list.
+
+ *:cbe* *:cbelow*
+:[count]cbe[low] Go to the [count] error below the current line in the
+ current buffer. If [count] is omitted, then 1 is
+ used. If there are no errors, then an error message
+ is displayed. Assumes that the entries in a quickfix
+ list are sorted by their buffer number and line
+ number. If there are multiple errors on the same
+ line, then only the first entry is used. If [count]
+ exceeds the number of entries below the current line,
+ then the last error in the file is selected.
+
+ *:lbe* *:lbelow*
+:[count]lbe[low] Same as ":cbelow", except the location list for the
+ current window is used instead of the quickfix list.
+
*:cnf* *:cnfile*
:[count]cnf[ile][!] Display the first error in the [count] next file in
the list that includes a file name. If there are no
diff --git a/runtime/doc/quickref.txt b/runtime/doc/quickref.txt
index 87cb9b54f5..224f14a18b 100644
--- a/runtime/doc/quickref.txt
+++ b/runtime/doc/quickref.txt
@@ -47,6 +47,7 @@ N is used to indicate an optional count that can be given before the command.
|g$| N g$ to last character in screen line (differs from "$"
when lines wrap)
|gm| gm to middle of the screen line
+|gM| gM to middle of the line
|bar| N | to column N (default: 1)
|f| N f{char} to the Nth occurrence of {char} to the right
|F| N F{char} to the Nth occurrence of {char} to the left
@@ -742,6 +743,7 @@ Short explanation of each option: *option-list*
'iskeyword' 'isk' characters included in keywords
'isprint' 'isp' printable characters
'joinspaces' 'js' two spaces after a period with a join command
+'jumpoptions' 'jop' specifies how jumping is done
'keymap' 'kmp' name of a keyboard mapping
'keymodel' 'km' enable starting/stopping selection with keys
'keywordprg' 'kp' program to use for the "K" command
diff --git a/runtime/doc/sign.txt b/runtime/doc/sign.txt
index 4e0d91dae0..e3ba4ba181 100644
--- a/runtime/doc/sign.txt
+++ b/runtime/doc/sign.txt
@@ -176,9 +176,9 @@ See |sign_place()| for the equivalent Vim script function.
By default, the sign is assigned a default priority of 10. To
assign a different priority value, use "priority={prio}" to
- specify a value. The priority is used to determine the
- highlight group used when multiple signs are placed on the
- same line.
+ specify a value. The priority is used to determine the sign
+ that is displayed when multiple signs are placed on the same
+ line.
Examples: >
:sign place 5 line=3 name=sign1 file=a.py
@@ -198,7 +198,9 @@ See |sign_place()| for the equivalent Vim script function.
it (e.g., when the debugger has stopped at a breakpoint).
The optional "group={group}" attribute can be used before
- "file=" to select a sign in a particular group.
+ "file=" to select a sign in a particular group. The optional
+ "priority={prio}" attribute can be used to change the priority
+ of an existing sign.
:sign place {id} name={name} [buffer={nr}]
Same, but use buffer {nr}. If the buffer argument is not
diff --git a/runtime/doc/spell.txt b/runtime/doc/spell.txt
index 110c6ef221..b88e26cdff 100644
--- a/runtime/doc/spell.txt
+++ b/runtime/doc/spell.txt
@@ -504,7 +504,7 @@ then Vim will try to guess.
Multiple {inname} arguments can be given to combine
regions into one Vim spell file. Example: >
- :mkspell ~/.vim/spell/en /tmp/en_US /tmp/en_CA /tmp/en_AU
+ :mkspell ~/.config/nvim/spell/en /tmp/en_US /tmp/en_CA /tmp/en_AU
< This combines the English word lists for US, CA and AU
into one en.spl file.
Up to eight regions can be combined. *E754* *E755*
@@ -888,9 +888,9 @@ when using "cp1250" on Unix.
*spell-LOW* *spell-UPP*
Three lines in the affix file are needed. Simplistic example:
- FOL áëñ ~
- LOW áëñ ~
- UPP ÁËÑ ~
+ FOL áëñ ~
+ LOW áëñ ~
+ UPP ÃËÑ ~
All three lines must have exactly the same number of characters.
@@ -905,9 +905,9 @@ The "UPP" line specifies the characters with upper-case. That is, a character
is upper-case where it's different from the character at the same position in
"FOL".
-An exception is made for the German sharp s ß. The upper-case version is
+An exception is made for the German sharp s ß. The upper-case version is
"SS". In the FOL/LOW/UPP lines it should be included, so that it's recognized
-as a word character, but use the ß character in all three.
+as a word character, but use the ß character in all three.
ASCII characters should be omitted, Vim always handles these in the same way.
When the encoding is UTF-8 no word characters need to be specified.
@@ -1377,7 +1377,7 @@ suggestions would spend most time trying all kind of weird compound words.
*spell-SYLLABLE*
The SYLLABLE item defines characters or character sequences that are used to
count the number of syllables in a word. Example:
- SYLLABLE aáeéiíoóöõuúüûy/aa/au/ea/ee/ei/ie/oa/oe/oo/ou/uu/ui ~
+ SYLLABLE aáeéiíoóöõuúüûy/aa/au/ea/ee/ei/ie/oa/oe/oo/ou/uu/ui ~
Before the first slash is the set of characters that are counted for one
syllable, also when repeated and mixed, until the next character that is not
@@ -1458,8 +1458,8 @@ alike. This is mostly used for a letter with different accents. This is used
to prefer suggestions with these letters substituted. Example:
MAP 2 ~
- MAP eéëêè ~
- MAP uüùúû ~
+ MAP eéëêè ~
+ MAP uüùúû ~
The first line specifies the number of MAP lines following. Vim ignores the
number, but the line must be there.
diff --git a/runtime/doc/starting.txt b/runtime/doc/starting.txt
index 7dbbb2d424..0ded6a9060 100644
--- a/runtime/doc/starting.txt
+++ b/runtime/doc/starting.txt
@@ -90,6 +90,7 @@ argument.
--clean Equivalent to "-u NONE -i NONE":
- Skips initializations from files and environment variables.
- No 'shada' file is read or written.
+ - Excludes user directories from 'runtimepath'
*--noplugin*
--noplugin Skip loading plugins. Resets the 'loadplugins' option.
@@ -184,12 +185,17 @@ argument.
the 'modifiable' and 'write' options can be set to enable
changes and writing.
- *-Z* *restricted-mode* *E145*
+ *-Z* *restricted-mode* *E145* *E981*
-Z Restricted mode. All commands that make use of an external
shell are disabled. This includes suspending with CTRL-Z,
- ":sh", filtering, the system() function, backtick expansion,
- delete(), rename(), mkdir(), writefile(), libcall(),
- jobstart(), etc.
+ ":sh", filtering, the system() function, backtick expansion
+ and libcall().
+ Also disallowed are delete(), rename(), mkdir(), jobstart(),
+ etc.
+ Interfaces, such as Python, Ruby and Lua, are also disabled,
+ since they could be used to execute shell commands.
+ Note that the user may still find a loophole to execute a
+ shell command, it has only been made difficult.
-e *-e* *-E*
-E Start Nvim in Ex mode |gQ|.
@@ -1270,7 +1276,7 @@ exactly four MessagePack objects:
Key Type Def Description ~
rt UInteger 0 Register type:
No Description ~
- 0 |characterwise-register|
+ 0 |charwise-register|
1 |linewise-register|
2 |blockwise-register|
rw UInteger 0 Register width. Only valid
diff --git a/runtime/doc/syntax.txt b/runtime/doc/syntax.txt
index 0a4257e2b2..7da886dabd 100644
--- a/runtime/doc/syntax.txt
+++ b/runtime/doc/syntax.txt
@@ -3522,6 +3522,24 @@ DEFINING CASE *:syn-case* *E390*
:sy[ntax] case
Show either "syntax case match" or "syntax case ignore" (translated).
+
+DEFINING FOLDLEVEL *:syn-foldlevel*
+
+:sy[ntax] foldlevel [start | minimum]
+ This defines how the foldlevel of a line is computed when using
+ foldmethod=syntax (see |fold-syntax| and |:syn-fold|):
+
+ start: Use level of item containing start of line.
+ minimum: Use lowest local-minimum level of items on line.
+
+ The default is 'start'. Use 'minimum' to search a line horizontally
+ for the lowest level contained on the line that is followed by a
+ higher level. This produces more natural folds when syntax items
+ may close and open horizontally within a line.
+
+:sy[ntax] foldlevel
+ Show either "syntax foldlevel start" or "syntax foldlevel minimum".
+
SPELL CHECKING *:syn-spell*
:sy[ntax] spell [toplevel | notoplevel | default]
@@ -3985,6 +4003,8 @@ This will make each {} block form one fold.
The fold will start on the line where the item starts, and end where the item
ends. If the start and end are within the same line, there is no fold.
The 'foldnestmax' option limits the nesting of syntax folds.
+See |:syn-foldlevel| to control how the foldlevel of a line is computed
+from its syntax items.
*:syn-contains* *E405* *E406* *E407* *E408* *E409*
@@ -4647,8 +4667,8 @@ in their own color.
":colorscheme" in a color scheme script.
To customize a colorscheme use another name, e.g.
- "~/.vim/colors/mine.vim", and use `:runtime` to load
- the original colorscheme: >
+ "~/.config/nvim/colors/mine.vim", and use `:runtime` to
+ load the original colorscheme: >
runtime colors/evening.vim
hi Statement ctermfg=Blue guifg=Blue
@@ -4720,18 +4740,19 @@ the same syntax file on all UIs.
*bold* *underline* *undercurl*
*inverse* *italic* *standout*
- *strikethrough*
+ *nocombine* *strikethrough*
cterm={attr-list} *attr-list* *highlight-cterm* *E418*
attr-list is a comma separated list (without spaces) of the
following items (in any order):
bold
underline
undercurl curly underline
+ strikethrough
reverse
inverse same as reverse
italic
standout
- strikethrough
+ nocombine override attributes instead of combining them
NONE no attributes used (used to reset it)
Note that "bold" can be used here and by using a bold font. They
@@ -4919,6 +4940,8 @@ Conceal placeholder characters substituted for concealed
text (see 'conceallevel')
*hl-Cursor*
Cursor character under the cursor
+lCursor the character under the cursor when |language-mapping|
+ is used (see 'guicursor')
*hl-CursorIM*
CursorIM like Cursor, but used when in IME mode |CursorIM|
*hl-CursorColumn*
diff --git a/runtime/doc/tabpage.txt b/runtime/doc/tabpage.txt
index d0a5678179..1407fdd968 100644
--- a/runtime/doc/tabpage.txt
+++ b/runtime/doc/tabpage.txt
@@ -190,6 +190,9 @@ gt *i_CTRL-<PageDown>* *i_<C-PageDown>*
{count}<C-PageDown>
{count}gt Go to tab page {count}. The first tab page has number one.
+CTRL-<Tab> *CTRL-<Tab>*
+CTRL-W g<Tab> *g<Tab>* *CTRL-W_g<Tab>*
+g<Tab> Go to previous (last accessed) tab page.
:tabp[revious] *:tabp* *:tabprevious* *gT* *:tabN*
:tabN[ext] *:tabNext* *CTRL-<PageUp>*
diff --git a/runtime/doc/tagsrch.txt b/runtime/doc/tagsrch.txt
index bb3134feb6..b011db3dd3 100644
--- a/runtime/doc/tagsrch.txt
+++ b/runtime/doc/tagsrch.txt
@@ -838,4 +838,70 @@ Common arguments for the commands above:
< For a ":djump", ":dsplit", ":dlist" and ":dsearch" command the pattern
is used as a literal string, not as a search pattern.
+==============================================================================
+7. Using 'tagfunc' *tag-function*
+
+It is possible to provide Vim with a function which will generate a list of
+tags used for commands like |:tag|, |:tselect| and Normal mode tag commands
+like |CTRL-]|.
+
+The function used for generating the taglist is specified by setting the
+'tagfunc' option. The function will be called with three arguments:
+ a:pattern The tag identifier used during the tag search.
+ a:flags List of flags to control the function behavior.
+ a:info Dict containing the following entries:
+ buf_ffname Full filename which can be used for priority.
+ user_data Custom data String, if stored in the tag
+ stack previously by tagfunc.
+
+Currently two flags may be passed to the tag function:
+ 'c' The function was invoked by a normal command being processed
+ (mnemonic: the tag function may use the context around the
+ cursor to perform a better job of generating the tag list.)
+ 'i' In Insert mode, the user was completing a tag (with
+ |i_CTRL-X_CTRL-]|).
+
+Note that when 'tagfunc' is set, the priority of the tags described in
+|tag-priority| does not apply. Instead, the priority is exactly as the
+ordering of the elements in the list returned by the function.
+ *E987*
+The function should return a List of Dict entries. Each Dict must at least
+include the following entries and each value must be a string:
+ 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. This
+ can be either an Ex search pattern or a line number.
+Note that the format is similar to that of |taglist()|, which makes it possible
+to use its output to generate the result.
+The following fields are optional:
+ kind Type of the tag.
+ user_data String of custom data stored in the tag stack which
+ can be used to disambiguate tags between operations.
+
+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*
+
+The following is a hypothetical example of a function used for 'tagfunc'. It
+uses the output of |taglist()| to generate the result: a list of tags in the
+inverse order of file names.
+>
+ function! TagFunc(pattern, flags, info)
+ function! CompareFilenames(item1, item2)
+ let f1 = a:item1['filename']
+ let f2 = a:item2['filename']
+ return f1 >=# f2 ?
+ \ -1 : f1 <=# f2 ? 1 : 0
+ endfunction
+
+ let result = taglist(a:pattern)
+ call sort(result, "CompareFilenames")
+
+ return result
+ endfunc
+ set tagfunc=TagFunc
+<
+
vim:tw=78:ts=8:noet:ft=help:norl:
diff --git a/runtime/doc/term.txt b/runtime/doc/term.txt
index 4f4d379f01..6a271c08d3 100644
--- a/runtime/doc/term.txt
+++ b/runtime/doc/term.txt
@@ -149,6 +149,8 @@ Nvim will adjust the shape of the cursor from a block to a line when in insert
mode (or as specified by the 'guicursor' option), on terminals that support
it. It uses the same |terminfo| extensions that were pioneered by tmux for
this: "Ss" and "Se".
+Similarly, if you set the cursor highlight group with blend=100, Nvim hides
+the cursor through the "cvvis" and "civis" extensions.
If your terminfo definition is missing them, then Nvim will decide whether to
add them to your terminfo definition, by looking at $TERM and other
diff --git a/runtime/doc/ui.txt b/runtime/doc/ui.txt
index a2f19593ae..2817c1015b 100644
--- a/runtime/doc/ui.txt
+++ b/runtime/doc/ui.txt
@@ -161,7 +161,9 @@ the editor.
`cursor_shape`: "block", "horizontal", "vertical"
`cell_percentage`: Cell % occupied by the cursor.
`blinkwait`, `blinkon`, `blinkoff`: See |cursor-blinking|.
- `attr_id`: Cursor attribute id (defined by `hl_attr_define`)
+ `attr_id`: Cursor attribute id (defined by `hl_attr_define`).
+ When attr_id is 0, the background and foreground
+ colors should be swapped.
`attr_id_lm`: Cursor attribute id for when 'langmap' is active.
`short_name`: Mode code name, see 'guicursor'.
`name`: Mode descriptive name.
@@ -201,8 +203,8 @@ the editor.
sent from Nvim, like for |ui-cmdline|.
["mode_change", mode, mode_idx]
- The mode changed. The first parameter `mode` is a string representing
- the current mode. `mode_idx` is an index into the array received in
+ Editor mode changed. The `mode` parameter is a string representing
+ the current mode. `mode_idx` is an index into the array emitted in
the `mode_info_set` event. UIs should change the cursor style
according to the properties specified in the corresponding item. The
set of modes reported will change in new versions of Nvim, for
@@ -211,11 +213,11 @@ the editor.
["mouse_on"]
["mouse_off"]
- Tells the client whether mouse support, as determined by |'mouse'|
- option, is considered to be active in the current mode. This is mostly
- useful for a terminal frontend, or other situations where Nvim mouse
- would conflict with other usages of the mouse. It is safe for a client
- to ignore this and always send mouse events.
+ |'mouse'| was enabled/disabled in the current editor mode. Useful for
+ a terminal UI, or other situations where Nvim mouse would conflict
+ with other usages of the mouse. UIs may ignore this and always send
+ mouse input, because 'mouse' decides the behavior of |nvim_input()|
+ implicitly.
["busy_start"]
["busy_stop"]
@@ -294,7 +296,8 @@ numerical highlight ids to the actual attributes.
`underline`: underlined text. The line has `special` color.
`undercurl`: undercurled text. The curl has `special` color.
`blend`: Blend level (0-100). Could be used by UIs to support
- blending floating windows to the background.
+ blending floating windows to the background or to
+ signal a transparent cursor.
For absent color keys the default color should be used. Don't store
the default value in the table, rather a sentinel value, so that
@@ -591,6 +594,12 @@ tabs.
When |ext_messages| is active, no message grid is used, and this event
will not be sent.
+["win_viewport", grid, win, topline, botline, curline, curcol]
+ 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.
+
==============================================================================
Popupmenu Events *ui-popupmenu*
diff --git a/runtime/doc/usr_21.txt b/runtime/doc/usr_21.txt
index 99a78e7b05..44d653a1a7 100644
--- a/runtime/doc/usr_21.txt
+++ b/runtime/doc/usr_21.txt
@@ -339,21 +339,6 @@ window, move the cursor to the filename and press "O". Double clicking with
the mouse will also do this.
-UNIX AND MS-WINDOWS
-
-Some people have to do work on MS-Windows systems one day and on Unix another
-day. If you are one of them, consider adding "slash" and "unix" to
-'sessionoptions'. The session files will then be written in a format that can
-be used on both systems. This is the command to put in your |init.vim| file:
->
- :set sessionoptions+=unix,slash
-
-Vim will use the Unix format then, because the MS-Windows Vim can read and
-write Unix files, but Unix Vim can't read MS-Windows format session files.
-Similarly, MS-Windows Vim understands file names with / to separate names, but
-Unix Vim doesn't understand \.
-
-
SESSIONS AND SHADA
Sessions store many things, but not the position of marks, contents of
diff --git a/runtime/doc/usr_25.txt b/runtime/doc/usr_25.txt
index 3a58af6412..2efb67e55f 100644
--- a/runtime/doc/usr_25.txt
+++ b/runtime/doc/usr_25.txt
@@ -346,12 +346,13 @@ scroll:
g0 to first visible character in this line
g^ to first non-blank visible character in this line
- gm to middle of this line
+ gm to middle of screen line
+ gM to middle of the text in this line
g$ to last visible character in this line
- |<-- window -->|
- some long text, part of which is visible ~
- g0 g^ gm g$
+ |<-- window -->|
+ some long text, part of which is visible in one line ~
+ g0 g^ gm gM g$
BREAKING AT WORDS *edit-no-break*
diff --git a/runtime/doc/usr_41.txt b/runtime/doc/usr_41.txt
index c770950a96..31da51bfbe 100644
--- a/runtime/doc/usr_41.txt
+++ b/runtime/doc/usr_41.txt
@@ -578,8 +578,10 @@ used for. You can find an alphabetical list here: |functions|. Use CTRL-] on
the function name to jump to detailed help on it.
String manipulation: *string-functions*
- nr2char() get a character by its ASCII value
- char2nr() get ASCII value of a character
+ nr2char() get a character by its number value
+ list2str() get a character string from a list of numbers
+ char2nr() get number value of a character
+ str2list() get list of numbers from a string
str2nr() convert a string to a Number
str2float() convert a string to a Float
printf() format a string according to % items
@@ -607,6 +609,7 @@ String manipulation: *string-functions*
strcharpart() get part of a string using char index
strgetchar() get character from a string using char index
expand() expand special keywords
+ expandcmd() expand a command like done for `:edit`
iconv() convert text from one encoding to another
byteidx() byte index of a character in a string
byteidxcomp() like byteidx() but count composing characters
@@ -641,6 +644,7 @@ List manipulation: *list-functions*
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
Dictionary manipulation: *dict-functions*
get() get an entry without an error for a wrong key
diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt
index 45a94bb961..24b562543e 100644
--- a/runtime/doc/vim_diff.txt
+++ b/runtime/doc/vim_diff.txt
@@ -38,7 +38,7 @@ the differences.
- 'directory' defaults to ~/.local/share/nvim/swap// (|xdg|), auto-created
- 'display' defaults to "lastline,msgsep"
- 'encoding' is UTF-8 (cf. 'fileencoding' for file-content encoding)
-- 'fillchars' defaults (in effect) to "vert:│,fold:·"
+- 'fillchars' defaults (in effect) to "vert:│,fold:·,sep:│"
- 'formatoptions' defaults to "tcqj"
- 'fsync' is disabled
- 'history' defaults to 10000 (the maximum)
@@ -55,6 +55,7 @@ the differences.
- 'showcmd' is enabled
- 'sidescroll' defaults to 1
- 'smarttab' is enabled
+- 'startofline' is disabled
- 'tabpagemax' defaults to 50
- 'tags' defaults to "./tags;,tags"
- 'ttimeoutlen' defaults to 50
@@ -115,6 +116,10 @@ backwards-compatibility cost. Some examples:
- Directories for 'directory' and 'undodir' are auto-created.
- Terminal features such as 'guicursor' are enabled where possible.
+Some features are built in that otherwise required external plugins:
+
+- Highlighting the yanked region, see |lua-highlight|.
+
ARCHITECTURE ~
External plugins run in separate processes. |remote-plugin| This improves
@@ -158,6 +163,7 @@ Events:
|UILeave|
|VimResume|
|VimSuspend|
+ |WinClosed|
Functions:
|dictwatcheradd()| notifies a callback whenever a |Dict| is modified
@@ -168,6 +174,7 @@ Functions:
|system()|, |systemlist()| can run {cmd} directly (without 'shell')
Highlight groups:
+ |highlight-blend| controls blend level for a highlight group
|expr-highlight| highlight groups (prefixed with "Nvim")
|hl-NormalFloat| highlights floating window
|hl-NormalNC| highlights non-current windows
@@ -193,19 +200,21 @@ Normal commands:
"Outline": Type |gO| in |:Man| and |:help| pages to see a document outline.
Options:
- 'cpoptions' flags: |cpo-_|
- 'display' flag `msgsep` to minimize scrolling when showing messages
- 'guicursor' works in the terminal
- 'fillchars' local to window. flags: `msgsep` (see 'display' above) and `eob`
- for |hl-EndOfBuffer| marker
- 'inccommand' shows interactive results for |:substitute|-like commands
- 'listchars' local to window
- 'pumblend' pseudo-transparent popupmenu
+ 'cpoptions' flags: |cpo-_|
+ 'display' flags: "msgsep" minimizes scrolling when showing messages
+ 'guicursor' works in the terminal
+ 'fillchars' flags: "msgsep" (see 'display'), "eob" for |hl-EndOfBuffer|
+ marker, "foldopen", "foldsep", "foldclose"
+ 'foldcolumn' supports up to 9 dynamic/fixed columns
+ 'inccommand' shows interactive results for |:substitute|-like commands
+ 'listchars' local to window
+ 'pumblend' pseudo-transparent popupmenu
'scrollback'
- 'signcolumn' supports up to 9 dynamic/fixed columns
- 'statusline' supports unlimited alignment sections
- 'tabline' %@Func@foo%X can call any function on mouse-click
- 'wildoptions' `pum` flag to use popupmenu for wildmode completion
+ 'signcolumn' supports up to 9 dynamic/fixed columns
+ 'statusline' supports unlimited alignment sections
+ 'tabline' %@Func@foo%X can call any function on mouse-click
+ 'wildoptions' "pum" flag to use popupmenu for wildmode completion
+ 'winblend' pseudo-transparency in floating windows |api-floatwin|
'winhighlight' window-local highlights
Signs:
@@ -267,11 +276,6 @@ are always available and may be used simultaneously. See |provider-python|.
|json_encode()| behaviour slightly changed: now |msgpack-special-dict| values
are accepted, but |v:none| is not.
-*v:none* variable is absent. In Vim it represents “no value†in “js†strings
-like "[,]" parsed as "[v:none]" by |js_decode()|.
-
-*js_encode()* and *js_decode()* functions are also absent.
-
Viminfo text files were replaced with binary (messagepack) ShaDa files.
Additional differences:
@@ -296,7 +300,7 @@ coerced to strings. See |id()| for more details, currently it uses
|c_CTRL-R| pasting a non-special register into |cmdline| omits the last <CR>.
-Lua interface (|if_lua.txt|):
+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
@@ -307,15 +311,15 @@ Lua interface (|if_lua.txt|):
- Lua package.path and package.cpath are automatically updated according to
'runtimepath': |lua-require|.
-|input()| and |inputdialog()| support for each other’s features (return on
-cancel and completion respectively) via dictionary argument (replaces all
-other arguments if used).
-
-|input()| and |inputdialog()| support user-defined cmdline highlighting.
-
Commands:
|:doautocmd| does not warn about "No matching autocommands".
+Functions:
+ |input()| and |inputdialog()| support for each other’s features (return on
+ cancel and completion respectively) via dictionary argument (replaces all
+ other arguments if used).
+ |input()| and |inputdialog()| support user-defined cmdline highlighting.
+
Highlight groups:
|hl-ColorColumn|, |hl-CursorColumn| are lower priority than most other
groups
@@ -338,6 +342,7 @@ Normal commands:
Options:
'ttimeout', 'ttimeoutlen' behavior was simplified
+ |jumpoptions| "stack" behavior
Shell:
Shell output (|:!|, |:make|, …) is always routed through the UI, so it
@@ -399,10 +404,10 @@ VimL (Vim script) compatibility:
Some legacy Vim features are not implemented:
-- |if_py|: *python-bindeval* *python-Function* are not supported
-- |if_lua|: the `vim` object is missing some legacy methods
-- *if_perl*
+- |if_lua|: Nvim Lua API is not compatible with Vim's "if_lua"
- *if_mzscheme*
+- *if_perl*
+- |if_py|: *python-bindeval* *python-Function* are not supported
- *if_tcl*
==============================================================================
@@ -439,6 +444,11 @@ Compile-time features:
Emacs tags support
X11 integration (see |x11-selection|)
+Eval:
+ *js_encode()*
+ *js_decode()*
+ *v:none* (used by Vim to represent JavaScript "undefined"); use |v:null| instead.
+
Highlight groups:
*hl-StatusLineTerm* *hl-StatusLineTermNC* are unnecessary because Nvim
supports 'winhighlight' window-local highlights.
@@ -524,4 +534,4 @@ TUI:
always uses 7-bit control sequences.
==============================================================================
- vim:tw=78:ts=8:sw=2:noet:ft=help:norl:
+ vim:tw=78:ts=8:sw=2:et:ft=help:norl:
diff --git a/runtime/doc/visual.txt b/runtime/doc/visual.txt
index ccbbc092ec..0052382044 100644
--- a/runtime/doc/visual.txt
+++ b/runtime/doc/visual.txt
@@ -48,7 +48,7 @@ position.
==============================================================================
2. Starting and stopping Visual mode *visual-start*
- *v* *characterwise-visual*
+ *v* *charwise-visual*
[count]v Start Visual mode per character.
With [count] select the same number of characters or
lines as used for the last Visual operation, but at
@@ -74,7 +74,7 @@ position.
If you use <Esc>, click the left mouse button or use any command that
does a jump to another buffer while in Visual mode, the highlighting stops
-and no text is affected. Also when you hit "v" in characterwise Visual mode,
+and no text is affected. Also when you hit "v" in charwise Visual mode,
"CTRL-V" in blockwise Visual mode or "V" in linewise Visual mode. If you hit
CTRL-Z the highlighting stops and the editor is suspended or a new shell is
started |CTRL-Z|.
@@ -477,7 +477,7 @@ Commands in Select mode:
Otherwise, typed characters are handled as in Visual mode.
When using an operator in Select mode, and the selection is linewise, the
-selected lines are operated upon, but like in characterwise selection. For
+selected lines are operated upon, but like in charwise selection. For
example, when a whole line is deleted, it can later be pasted in the middle of
a line.
@@ -510,7 +510,7 @@ gV Avoid the automatic reselection of the Visual area
selection.
*gh*
-gh Start Select mode, characterwise. This is like "v",
+gh Start Select mode, charwise. This is like "v",
but starts Select mode instead of Visual mode.
Mnemonic: "get highlighted".
diff --git a/runtime/doc/windows.txt b/runtime/doc/windows.txt
index 76bb096ee3..b5623f4ea4 100644
--- a/runtime/doc/windows.txt
+++ b/runtime/doc/windows.txt
@@ -201,9 +201,11 @@ CTRL-W CTRL_N *CTRL-W_CTRL-N*
|:find|. Doesn't split if {file} is not found.
CTRL-W CTRL-^ *CTRL-W_CTRL-^* *CTRL-W_^*
-CTRL-W ^ Does ":split #", split window in two and edit alternate file.
- When a count is given, it becomes ":split #N", split window
- and edit buffer N.
+CTRL-W ^ Split the current window in two and edit the alternate file.
+ When a count N is given, split the current window and edit
+ buffer N. Similar to ":sp #" and ":sp #N", but it allows the
+ other buffer to be unnamed. This command matches the behavior
+ of |CTRL-^|, except that it splits a window first.
CTRL-W ge *CTRL-W_ge*
Detach the current window as an external window.
@@ -1019,6 +1021,9 @@ list of buffers. |unlisted-buffer|
x buffers with a read error
% current buffer
# alternate buffer
+ R terminal buffers with a running job
+ F terminal buffers with a finished job
+ t show time last used and sort buffers
Combining flags means they are "and"ed together, e.g.:
h+ hidden buffers which are modified
a+ active buffers which are modified
diff --git a/runtime/filetype.vim b/runtime/filetype.vim
index 6abf9da55f..c0d656107c 100644
--- a/runtime/filetype.vim
+++ b/runtime/filetype.vim
@@ -1,7 +1,7 @@
" Vim support file to detect file types
"
" Maintainer: Bram Moolenaar <Bram@vim.org>
-" Last Change: 2019 Aug 26
+" Last Change: 2020 Apr 29
" Listen very carefully, I will say this only once
if exists("did_load_filetypes")
@@ -84,6 +84,9 @@ au BufNewFile,BufRead *.gpr setf ada
" AHDL
au BufNewFile,BufRead *.tdf setf ahdl
+" AIDL
+au BufNewFile,BufRead *.aidl setf aidl
+
" AMPL
au BufNewFile,BufRead *.run setf ampl
@@ -229,11 +232,14 @@ au BufNewFile,BufRead *.bl setf blank
" Blkid cache file
au BufNewFile,BufRead */etc/blkid.tab,*/etc/blkid.tab.old setf xml
+" BSDL
+au BufNewFile,BufRead *bsd,*.bsdl setf bsdl
+
" Bazel (http://bazel.io)
autocmd BufRead,BufNewFile *.bzl,WORKSPACE,BUILD.bazel setf bzl
if has("fname_case")
" There is another check for BUILD further below.
- autocmd BufRead,BufNewFile BUILD setf bzl
+ autocmd BufRead,BufNewFile BUILD setf bzl
endif
" C or lpc
@@ -421,6 +427,9 @@ au BufNewFile,BufRead *.csp,*.fdr setf csp
au BufNewFile,BufRead *.pld setf cupl
au BufNewFile,BufRead *.si setf cuplsim
+" Dart
+au BufRead,BufNewfile *.dart,*.drt setf dart
+
" Debian Control
au BufNewFile,BufRead */debian/control setf debcontrol
au BufNewFile,BufRead control
@@ -452,7 +461,7 @@ au BufNewFile,BufRead *.desc setf desc
au BufNewFile,BufRead *.d call dist#ft#DtraceCheck()
" Desktop files
-au BufNewFile,BufRead *.desktop,.directory setf desktop
+au BufNewFile,BufRead *.desktop,*.directory setf desktop
" Dict config
au BufNewFile,BufRead dict.conf,.dictrc setf dictconf
@@ -484,7 +493,7 @@ au BufNewFile,BufRead *.rul
au BufNewFile,BufRead *.com call dist#ft#BindzoneCheck('dcl')
" DOT
-au BufNewFile,BufRead *.dot setf dot
+au BufNewFile,BufRead *.dot,*.gv setf dot
" Dylan - lid files
au BufNewFile,BufRead *.lid setf dylanlid
@@ -532,11 +541,14 @@ au BufNewFile,BufRead *.ecd setf ecd
au BufNewFile,BufRead *.e,*.E call dist#ft#FTe()
" Elinks configuration
-au BufNewFile,BufRead */etc/elinks.conf,*/.elinks/elinks.conf setf elinks
+au BufNewFile,BufRead elinks.conf setf elinks
" ERicsson LANGuage; Yaws is erlang too
au BufNewFile,BufRead *.erl,*.hrl,*.yaws setf erlang
+" Elm
+au BufNewFile,BufRead *.elm setf elm
+
" Elm Filter Rules file
au BufNewFile,BufRead filter-rules setf elmfilt
@@ -793,8 +805,8 @@ au BufNewFile,BufRead *.java,*.jav setf java
" JavaCC
au BufNewFile,BufRead *.jj,*.jjt setf javacc
-" JavaScript, ECMAScript
-au BufNewFile,BufRead *.js,*.javascript,*.es,*.mjs setf javascript
+" JavaScript, ECMAScript, ES module script, CommonJS script
+au BufNewFile,BufRead *.js,*.javascript,*.es,*.mjs,*.cjs setf javascript
" JavaScript with React
au BufNewFile,BufRead *.jsx setf javascriptreact
@@ -826,6 +838,9 @@ au BufNewFile,BufRead *.k setf kwt
" Kivy
au BufNewFile,BufRead *.kv setf kivy
+" Kotlin
+au BufNewFile,BufRead *.kt,*.ktm,*.kts setf kotlin
+
" KDE script
au BufNewFile,BufRead *.ks setf kscript
@@ -871,11 +886,12 @@ au BufNewFile,BufRead *.ll setf lifelines
" Lilo: Linux loader
au BufNewFile,BufRead lilo.conf setf lilo
-" Lisp (*.el = ELisp, *.cl = Common Lisp, *.jl = librep Lisp)
+" Lisp (*.el = ELisp, *.cl = Common Lisp)
+" *.jl was removed, it's also used for Julia, better skip than guess wrong.
if has("fname_case")
- au BufNewFile,BufRead *.lsp,*.lisp,*.el,*.cl,*.jl,*.L,.emacs,.sawfishrc setf lisp
+ au BufNewFile,BufRead *.lsp,*.lisp,*.el,*.cl,*.L,.emacs,.sawfishrc setf lisp
else
- au BufNewFile,BufRead *.lsp,*.lisp,*.el,*.cl,*.jl,.emacs,.sawfishrc setf lisp
+ au BufNewFile,BufRead *.lsp,*.lisp,*.el,*.cl,.emacs,.sawfishrc setf lisp
endif
" SBCL implementation of Common Lisp
@@ -975,6 +991,9 @@ au BufNewFile,BufRead hg-editor-*.txt setf hgcommit
" Mercurial config (looks like generic config file)
au BufNewFile,BufRead *.hgrc,*hgrc setf cfg
+" Meson Build system config
+au BufNewFile,BufRead meson.build,meson_options.txt setf meson
+
" Messages (logs mostly)
au BufNewFile,BufRead */log/{auth,cron,daemon,debug,kern,lpr,mail,messages,news/news,syslog,user}{,.log,.err,.info,.warn,.crit,.notice}{,.[0-9]*,-[0-9]*} setf messages
@@ -1114,8 +1133,20 @@ au BufNewFile,BufRead *.ora setf ora
" Packet filter conf
au BufNewFile,BufRead pf.conf setf pf
+" Pacman Config (close enough to dosini)
+au BufNewFile,BufRead */etc/pacman.conf setf dosini
+
+" Pacman hooks
+au BufNewFile,BufRead *.hook
+ \ if getline(1) == '[Trigger]' |
+ \ setf dosini |
+ \ endif
+
" Pam conf
-au BufNewFile,BufRead */etc/pam.conf setf pamconf
+au BufNewFile,BufRead */etc/pam.conf setf pamconf
+
+" Pam environment
+au BufNewFile,BufRead pam_env.conf,.pam_environment setf pamenv
" PApp
au BufNewFile,BufRead *.papp,*.pxml,*.pxsl setf papp
@@ -1143,6 +1174,7 @@ else
endif
au BufNewFile,BufRead *.plx,*.al,*.psgi setf perl
au BufNewFile,BufRead *.p6,*.pm6,*.pl6 setf perl6
+au BufNewFile,BufRead *.raku,*.rakumod setf perl6
" Perl, XPM or XPM2
au BufNewFile,BufRead *.pm
@@ -1272,7 +1304,8 @@ au BufNewFile,BufRead *.pyx,*.pxd setf pyrex
" Python, Python Shell Startup and Python Stub Files
" Quixote (Python-based web framework)
-au BufNewFile,BufRead *.py,*.pyw,.pythonstartup,.pythonrc,*.ptl,*.pyi setf python
+au BufNewFile,BufRead *.py,*.pyw,.pythonstartup,.pythonrc setf python
+au BufNewFile,BufRead *.ptl,*.pyi,SConstruct setf python
" Radiance
au BufNewFile,BufRead *.rad,*.mat setf radiance
@@ -1293,6 +1326,9 @@ au BufNewFile,BufRead *.reg
" Renderman Interface Bytestream
au BufNewFile,BufRead *.rib setf rib
+" Rego Policy Language
+au BufNewFile,BufRead *.rego setf rego
+
" Rexx
au BufNewFile,BufRead *.rex,*.orx,*.rxo,*.rxj,*.jrexx,*.rexxj,*.rexx,*.testGroup,*.testUnit setf rexx
@@ -1591,10 +1627,12 @@ au BufNewFile,BufRead *.sqlj setf sqlj
au BufNewFile,BufRead *.sqr,*.sqi setf sqr
" OpenSSH configuration
-au BufNewFile,BufRead ssh_config,*/.ssh/config setf sshconfig
+au BufNewFile,BufRead ssh_config,*/.ssh/config setf sshconfig
+au BufNewFile,BufRead */etc/ssh/ssh_config.d/*.conf setf sshconfig
" OpenSSH server configuration
-au BufNewFile,BufRead sshd_config setf sshdconfig
+au BufNewFile,BufRead sshd_config setf sshdconfig
+au BufNewFile,BufRead */etc/ssh/sshd_config.d/*.conf setf sshdconfig
" Stata
au BufNewFile,BufRead *.ado,*.do,*.imata,*.mata setf stata
@@ -1614,15 +1652,27 @@ au BufNewFile,BufRead *.sml setf sml
" Sratus VOS command macro
au BufNewFile,BufRead *.cm setf voscm
+" Swift
+au BufNewFile,BufRead *.swift setf swift
+au BufNewFile,BufRead *.swift.gyb setf swiftgyb
+
+" Swift Intermediate Language
+au BufNewFile,BufRead *.sil setf sil
+
" Sysctl
au BufNewFile,BufRead */etc/sysctl.conf,*/etc/sysctl.d/*.conf setf sysctl
" Systemd unit files
-au BufNewFile,BufRead */systemd/*.{automount,mount,path,service,socket,swap,target,timer} setf systemd
+au BufNewFile,BufRead */systemd/*.{automount,dnssd,link,mount,netdev,network,nspawn,path,service,slice,socket,swap,target,timer} setf systemd
" Systemd overrides
-au BufNewFile,BufRead /etc/systemd/system/*.d/*.conf setf systemd
+au BufNewFile,BufRead */etc/systemd/*.conf.d/*.conf setf systemd
+au BufNewFile,BufRead */etc/systemd/system/*.d/*.conf setf systemd
+au BufNewFile,BufRead */.config/systemd/user/*.d/*.conf setf systemd
" Systemd temp files
-au BufNewFile,BufRead /etc/systemd/system/*.d/.#* setf systemd
+au BufNewFile,BufRead */etc/systemd/system/*.d/.#* setf systemd
+au BufNewFile,BufRead */etc/systemd/system/.#* setf systemd
+au BufNewFile,BufRead */.config/systemd/user/*.d/.#* setf systemd
+au BufNewFile,BufRead */.config/systemd/user/.#* setf systemd
" Synopsys Design Constraints
au BufNewFile,BufRead *.sdc setf sdc
@@ -1753,7 +1803,7 @@ au BufNewFile,BufRead *.va,*.vams setf verilogams
au BufNewFile,BufRead *.sv,*.svh setf systemverilog
" VHDL
-au BufNewFile,BufRead *.hdl,*.vhd,*.vhdl,*.vbe,*.vst setf vhdl
+au BufNewFile,BufRead *.hdl,*.vhd,*.vhdl,*.vbe,*.vst,*.vho setf vhdl
" Vim script
au BufNewFile,BufRead *.vim,*.vba,.exrc,_exrc setf vim
diff --git a/runtime/ftplugin/man.vim b/runtime/ftplugin/man.vim
index 6c7f095f62..0416e41368 100644
--- a/runtime/ftplugin/man.vim
+++ b/runtime/ftplugin/man.vim
@@ -1,4 +1,4 @@
-" Maintainer: Anmol Sethi <anmol@aubble.com>
+" Maintainer: Anmol Sethi <hi@nhooyr.io>
" Previous Maintainer: SungHyun Nam <goweol@gmail.com>
if exists('b:did_ftplugin') || &filetype !=# 'man'
@@ -6,7 +6,7 @@ if exists('b:did_ftplugin') || &filetype !=# 'man'
endif
let b:did_ftplugin = 1
-let s:pager = get(s:, 'pager', 0) || !exists('b:man_sect')
+let s:pager = !exists('b:man_sect')
if s:pager
call man#init_pager()
@@ -20,14 +20,13 @@ setlocal wrap breakindent linebreak
setlocal nonumber norelativenumber
setlocal foldcolumn=0 colorcolumn=0 nolist nofoldenable
+setlocal tagfunc=man#goto_tag
+
if !exists('g:no_plugin_maps') && !exists('g:no_man_maps')
nnoremap <silent> <buffer> j gj
nnoremap <silent> <buffer> k gk
nnoremap <silent> <buffer> gO :call man#show_toc()<CR>
- nnoremap <silent> <buffer> <C-]> :Man<CR>
- nnoremap <silent> <buffer> K :Man<CR>
- nnoremap <silent> <buffer> <C-T> :call man#pop_tag()<CR>
- if 1 == bufnr('%') || s:pager
+ if s:pager
nnoremap <silent> <buffer> <nowait> q :lclose<CR>:q<CR>
else
nnoremap <silent> <buffer> <nowait> q :lclose<CR><C-W>c
diff --git a/runtime/indent/Makefile b/runtime/indent/Makefile
new file mode 100644
index 0000000000..d192605527
--- /dev/null
+++ b/runtime/indent/Makefile
@@ -0,0 +1,14 @@
+# Portable Makefile for running indent tests.
+
+VIM = vim
+VIMRUNTIME = ..
+
+# Run the tests that didn't run yet or failed previously.
+# If a test succeeds a testdir/*.out file will be written.
+# If a test fails a testdir/*.fail file will be written.
+test:
+ VIMRUNTIME=$(VIMRUNTIME) $(VIM) --clean --not-a-term -u testdir/runtest.vim
+
+
+clean:
+ rm -f testdir/*.fail testdir/*.out
diff --git a/runtime/indent/README.txt b/runtime/indent/README.txt
index a424b4f8c0..8b114365c6 100644
--- a/runtime/indent/README.txt
+++ b/runtime/indent/README.txt
@@ -43,3 +43,5 @@ running. Add a test if the function exists and use ":finish", like this:
The user may have several options set unlike you, try to write the file such
that it works with any option settings. Also be aware of certain features not
being compiled in.
+
+To test the indent file, see testdir/README.txt.
diff --git a/runtime/indent/testdir/README.txt b/runtime/indent/testdir/README.txt
new file mode 100644
index 0000000000..65975605c2
--- /dev/null
+++ b/runtime/indent/testdir/README.txt
@@ -0,0 +1,97 @@
+TESTING INDENT SCRIPTS
+
+We'll use FILETYPE for the filetype name here.
+
+
+FORMAT OF THE FILETYPE.IN FILE
+
+First of all, create a FILETYPE.in file. It should contain:
+
+- A modeline setting the 'filetype' and any other option values.
+ This must work like a comment for FILETYPE. E.g. for vim:
+ " vim: set ft=vim sw=4 :
+
+- At least one block of lines to indent, prefixed with START_INDENT and
+ followed by END_INDENT. These lines must also look like a comment for your
+ FILETYPE. You would normally leave out all indent, so that the effect of
+ the indent command results in adding indent. Example:
+
+ " START_INDENT
+ func Some()
+ let x = 1
+ endfunc
+ " END_INDENT
+
+ If you just want to test normal indenting with default options, you can make
+ this a large number of lines. Just add all kinds of language constructs,
+ nested statements, etc. with valid syntax.
+
+- Optionally, add lines with INDENT_EXE after START_INDENT, followed by a Vim
+ command. This will be executed before indenting the lines. Example:
+
+ " START_INDENT
+ " INDENT_EXE let g:vim_indent_cont = 6
+ let cmd =
+ \ 'some '
+ \ 'string'
+ " END_INDENT
+
+ Note that the command is not undone, you may need to reverse the effect for
+ the next block of lines.
+
+- Alternatively to indenting all the lines between START_INDENT and
+ END_INDENT, use an INDENT_AT line, which specifies a pattern to find the
+ line to indent. Example:
+
+ " START_INDENT
+ " INDENT_AT this-line
+ func Some()
+ let f = x " this-line
+ endfunc
+ " END_INDENT
+
+ Alternatively you can use INDENT_NEXT to indent the line below the matching
+ pattern. Keep in mind that quite often it will indent relative to the
+ matching line:
+
+ " START_INDENT
+ " INDENT_NEXT next-line
+ func Some()
+ " next-line
+ let f = x
+ endfunc
+ " END_INDENT
+
+ Or use INDENT_PREV to indent the line above the matching pattern:
+
+ " START_INDENT
+ " INDENT_PREV prev-line
+ func Some()
+ let f = x
+ " prev-line
+ endfunc
+ " END_INDENT
+
+It's best to keep the whole file valid for FILETYPE, so that syntax
+highlighting works normally, and any indenting that depends on the syntax
+highlighting also works.
+
+
+RUNNING THE TEST
+
+Before running the test, create a FILETYPE.ok file. You can leave it empty at
+first.
+
+Now run "make test" from the parent directory. After Vim has done the
+indenting you will see a FILETYPE.fail file. This contains the actual result
+of indenting, and it's different from the FILETYPE.ok file.
+
+Check the contents of the FILETYPE.fail file. If it is perfectly OK, then
+rename it to overwrite the FILETYPE.ok file. If you now run "make test" again,
+the test will pass and create a FILETYPE.out file, which is identical to the
+FILETYPE.ok file. The FILETYPE.fail file will be deleted.
+
+If you try to run "make test" again you will notice that nothing happens,
+because the FILETYPE.out file already exists. Delete it, or do "make clean",
+so that the text runs again. If you edit the FILETYPE.in file, so that it's
+newer than the FILETYPE.out file, the test will also run.
diff --git a/runtime/indent/testdir/runtest.vim b/runtime/indent/testdir/runtest.vim
new file mode 100644
index 0000000000..945c2753e9
--- /dev/null
+++ b/runtime/indent/testdir/runtest.vim
@@ -0,0 +1,133 @@
+" Runs all the indent tests for which there is no .out file.
+"
+" Current directory must be runtime/indent.
+
+" Only do this with the +eval feature
+if 1
+
+set nocp
+filetype indent on
+syn on
+set nowrapscan
+set report=9999
+set modeline
+
+au! SwapExists * call HandleSwapExists()
+func HandleSwapExists()
+ " Ignore finding a swap file for the test input and output, the user might be
+ " editing them and that's OK.
+ if expand('<afile>') =~ '.*\.\(in\|out\|fail\|ok\)'
+ let v:swapchoice = 'e'
+ endif
+endfunc
+
+let failed_count = 0
+for fname in glob('testdir/*.in', 1, 1)
+ let root = substitute(fname, '\.in', '', '')
+
+ " Execute the test if the .out file does not exist of when the .in file is
+ " newer.
+ let in_time = getftime(fname)
+ let out_time = getftime(root . '.out')
+ if out_time < 0 || in_time > out_time
+ call delete(root . '.fail')
+ call delete(root . '.out')
+
+ set sw& ts& filetype=
+ exe 'split ' . fname
+
+ let did_some = 0
+ let failed = 0
+ let end = 1
+ while 1
+ " Indent all the lines between "START_INDENT" and "END_INDENT"
+ exe end
+ let start = search('\<START_INDENT\>')
+ let end = search('\<END_INDENT\>')
+ if start <= 0 || end <= 0 || end <= start
+ if did_some == 0
+ call append(0, 'ERROR: START_INDENT and/or END_INDENT not found')
+ let failed = 1
+ endif
+ break
+ else
+ let did_some = 1
+
+ " Execute all commands marked with INDENT_EXE and find any pattern.
+ let lnum = start
+ let pattern = ''
+ let at = ''
+ while 1
+ exe lnum + 1
+ let lnum_exe = search('\<INDENT_EXE\>')
+ exe lnum + 1
+ let indent_at = search('\<INDENT_\(AT\|NEXT\|PREV\)\>')
+ if lnum_exe > 0 && lnum_exe < end && (indent_at <= 0 || lnum_exe < indent_at)
+ exe substitute(getline(lnum_exe), '.*INDENT_EXE', '', '')
+ let lnum = lnum_exe
+ let start = lnum
+ elseif indent_at > 0 && indent_at < end
+ if pattern != ''
+ call append(indent_at, 'ERROR: duplicate pattern')
+ let failed = 1
+ break
+ endif
+ let text = getline(indent_at)
+ let pattern = substitute(text, '.*INDENT_\S*\s*', '', '')
+ let at = substitute(text, '.*INDENT_\(\S*\).*', '\1', '')
+ let lnum = indent_at
+ let start = lnum
+ else
+ break
+ endif
+ endwhile
+
+ exe start + 1
+ if pattern == ''
+ exe 'normal =' . (end - 1) . 'G'
+ else
+ let lnum = search(pattern)
+ if lnum <= 0
+ call append(indent_at, 'ERROR: pattern not found: ' . pattern)
+ let failed = 1
+ break
+ endif
+ if at == 'AT'
+ exe lnum
+ elseif at == 'NEXT'
+ exe lnum + 1
+ else
+ exe lnum - 1
+ endif
+ normal ==
+ endif
+ endif
+ endwhile
+
+ if !failed
+ " Check the resulting text equals the .ok file.
+ if getline(1, '$') != readfile(root . '.ok')
+ let failed = 1
+ endif
+ endif
+
+ if failed
+ let failed_count += 1
+ exe 'write ' . root . '.fail'
+ echoerr 'Test ' . fname . ' FAILED!'
+ else
+ exe 'write ' . root . '.out'
+ endif
+
+ quit! " close the indented file
+ endif
+endfor
+
+" Matching "if 1" at the start.
+endif
+
+if failed_count > 0
+ " have make report an error
+ cquit
+endif
+qall!
diff --git a/runtime/indent/testdir/vim.in b/runtime/indent/testdir/vim.in
new file mode 100644
index 0000000000..ca105f6eda
--- /dev/null
+++ b/runtime/indent/testdir/vim.in
@@ -0,0 +1,46 @@
+" vim: set ft=vim sw=4 :
+
+" START_INDENT
+
+func Some()
+let x = 1
+endfunc
+
+let cmd =
+\ 'some '
+\ 'string'
+
+" END_INDENT
+
+" START_INDENT
+" INDENT_EXE let g:vim_indent_cont = 6
+
+let cmd =
+\ 'some '
+\ 'string'
+
+" END_INDENT
+
+" START_INDENT
+" INDENT_EXE unlet g:vim_indent_cont
+" INDENT_AT this-line
+func Some()
+let f = x " this-line
+endfunc
+" END_INDENT
+
+" START_INDENT
+" INDENT_NEXT next-line
+func Some()
+ " next-line
+let f = x
+endfunc
+" END_INDENT
+
+" START_INDENT
+" INDENT_PREV prev-line
+func Some()
+let f = x
+" prev-line
+endfunc
+" END_INDENT
diff --git a/runtime/indent/testdir/vim.ok b/runtime/indent/testdir/vim.ok
new file mode 100644
index 0000000000..542861ec9c
--- /dev/null
+++ b/runtime/indent/testdir/vim.ok
@@ -0,0 +1,46 @@
+" vim: set ft=vim sw=4 :
+
+" START_INDENT
+
+func Some()
+ let x = 1
+endfunc
+
+let cmd =
+ \ 'some '
+ \ 'string'
+
+" END_INDENT
+
+" START_INDENT
+" INDENT_EXE let g:vim_indent_cont = 6
+
+let cmd =
+ \ 'some '
+ \ 'string'
+
+" END_INDENT
+
+" START_INDENT
+" INDENT_EXE unlet g:vim_indent_cont
+" INDENT_AT this-line
+func Some()
+ let f = x " this-line
+endfunc
+" END_INDENT
+
+" START_INDENT
+" INDENT_NEXT next-line
+func Some()
+ " next-line
+ let f = x
+endfunc
+" END_INDENT
+
+" START_INDENT
+" INDENT_PREV prev-line
+func Some()
+ let f = x
+" prev-line
+endfunc
+" END_INDENT
diff --git a/runtime/indent/tex.vim b/runtime/indent/tex.vim
index a748cfbb40..8a44ade1ac 100644
--- a/runtime/indent/tex.vim
+++ b/runtime/indent/tex.vim
@@ -64,14 +64,17 @@
" style) is supported. Thanks Miles Wheeler for reporting.
" 2018/02/07 by Yichao Zhou <broken.zhou AT gmail.com>
" (*) Make indentation more smart in the normal mode
+" 2020/04/26 by Yichao Zhou <broken.zhou AT gmail.com>
+" (*) Fix a bug related to \[ & \]. Thanks Manuel Boni for
+" reporting.
"
" }}}
" Document: {{{
"
-" To set the following options (ok, currently it's just one), add a line like
-" let g:tex_indent_items = 1
-" to your ~/.vimrc.
+" For proper latex experience, please put
+" let g:tex_flavor = "latex"
+" into your vimrc.
"
" * g:tex_indent_brace
"
@@ -184,13 +187,18 @@ function! GetTeXIndent() " {{{
let line = substitute(getline(lnum), '\s*%.*', '','g') " last line
let cline = substitute(getline(v:lnum), '\s*%.*', '', 'g') " current line
+ let ccol = 1
+ while cline[ccol] =~ '\s'
+ let ccol += 1
+ endwhile
+
" We are in verbatim, so do what our user what.
- if synIDattr(synID(v:lnum, indent(v:lnum), 1), "name") == "texZone"
+ if synIDattr(synID(v:lnum, ccol, 1), "name") == "texZone"
if empty(cline)
return indent(lnum)
else
return indent(v:lnum)
- end
+ endif
endif
if lnum == 0
@@ -253,13 +261,13 @@ function! GetTeXIndent() " {{{
let stay = 0
endif
- if cline =~ '^\s*\\\?[\]}]' && s:CheckPairedIsLastCharacter(v:lnum, indent(v:lnum))
+ if cline =~ '^\s*\\\?[\]}]' && s:CheckPairedIsLastCharacter(v:lnum, ccol)
let ind -= shiftwidth()
let stay = 0
endif
if line !~ '^\s*\\\?[\]}]'
- for i in range(indent(lnum)+1, strlen(line)-1)
+ for i in range(1, strlen(line)-1)
let char = line[i]
if char == ']' || char == '}'
if s:CheckPairedIsLastCharacter(lnum, i)
diff --git a/runtime/keymap/russian-jcukenwintype.vim b/runtime/keymap/russian-jcukenwintype.vim
new file mode 100644
index 0000000000..25d6286e24
--- /dev/null
+++ b/runtime/keymap/russian-jcukenwintype.vim
@@ -0,0 +1,112 @@
+" Vim Keymap file for russian characters, layout 'jcuken', MS Windows
+" Typewriter variant (slightly incompatible with XFree86 'ru' keymap -
+" makes use of NUMERO SIGN)
+" Useful mainly with utf-8 but may work with other encodings
+
+" Derived from russian-jcuken.vim by Artem Chuprina <ran@ran.pp.ru>
+" Typewriter variant of standart russian layout
+" Maintainer: Denis Proskurin <danwerspb@gmail.com>
+" Last Changed: 2015 May 15
+
+" All characters are given literally, conversion to another encoding (e.g.,
+" UTF-8) should work.
+
+scriptencoding utf-8
+
+let b:keymap_name = "ru"
+
+loadkeymap
+F Ð CYRILLIC CAPITAL LETTER A
+< Б CYRILLIC CAPITAL LETTER BE
+D Ð’ CYRILLIC CAPITAL LETTER VE
+U Г CYRILLIC CAPITAL LETTER GHE
+L Д CYRILLIC CAPITAL LETTER DE
+T Е CYRILLIC CAPITAL LETTER IE
+? Ð CYRILLIC CAPITAL LETTER IO
+: Ж CYRILLIC CAPITAL LETTER ZHE
+P З CYRILLIC CAPITAL LETTER ZE
+B И CYRILLIC CAPITAL LETTER I
+Q Й CYRILLIC CAPITAL LETTER SHORT I
+R К CYRILLIC CAPITAL LETTER KA
+K Л CYRILLIC CAPITAL LETTER EL
+V М CYRILLIC CAPITAL LETTER EM
+Y Ð CYRILLIC CAPITAL LETTER EN
+J О CYRILLIC CAPITAL LETTER O
+G П CYRILLIC CAPITAL LETTER PE
+H Р CYRILLIC CAPITAL LETTER ER
+C С CYRILLIC CAPITAL LETTER ES
+N Т CYRILLIC CAPITAL LETTER TE
+E У CYRILLIC CAPITAL LETTER U
+A Ф CYRILLIC CAPITAL LETTER EF
+{ Х CYRILLIC CAPITAL LETTER HA
+W Ц CYRILLIC CAPITAL LETTER TSE
+X Ч CYRILLIC CAPITAL LETTER CHE
+I Ш CYRILLIC CAPITAL LETTER SHA
+O Щ CYRILLIC CAPITAL LETTER SHCHA
+} Ъ CYRILLIC CAPITAL LETTER HARD SIGN
+S Ы CYRILLIC CAPITAL LETTER YERU
+M Ь CYRILLIC CAPITAL LETTER SOFT SIGN
+\" Э CYRILLIC CAPITAL LETTER E
+> Ю CYRILLIC CAPITAL LETTER YU
+Z Я CYRILLIC CAPITAL LETTER YA
+f а CYRILLIC SMALL LETTER A
+, б CYRILLIC SMALL LETTER BE
+d в CYRILLIC SMALL LETTER VE
+u г CYRILLIC SMALL LETTER GHE
+l д CYRILLIC SMALL LETTER DE
+t е CYRILLIC SMALL LETTER IE
+/ Ñ‘ CYRILLIC SMALL LETTER IO
+; ж CYRILLIC SMALL LETTER ZHE
+p з CYRILLIC SMALL LETTER ZE
+b и CYRILLIC SMALL LETTER I
+q й CYRILLIC SMALL LETTER SHORT I
+r к CYRILLIC SMALL LETTER KA
+k л CYRILLIC SMALL LETTER EL
+v м CYRILLIC SMALL LETTER EM
+y н CYRILLIC SMALL LETTER EN
+j о CYRILLIC SMALL LETTER O
+g п CYRILLIC SMALL LETTER PE
+h р CYRILLIC SMALL LETTER ER
+c Ñ CYRILLIC SMALL LETTER ES
+n Ñ‚ CYRILLIC SMALL LETTER TE
+e у CYRILLIC SMALL LETTER U
+a Ñ„ CYRILLIC SMALL LETTER EF
+[ Ñ… CYRILLIC SMALL LETTER HA
+w ц CYRILLIC SMALL LETTER TSE
+x ч CYRILLIC SMALL LETTER CHE
+i ш CYRILLIC SMALL LETTER SHA
+o щ CYRILLIC SMALL LETTER SHCHA
+] ÑŠ CYRILLIC SMALL LETTER HARD SIGN
+s Ñ‹ CYRILLIC SMALL LETTER YERU
+m ь CYRILLIC SMALL LETTER SOFT SIGN
+' Ñ CYRILLIC SMALL LETTER E
+. ÑŽ CYRILLIC SMALL LETTER YU
+z Ñ CYRILLIC SMALL LETTER YA
+` |
+1 â„–
+2 -
+3 /
+4 "
+5 :
+6 ,
+7 .
+8 _
+9 ?
+0 %
+- !
+= ;
+~ +
+! 1
+@ 2
+# 3
+$ 4
+% 5
+^ 6
+& 7
+* 8
+( 9
+) 0
+_ =
++ \\
+\\ )
+\| (
diff --git a/runtime/lua/vim/highlight.lua b/runtime/lua/vim/highlight.lua
new file mode 100644
index 0000000000..ce0a3de520
--- /dev/null
+++ b/runtime/lua/vim/highlight.lua
@@ -0,0 +1,77 @@
+local api = vim.api
+
+local highlight = {}
+
+--- Highlight range between two positions
+---
+--@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 rtype type of range (:help setreg, default charwise)
+--@param inclusive boolean indicating whether the range is end-inclusive (default false)
+function highlight.range(bufnr, ns, higroup, start, finish, rtype, inclusive)
+ rtype = rtype or 'v'
+ inclusive = inclusive or false
+
+ -- sanity check
+ if start[2] < 0 or finish[2] < start[2] then return end
+
+ local region = vim.region(bufnr, start, finish, rtype, inclusive)
+ for linenr, cols in pairs(region) do
+ api.nvim_buf_add_highlight(bufnr, ns, higroup, linenr, cols[1], cols[2])
+ end
+
+end
+
+local yank_ns = api.nvim_create_namespace('hlyank')
+--- 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}
+---
+-- @param opts dictionary 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)
+function highlight.on_yank(opts)
+ vim.validate {
+ opts = { opts,
+ function(t) if t == nil then return true else return type(t) == 'table' end end,
+ 'a table or nil to configure options (see `:h highlight.on_yank`)',
+ }}
+ opts = opts or {}
+ local event = opts.event or vim.v.event
+ local on_macro = opts.on_macro or false
+ local on_visual = (opts.on_visual ~= false)
+
+ if (not on_macro) and vim.fn.reg_executing() ~= '' then return end
+ if event.operator ~= 'y' or event.regtype == '' then return end
+ if (not on_visual) and event.visual then return end
+
+ local higroup = opts.higroup or "IncSearch"
+ local timeout = opts.timeout or 150
+
+ local bufnr = api.nvim_get_current_buf()
+ api.nvim_buf_clear_namespace(bufnr, yank_ns, 0, -1)
+
+ 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]}
+
+ highlight.range(bufnr, yank_ns, higroup, pos1, pos2, event.regtype, event.inclusive)
+
+ vim.defer_fn(
+ function() api.nvim_buf_clear_namespace(bufnr, yank_ns, 0, -1) end,
+ timeout
+ )
+end
+
+return highlight
diff --git a/runtime/lua/vim/inspect.lua b/runtime/lua/vim/inspect.lua
index 7cb40ca64d..0448ea487f 100644
--- a/runtime/lua/vim/inspect.lua
+++ b/runtime/lua/vim/inspect.lua
@@ -244,6 +244,11 @@ function Inspector:putTable(t)
local nonSequentialKeys, nonSequentialKeysLength, sequenceLength = getNonSequentialKeys(t)
local mt = getmetatable(t)
+ if (vim and sequenceLength == 0 and nonSequentialKeysLength == 0
+ and mt == vim._empty_dict_mt) then
+ self:puts(tostring(t))
+ return
+ end
self:puts('{')
self:down(function()
@@ -289,7 +294,7 @@ function Inspector:putValue(v)
if tv == 'string' then
self:puts(smartQuote(escape(v)))
elseif tv == 'number' or tv == 'boolean' or tv == 'nil' or
- tv == 'cdata' or tv == 'ctype' then
+ tv == 'cdata' or tv == 'ctype' or (vim and v == vim.NIL) then
self:puts(tostring(v))
elseif tv == 'table' then
self:putTable(v)
diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua
new file mode 100644
index 0000000000..6fe1d15b7e
--- /dev/null
+++ b/runtime/lua/vim/lsp.lua
@@ -0,0 +1,1013 @@
+local default_callbacks = require 'vim.lsp.callbacks'
+local log = require 'vim.lsp.log'
+local lsp_rpc = require 'vim.lsp.rpc'
+local protocol = require 'vim.lsp.protocol'
+local util = require 'vim.lsp.util'
+
+local vim = vim
+local nvim_err_writeln, nvim_buf_get_lines, nvim_command, nvim_buf_get_option
+ = vim.api.nvim_err_writeln, vim.api.nvim_buf_get_lines, vim.api.nvim_command, vim.api.nvim_buf_get_option
+local uv = vim.loop
+local tbl_isempty, tbl_extend = vim.tbl_isempty, vim.tbl_extend
+local validate = vim.validate
+
+local lsp = {
+ protocol = protocol;
+ callbacks = default_callbacks;
+ buf = require'vim.lsp.buf';
+ util = util;
+ -- Allow raw RPC access.
+ rpc = lsp_rpc;
+ -- Export these directly from rpc.
+ rpc_response_error = lsp_rpc.rpc_response_error;
+ -- You probably won't need this directly, since __tostring is set for errors
+ -- by the RPC.
+ -- format_rpc_error = lsp_rpc.format_rpc_error;
+}
+
+-- TODO improve handling of scratch buffers with LSP attached.
+
+local function err_message(...)
+ nvim_err_writeln(table.concat(vim.tbl_flatten{...}))
+ nvim_command("redraw")
+end
+
+local function resolve_bufnr(bufnr)
+ validate { bufnr = { bufnr, 'n', true } }
+ if bufnr == nil or bufnr == 0 then
+ return vim.api.nvim_get_current_buf()
+ end
+ return bufnr
+end
+
+local function is_dir(filename)
+ validate{filename={filename,'s'}}
+ local stat = uv.fs_stat(filename)
+ return stat and stat.type == 'directory' or false
+end
+
+local wait_result_reason = { [-1] = "timeout"; [-2] = "interrupted"; [-3] = "error" }
+
+local valid_encodings = {
+ ["utf-8"] = 'utf-8'; ["utf-16"] = 'utf-16'; ["utf-32"] = 'utf-32';
+ ["utf8"] = 'utf-8'; ["utf16"] = 'utf-16'; ["utf32"] = 'utf-32';
+ UTF8 = 'utf-8'; UTF16 = 'utf-16'; UTF32 = 'utf-32';
+}
+
+local client_index = 0
+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 function for_each_buffer_client(bufnr, callback)
+ validate {
+ callback = { callback, 'f' };
+ }
+ bufnr = resolve_bufnr(bufnr)
+ local client_ids = all_buffer_active_clients[bufnr]
+ if not client_ids or tbl_isempty(client_ids) then
+ return
+ end
+ for client_id in pairs(client_ids) do
+ local client = active_clients[client_id]
+ if client then
+ callback(client, client_id, bufnr)
+ end
+ 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.
+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;
+})
+
+local function validate_encoding(encoding)
+ validate {
+ encoding = { encoding, 's' };
+ }
+ return valid_encodings[encoding:lower()]
+ or error(string.format("Invalid offset encoding %q. Must be one of: 'utf-8', 'utf-16', 'utf-32'", encoding))
+end
+
+function lsp._cmd_parts(input)
+ vim.validate{cmd={
+ input,
+ function() return vim.tbl_islist(input) end,
+ "list"}}
+
+ local cmd = input[1]
+ local cmd_args = {}
+ -- Don't mutate our input.
+ for i, v in ipairs(input) do
+ vim.validate{["cmd argument"]={v, "s"}}
+ if i > 1 then
+ table.insert(cmd_args, v)
+ end
+ end
+ return cmd, cmd_args
+end
+
+local function optional_validator(fn)
+ return function(v)
+ return v == nil or fn(v)
+ end
+end
+
+local function validate_client_config(config)
+ validate {
+ config = { config, 't' };
+ }
+ validate {
+ root_dir = { config.root_dir, is_dir, "directory" };
+ callbacks = { config.callbacks, "t", true };
+ capabilities = { config.capabilities, "t", true };
+ cmd_cwd = { config.cmd_cwd, optional_validator(is_dir), "directory" };
+ cmd_env = { config.cmd_env, "t", true };
+ name = { config.name, 's', true };
+ on_error = { config.on_error, "f", true };
+ on_exit = { config.on_exit, "f", true };
+ on_init = { config.on_init, "f", true };
+ before_init = { config.before_init, "f", true };
+ offset_encoding = { config.offset_encoding, "s", true };
+ }
+ local cmd, cmd_args = lsp._cmd_parts(config.cmd)
+ 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;
+ }
+end
+
+local function buf_get_full_text(bufnr)
+ local text = table.concat(nvim_buf_get_lines(bufnr, 0, -1, true), '\n')
+ if nvim_buf_get_option(bufnr, 'eol') then
+ text = text .. '\n'
+ end
+ return text
+end
+
+local function text_document_did_open_handler(bufnr, client)
+ if not client.resolved_capabilities.text_document_open_close then
+ return
+ end
+ if not vim.api.nvim_buf_is_loaded(bufnr) then
+ return
+ end
+ local params = {
+ textDocument = {
+ version = 0;
+ uri = vim.uri_from_bufnr(bufnr);
+ -- TODO make sure our filetypes are compatible with languageId names.
+ languageId = nvim_buf_get_option(bufnr, 'filetype');
+ text = buf_get_full_text(bufnr);
+ }
+ }
+ client.notify('textDocument/didOpen', params)
+ util.buf_versions[bufnr] = params.textDocument.version
+end
+
+--- LSP client object.
+---
+--- - Methods:
+---
+--- - request(method, params, [callback])
+--- Send a request to the server. If callback is not specified, it will use
+--- {client.callbacks} to try to find a callback. If one is not found there,
+--- then an error will occur.
+--- This is a thin wrapper around {client.rpc.request} with some additional
+--- checking.
+--- Returns a boolean to indicate if the notification was successful. If it
+--- is false, then it will always be false (the client has shutdown).
+--- If it was successful, then it will return the request id as the second
+--- result. You can use this with `notify("$/cancel", { id = request_id })`
+--- to cancel the request. This helper is made automatically with
+--- |vim.lsp.buf_request()|
+--- Returns: status, [client_id]
+---
+--- - notify(method, params)
+--- This is just {client.rpc.notify}()
+--- Returns a boolean to indicate if the notification was successful. If it
+--- is false, then it will always be false (the client has shutdown).
+--- Returns: status
+---
+--- - cancel_request(id)
+--- This is just {client.rpc.notify}("$/cancelRequest", { id = id })
+--- Returns the same as `notify()`.
+---
+--- - stop([force])
+--- Stop a client, optionally with force.
+--- By default, it will just ask the server to shutdown without force.
+--- If you request to stop a client which has previously been requested to
+--- shutdown, it will automatically escalate and force shutdown.
+---
+--- - is_stopped()
+--- Returns true if the client is fully stopped.
+---
+--- - Members
+--- - id (number): The id allocated to the client.
+---
+--- - name (string): If a name is specified on creation, that will be
+--- used. Otherwise it is just the client id. This is used for
+--- logs and messages.
+---
+--- - offset_encoding (string): The encoding used for communicating
+--- with the server. You can modify this in the `on_init` method
+--- before text is sent to the server.
+---
+--- - callbacks (table): The callbacks used by the client as
+--- described in |lsp-callbacks|.
+---
+--- - 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.
+---
+--- - resolved_capabilities (table): Normalized table of
+--- capabilities that we have detected based on the initialize
+--- response from the server in `server_capabilities`.
+function lsp.client()
+ error()
+end
+
+--- Starts and initializes a client with the given configuration.
+---
+--- Parameters `cmd` and `root_dir` are required.
+---
+--@param root_dir: (required, string) Directory where the LSP server will base
+--- its rootUri on initialization.
+---
+--@param cmd: (required, string or list treated like |jobstart()|) Base command
+--- that initiates the LSP client.
+---
+--@param cmd_cwd: (string, default=|getcwd()|) Directory to launch
+--- the `cmd` process. Not related to `root_dir`.
+---
+--@param 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:
+--- <pre>
+--- { "PRODUCTION=true"; "TEST=123"; PORT = 8080; HOST = "0.0.0.0"; }
+--- </pre>
+---
+--@param capabilities Map overriding the default capabilities defined by
+--- |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
+--- array.
+---
+--@param callbacks Map of language server method names to
+--- `function(err, method, params, client_id)` handler. Invoked for:
+--- - Notifications from the server, where `err` will always be `nil`.
+--- - Requests initiated by the server. For these you can respond by returning
+--- two values: `result, err` where err must be shaped like a RPC error,
+--- i.e. `{ code, message, data? }`. Use |vim.lsp.rpc_response_error()| to
+--- help with this.
+--- - Default callback for client requests not explicitly specifying
+--- a callback.
+---
+--@param init_options Values to pass in the initialization request
+--- as `initializationOptions`. See `initialize` in the LSP spec.
+---
+--@param name (string, default=client-id) Name in log messages.
+---
+--@param offset_encoding (default="utf-16") One of "utf-8", "utf-16",
+--- or "utf-32" which is the encoding that the LSP server expects. Client does
+--- not verify this is correct.
+---
+--@param on_error Callback with parameters (code, ...), invoked
+--- when the client operation throws an error. `code` is a number describing
+--- the error. Other arguments may be passed depending on the error kind. See
+--- |vim.lsp.client_errors| for possible errors.
+--- Use `vim.lsp.client_errors[code]` to get human-friendly name.
+---
+--@param before_init Callback with parameters (initialize_params, config)
+--- invoked before the LSP "initialize" phase, where `params` contains the
+--- parameters being sent to the server and `config` is the config that was
+--- passed to `start_client()`. You can use this to modify parameters before
+--- they are sent.
+---
+--@param on_init Callback (client, initialize_result) invoked after LSP
+--- "initialize", where `result` is a table of `capabilities` and anything else
+--- the server may send. For example, clangd sends
+--- `initialize_result.offsetEncoding` if `capabilities.offsetEncoding` was
+--- sent to it. You can only modify the `client.offset_encoding` here before
+--- any notifications are sent.
+---
+--@param on_exit Callback (code, signal, client_id) invoked on client
+--- exit.
+--- - code: exit code of the process
+--- - signal: number describing the signal used to terminate (if any)
+--- - client_id: client handle
+---
+--@param on_attach Callback (client, bufnr) invoked when client
+--- attaches to a buffer.
+---
+--@param trace: "off" | "messages" | "verbose" | nil passed directly to the language
+--- server in the initialize request. Invalid/empty values will default to "off"
+---
+--@returns Client id. |vim.lsp.get_client_by_id()| Note: client is only
+--- available after it has been initialized, which may happen after a small
+--- delay (or never if there is an error). 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 client_id = next_client_id()
+
+ local callbacks = config.callbacks or {}
+ local name = config.name or tostring(client_id)
+ local log_prefix = string.format("LSP[%s]", name)
+
+ local handlers = {}
+
+ local function resolve_callback(method)
+ return callbacks[method] or default_callbacks[method]
+ end
+
+ function handlers.notification(method, params)
+ local _ = log.debug() and log.debug('notification', method, params)
+ local callback = resolve_callback(method)
+ if callback then
+ -- Method name is provided here for convenience.
+ callback(nil, method, params, client_id)
+ end
+ end
+
+ function handlers.server_request(method, params)
+ local _ = log.debug() and log.debug('server_request', method, params)
+ local callback = resolve_callback(method)
+ if callback then
+ local _ = log.debug() and log.debug("server_request: found callback for", method)
+ return callback(nil, method, params, client_id)
+ end
+ local _ = log.debug() and log.debug("server_request: no callback found for", method)
+ return nil, lsp.rpc_response_error(protocol.ErrorCodes.MethodNotFound)
+ end
+
+ function handlers.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))
+ if config.on_error then
+ local status, usererr = pcall(config.on_error, code, err)
+ if not status then
+ local _ = log.error() and log.error(log_prefix, "user on_error failed", { err = usererr })
+ err_message(log_prefix, ' user on_error failed: ', tostring(usererr))
+ end
+ end
+ end
+
+ function handlers.on_exit(code, signal)
+ active_clients[client_id] = nil
+ uninitialized_clients[client_id] = nil
+ local active_buffers = {}
+ for bufnr, client_ids in pairs(all_buffer_active_clients) do
+ if client_ids[client_id] then
+ table.insert(active_buffers, bufnr)
+ end
+ client_ids[client_id] = nil
+ end
+ -- Buffer level cleanup
+ vim.schedule(function()
+ for _, bufnr in ipairs(active_buffers) do
+ util.buf_clear_diagnostics(bufnr)
+ end
+ end)
+ if config.on_exit then
+ pcall(config.on_exit, code, signal, client_id)
+ end
+ end
+
+ -- Start the RPC client.
+ local rpc = lsp_rpc.start(cmd, cmd_args, handlers, {
+ cwd = config.cmd_cwd;
+ env = config.cmd_env;
+ })
+
+ local client = {
+ id = client_id;
+ name = name;
+ rpc = rpc;
+ offset_encoding = offset_encoding;
+ callbacks = callbacks;
+ config = config;
+ }
+
+ -- Store the uninitialized_clients for cleanup in case we exit before
+ -- initialize finishes.
+ uninitialized_clients[client_id] = client;
+
+ local function initialize()
+ local valid_traces = {
+ off = 'off'; messages = 'messages'; verbose = 'verbose';
+ }
+ local initialize_params = {
+ -- 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 (see exit notification)
+ -- its process.
+ processId = uv.getpid();
+ -- The rootPath of the workspace. Is null if no folder is open.
+ --
+ -- @deprecated in favour of rootUri.
+ rootPath = config.root_dir;
+ -- The rootUri of the workspace. Is null if no folder is open. If both
+ -- `rootPath` and `rootUri` are set `rootUri` wins.
+ rootUri = vim.uri_from_fname(config.root_dir);
+ -- 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();
+ -- The initial trace setting. If omitted trace is disabled ("off").
+ -- trace = "off" | "messages" | "verbose";
+ trace = valid_traces[config.trace] or 'off';
+ -- 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
+ -- workspaceFolders?: WorkspaceFolder[] | null;
+ -- export interface WorkspaceFolder {
+ -- -- The associated URI for this workspace folder.
+ -- uri
+ -- -- The name of the workspace folder. Used to refer to this
+ -- -- workspace folder in the user interface.
+ -- name
+ -- }
+ workspaceFolders = nil;
+ }
+ if config.before_init then
+ -- TODO(ashkan) handle errors here.
+ pcall(config.before_init, initialize_params, config)
+ end
+ local _ = log.debug() and log.debug(log_prefix, "initialize_params", initialize_params)
+ rpc.request('initialize', initialize_params, function(init_err, result)
+ assert(not init_err, tostring(init_err))
+ assert(result, "server sent empty result")
+ rpc.notify('initialized', {[vim.type_idx]=vim.types.dictionary})
+ client.initialized = true
+ uninitialized_clients[client_id] = nil
+ client.server_capabilities = assert(result.capabilities, "initialize result doesn't contain capabilities")
+ -- These are the cleaned up capabilities we use for dynamically deciding
+ -- when to send certain events to clients.
+ client.resolved_capabilities = protocol.resolve_capabilities(client.server_capabilities)
+ 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)
+ end
+ end
+ local _ = log.debug() and log.debug(log_prefix, "server_capabilities", client.server_capabilities)
+ local _ = log.info() and log.info(log_prefix, "initialized", { resolved_capabilities = client.resolved_capabilities })
+
+ -- Only assign after initialized.
+ active_clients[client_id] = client
+ -- If we had been registered before we start, then send didOpen This can
+ -- happen if we attach to buffers before initialize finishes or if
+ -- someone restarts a client.
+ for bufnr, client_ids in pairs(all_buffer_active_clients) do
+ if client_ids[client_id] then
+ client._on_attach(bufnr)
+ end
+ end
+ end)
+ end
+
+ local function unsupported_method(method)
+ local msg = "server doesn't support "..method
+ local _ = log.warn() and log.warn(msg)
+ err_message(msg)
+ return lsp.rpc_response_error(protocol.ErrorCodes.MethodNotFound, msg)
+ end
+
+ --- Checks capabilities before rpc.request-ing.
+ function client.request(method, params, callback, bufnr)
+ if not callback then
+ callback = resolve_callback(method)
+ or error(string.format("not found: %q request callback for client %q.", method, client.name))
+ end
+ local _ = log.debug() and log.debug(log_prefix, "client.request", client_id, method, params, callback, bufnr)
+ -- TODO keep these checks or just let it go anyway?
+ if (not client.resolved_capabilities.hover and method == 'textDocument/hover')
+ or (not client.resolved_capabilities.signature_help and method == 'textDocument/signatureHelp')
+ or (not client.resolved_capabilities.goto_definition and method == 'textDocument/definition')
+ or (not client.resolved_capabilities.implementation and method == 'textDocument/implementation')
+ or (not client.resolved_capabilities.declaration and method == 'textDocument/declaration')
+ or (not client.resolved_capabilities.type_definition and method == 'textDocument/typeDefinition')
+ or (not client.resolved_capabilities.document_symbol and method == 'textDocument/documentSymbol')
+ or (not client.resolved_capabilities.workspace_symbol and method == 'textDocument/workspaceSymbol')
+ or (not client.resolved_capabilities.call_hierarchy and method == 'textDocument/prepareCallHierarchy')
+ then
+ callback(unsupported_method(method), method, nil, client_id, bufnr)
+ return
+ end
+ return rpc.request(method, params, function(err, result)
+ callback(err, method, result, client_id, bufnr)
+ end)
+ end
+
+ function client.notify(...)
+ return rpc.notify(...)
+ end
+
+ function client.cancel_request(id)
+ validate{id = {id, 'n'}}
+ return rpc.notify("$/cancelRequest", { id = id })
+ end
+
+ -- Track this so that we can escalate automatically if we've alredy tried a
+ -- graceful shutdown
+ local tried_graceful_shutdown = false
+ function client.stop(force)
+ local handle = rpc.handle
+ if handle:is_closing() then
+ return
+ end
+ if force or (not client.initialized) or tried_graceful_shutdown then
+ handle:kill(15)
+ return
+ end
+ tried_graceful_shutdown = true
+ -- Sending a signal after a process has exited is acceptable.
+ rpc.request('shutdown', nil, function(err, _)
+ if err == nil then
+ rpc.notify('exit')
+ else
+ -- If there was an error in the shutdown request, then term to be safe.
+ handle:kill(15)
+ end
+ end)
+ end
+
+ function client.is_stopped()
+ return rpc.handle:is_closing()
+ end
+
+ function client._on_attach(bufnr)
+ text_document_did_open_handler(bufnr, client)
+ if config.on_attach then
+ -- TODO(ashkan) handle errors.
+ pcall(config.on_attach, client, bufnr)
+ end
+ end
+
+ initialize()
+
+ return client_id
+end
+
+local function once(fn)
+ local value
+ return function(...)
+ if not value then value = fn(...) end
+ return value
+ end
+end
+
+local text_document_did_change_handler
+do
+ local encoding_index = { ["utf-8"] = 1; ["utf-16"] = 2; ["utf-32"] = 3; }
+ text_document_did_change_handler = function(_, bufnr, changedtick,
+ firstline, lastline, new_lastline, old_byte_size, old_utf32_size,
+ old_utf16_size)
+ local _ = log.debug() and log.debug("on_lines", bufnr, changedtick, firstline,
+ lastline, new_lastline, old_byte_size, old_utf32_size, old_utf16_size, nvim_buf_get_lines(bufnr, firstline, new_lastline, true))
+
+ -- Don't do anything if there are no clients attached.
+ if tbl_isempty(all_buffer_active_clients[bufnr] or {}) then
+ return
+ end
+
+ util.buf_versions[bufnr] = changedtick
+ -- Lazy initialize these because clients may not even need them.
+ local incremental_changes = once(function(client)
+ local size_index = encoding_index[client.offset_encoding]
+ local length = select(size_index, old_byte_size, old_utf16_size, old_utf32_size)
+ local lines = nvim_buf_get_lines(bufnr, firstline, new_lastline, true)
+ -- This is necessary because we are specifying the full line including the
+ -- newline in range. Therefore, we must replace the newline as well.
+ if #lines > 0 then
+ table.insert(lines, '')
+ end
+ return {
+ range = {
+ start = { line = firstline, character = 0 };
+ ["end"] = { line = lastline, character = 0 };
+ };
+ rangeLength = length;
+ text = table.concat(lines, '\n');
+ };
+ end)
+ local full_changes = once(function()
+ return {
+ text = buf_get_full_text(bufnr);
+ };
+ end)
+ local uri = vim.uri_from_bufnr(bufnr)
+ for_each_buffer_client(bufnr, function(client, _client_id)
+ local text_document_did_change = client.resolved_capabilities.text_document_did_change
+ local changes
+ if text_document_did_change == protocol.TextDocumentSyncKind.None then
+ return
+ --[=[ TODO(ashkan) there seem to be problems with the byte_sizes sent by
+ -- neovim right now so only send the full content for now. In general, we
+ -- can assume that servers *will* support both versions anyway, as there
+ -- is no way to specify the sync capability by the client.
+ -- See https://github.com/palantir/python-language-server/commit/cfd6675bc10d5e8dbc50fc50f90e4a37b7178821#diff-f68667852a14e9f761f6ebf07ba02fc8 for an example of pyls handling both.
+ --]=]
+ elseif true or text_document_did_change == protocol.TextDocumentSyncKind.Full then
+ changes = full_changes(client)
+ elseif text_document_did_change == protocol.TextDocumentSyncKind.Incremental then
+ changes = incremental_changes(client)
+ end
+ client.notify("textDocument/didChange", {
+ textDocument = {
+ uri = uri;
+ version = changedtick;
+ };
+ contentChanges = { changes; }
+ })
+ end)
+ end
+end
+
+-- Buffer lifecycle handler for textDocument/didSave
+function lsp._text_document_did_save_handler(bufnr)
+ bufnr = resolve_bufnr(bufnr)
+ local uri = vim.uri_from_bufnr(bufnr)
+ local text = once(function()
+ return table.concat(nvim_buf_get_lines(bufnr, 0, -1, false), '\n')
+ end)
+ for_each_buffer_client(bufnr, function(client, _client_id)
+ if client.resolved_capabilities.text_document_save then
+ local included_text
+ if client.resolved_capabilities.text_document_save_include_text then
+ included_text = text()
+ end
+ client.notify('textDocument/didSave', {
+ textDocument = {
+ uri = uri;
+ text = included_text;
+ }
+ })
+ end
+ end)
+end
+
+--- Implements the `textDocument/did…` notifications required to track a buffer
+--- for any language server.
+---
+--- 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
+function lsp.buf_attach_client(bufnr, client_id)
+ validate {
+ bufnr = {bufnr, 'n', true};
+ client_id = {client_id, 'n'};
+ }
+ bufnr = resolve_bufnr(bufnr)
+ local buffer_client_ids = all_buffer_active_clients[bufnr]
+ -- This is our first time attaching to this buffer.
+ if not buffer_client_ids then
+ buffer_client_ids = {}
+ all_buffer_active_clients[bufnr] = buffer_client_ids
+
+ local uri = vim.uri_from_bufnr(bufnr)
+ nvim_command(string.format("autocmd BufWritePost <buffer=%d> lua vim.lsp._text_document_did_save_handler(0)", bufnr))
+ -- First time, so attach and set up stuff.
+ vim.api.nvim_buf_attach(bufnr, false, {
+ on_lines = text_document_did_change_handler;
+ on_detach = function()
+ local params = { textDocument = { uri = uri; } }
+ for_each_buffer_client(bufnr, function(client, _client_id)
+ if client.resolved_capabilities.text_document_open_close then
+ client.notify('textDocument/didClose', params)
+ end
+ end)
+ util.buf_versions[bufnr] = nil
+ all_buffer_active_clients[bufnr] = nil
+ end;
+ -- TODO if we know all of the potential clients ahead of time, then we
+ -- could conditionally set this.
+ -- utf_sizes = size_index > 1;
+ utf_sizes = true;
+ })
+ end
+ if buffer_client_ids[client_id] then return end
+ -- This is our first time attaching this client to this buffer.
+ buffer_client_ids[client_id] = true
+
+ local client = active_clients[client_id]
+ -- Send didOpen for the client if it is initialized. If it isn't initialized
+ -- then it will send didOpen on initialize.
+ if client then
+ client._on_attach(bufnr)
+ end
+ return true
+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
+function lsp.buf_is_attached(bufnr, client_id)
+ return (all_buffer_active_clients[bufnr] or {})[client_id] == true
+end
+
+--- Gets an active client by id, or nil if the id is invalid or the
+--- client is not yet initialized.
+---
+--@param client_id client id number
+---
+--@return |vim.lsp.client| object, or nil
+function lsp.get_client_by_id(client_id)
+ return active_clients[client_id]
+end
+
+--- Stops a client(s).
+---
+--- You can also use the `stop()` function on a |vim.lsp.client| object.
+--- To stop all clients:
+---
+--- <pre>
+--- vim.lsp.stop_client(lsp.get_active_clients())
+--- </pre>
+---
+--- By default asks the server to shutdown, unless stop was requested
+--- already for this client, then force-shutdown is attempted.
+---
+--@param client_id client id or |vim.lsp.client| object, or list thereof
+--@param force boolean (optional) shutdown forcefully
+function lsp.stop_client(client_id, force)
+ local ids = type(client_id) == 'table' and client_id or {client_id}
+ for _, id in ipairs(ids) do
+ if type(id) == 'table' and id.stop ~= nil then
+ id.stop(force)
+ elseif active_clients[id] then
+ active_clients[id].stop(force)
+ elseif uninitialized_clients[id] then
+ uninitialized_clients[id].stop(true)
+ end
+ end
+end
+
+--- Gets all active clients.
+---
+--@return Table of |vim.lsp.client| objects
+function lsp.get_active_clients()
+ return vim.tbl_values(active_clients)
+end
+
+function lsp._vim_exit_handler()
+ log.info("exit_handler", active_clients)
+ for _, client in pairs(uninitialized_clients) do
+ client.stop(true)
+ end
+ -- TODO handle v:dying differently?
+ if tbl_isempty(active_clients) then
+ return
+ end
+ for _, client in pairs(active_clients) do
+ client.stop()
+ end
+
+ if not vim.wait(500, function() return tbl_isempty(active_clients) end, 50) then
+ for _, client in pairs(active_clients) do
+ client.stop(true)
+ end
+ end
+end
+
+nvim_command("autocmd VimLeavePre * lua vim.lsp._vim_exit_handler()")
+
+
+--- Sends an async request for all active clients attached to the
+--- buffer.
+---
+--@param bufnr (number) Buffer handle, or 0 for current.
+--@param method (string) LSP method name
+--@param params (optional, table) Parameters to send to the server
+--@param callback (optional, functionnil) Handler
+-- `function(err, method, params, client_id)` for this request. Defaults
+-- to the client callback in `client.callbacks`. See |lsp-callbacks|.
+--
+--@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.
+function lsp.buf_request(bufnr, method, params, callback)
+ validate {
+ bufnr = { bufnr, 'n', true };
+ method = { method, 's' };
+ callback = { callback, 'f', true };
+ }
+ local client_request_ids = {}
+ for_each_buffer_client(bufnr, function(client, client_id, resolved_bufnr)
+ local request_success, request_id = client.request(method, params, callback, 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)
+
+ local function _cancel_all_requests()
+ for client_id, request_id in pairs(client_request_ids) do
+ local client = active_clients[client_id]
+ client.cancel_request(request_id)
+ end
+ end
+
+ return client_request_ids, _cancel_all_requests
+end
+
+--- Sends a request to a server and waits for the response.
+---
+--- Calls |vim.lsp.buf_request()| 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 100) ms.
+---
+--@param bufnr (number) Buffer handle, or 0 for current.
+--@param method (string) LSP method name
+--@param params (optional, table) Parameters to send to the server
+--@param timeout_ms (optional, number, default=100) Maximum time in
+--- milliseconds to wait for a result.
+---
+--@returns Map of client_id:request_result. On timeout, cancel or error,
+--- returns `(nil, err)` where `err` is a string describing the failure
+--- reason.
+function lsp.buf_request_sync(bufnr, method, params, timeout_ms)
+ local request_results = {}
+ local result_count = 0
+ local function _callback(err, _method, result, client_id)
+ request_results[client_id] = { error = err, result = result }
+ result_count = result_count + 1
+ end
+ local client_request_ids, cancel = lsp.buf_request(bufnr, method, params, _callback)
+ local expected_result_count = 0
+ for _ in pairs(client_request_ids) do
+ expected_result_count = expected_result_count + 1
+ end
+
+ local wait_result, reason = vim.wait(timeout_ms or 100, function()
+ return result_count >= expected_result_count
+ end, 10)
+
+ if not wait_result then
+ cancel()
+ return nil, wait_result_reason[reason]
+ end
+ return request_results
+end
+
+--- Send a notification to a server
+--@param bufnr [number] (optional): The number of the buffer
+--@param method [string]: Name of the request method
+--@param params [string]: Arguments to send to the server
+---
+--@returns 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)
+ if client.rpc.notify(method, params) then resp = true end
+ end)
+ return resp
+end
+
+--- Implements 'omnifunc' compatible LSP completion.
+---
+--@see |complete-functions|
+--@see |complete-items|
+--@see |CompleteDone|
+---
+--@param findstart 0 or 1, decides behavior
+--@param base If findstart=0, text to match against
+---
+--@return (number) 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
+ end
+
+ -- Then, perform standard completion request
+ local _ = log.info() and log.info("base ", base)
+
+ local pos = vim.api.nvim_win_get_cursor(0)
+ local line = vim.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 prefix = line_to_cursor:sub(textMatch+1)
+
+ local params = util.make_position_params()
+
+ local items = {}
+ lsp.buf_request(bufnr, 'textDocument/completion', params, function(err, _, result)
+ if err or not result then return end
+ 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(textMatch+1, items)
+ end)
+
+ -- Return -2 to signal that we should continue completion so that we can
+ -- async complete.
+ return -2
+end
+
+function lsp.client_is_stopped(client_id)
+ return active_clients[client_id] == nil
+end
+
+--- Gets a map of client_id:client pairs for the given buffer, where each value
+--- is a |vim.lsp.client| object.
+---
+--@param bufnr (optional, number): Buffer handle, or 0 for current
+function lsp.buf_get_clients(bufnr)
+ bufnr = resolve_bufnr(bufnr)
+ local result = {}
+ for_each_buffer_client(bufnr, function(client, client_id)
+ 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"
+-- Level numbers begin with "trace" at 0
+lsp.log_levels = log.levels
+
+--- Sets the global log level for LSP logging.
+---
+--- Levels by name: "trace", "debug", "info", "warn", "error"
+--- Level numbers begin with "trace" at 0
+---
+--- Use `lsp.log_levels` for reverse lookup.
+---
+--@see |vim.lsp.log_levels|
+---
+--@param level [number|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)
+ else
+ error(string.format("Invalid log level: %q", level))
+ end
+end
+
+--- Gets the path of the logfile used by the LSP client.
+function lsp.get_log_path()
+ return log.get_filename()
+end
+
+-- Define the LspDiagnostics signs if they're not defined already.
+do
+ local function define_default_sign(name, properties)
+ if vim.tbl_isempty(vim.fn.sign_getdefined(name)) then
+ vim.fn.sign_define(name, properties)
+ end
+ end
+ define_default_sign('LspDiagnosticsErrorSign', {text='E', texthl='LspDiagnosticsErrorSign', linehl='', numhl=''})
+ define_default_sign('LspDiagnosticsWarningSign', {text='W', texthl='LspDiagnosticsWarningSign', linehl='', numhl=''})
+ define_default_sign('LspDiagnosticsInformationSign', {text='I', texthl='LspDiagnosticsInformationSign', linehl='', numhl=''})
+ define_default_sign('LspDiagnosticsHintSign', {text='H', texthl='LspDiagnosticsHintSign', linehl='', numhl=''})
+end
+
+return lsp
+-- vim:sw=2 ts=2 et
diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua
new file mode 100644
index 0000000000..2e27617997
--- /dev/null
+++ b/runtime/lua/vim/lsp/buf.lua
@@ -0,0 +1,251 @@
+local vim = vim
+local validate = vim.validate
+local api = vim.api
+local vfn = vim.fn
+local util = require 'vim.lsp.util'
+local list_extend = vim.list_extend
+
+local M = {}
+
+local function ok_or_nil(status, ...)
+ if not status then return end
+ return ...
+end
+local function npcall(fn, ...)
+ return ok_or_nil(pcall(fn, ...))
+end
+
+local function request(method, params, callback)
+ validate {
+ method = {method, 's'};
+ callback = {callback, 'f', true};
+ }
+ return vim.lsp.buf_request(0, method, params, callback)
+end
+
+--- Sends a notification through all clients associated with current buffer.
+--
+--@return `true` if server responds.
+function M.server_ready()
+ return not not vim.lsp.buf_notify(0, "window/progress", {})
+end
+
+--- Displays hover information about the symbol under the cursor in a floating
+--- window. Calling the function twice will jump into the floating window.
+function M.hover()
+ local params = util.make_position_params()
+ request('textDocument/hover', params)
+end
+
+--- Jumps to the declaration of the symbol under the cursor.
+---
+function M.declaration()
+ local params = util.make_position_params()
+ request('textDocument/declaration', params)
+end
+
+--- Jumps to the definition of the symbol under the cursor.
+---
+function M.definition()
+ local params = util.make_position_params()
+ request('textDocument/definition', params)
+end
+
+--- Jumps to the definition of the type of the symbol under the cursor.
+---
+function M.type_definition()
+ local params = util.make_position_params()
+ request('textDocument/typeDefinition', params)
+end
+
+--- Lists all the implementations for the symbol under the cursor in the
+--- quickfix window.
+function M.implementation()
+ local params = util.make_position_params()
+ request('textDocument/implementation', params)
+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)
+end
+
+--- Retrieves the completion items at the current cursor position. Can only be
+--- called in Insert mode.
+function M.completion(context)
+ local params = util.make_position_params()
+ params.context = context
+ return request('textDocument/completion', params)
+end
+
+--- Formats the current buffer.
+---
+--- The optional {options} table can be used to specify FormattingOptions, a
+--- list of which is available at
+--- https://microsoft.github.io/language-server-protocol/specification#textDocument_formatting.
+--- Some unspecified options will be automatically derived from the current
+--- Neovim options.
+function M.formatting(options)
+ local params = util.make_formatting_params(options)
+ return request('textDocument/formatting', params)
+end
+
+--- Perform |vim.lsp.buf.formatting()| synchronously.
+---
+--- Useful for running on save, to make sure buffer is formatted prior to being
+--- saved. {timeout_ms} is passed on to |vim.lsp.buf_request_sync()|.
+function M.formatting_sync(options, timeout_ms)
+ local params = util.make_formatting_params(options)
+ local result = vim.lsp.buf_request_sync(0, "textDocument/formatting", params, timeout_ms)
+ if not result then return end
+ result = result[1].result
+ vim.lsp.util.apply_text_edits(result)
+end
+
+function M.range_formatting(options, start_pos, end_pos)
+ validate {
+ options = {options, 't', true};
+ start_pos = {start_pos, 't', true};
+ end_pos = {end_pos, 't', true};
+ }
+ local sts = vim.bo.softtabstop;
+ options = vim.tbl_extend('keep', options or {}, {
+ tabSize = (sts > 0 and sts) or (sts < 0 and vim.bo.shiftwidth) or vim.bo.tabstop;
+ insertSpaces = vim.bo.expandtab;
+ })
+ local A = list_extend({}, start_pos or api.nvim_buf_get_mark(0, '<'))
+ local B = list_extend({}, end_pos or api.nvim_buf_get_mark(0, '>'))
+ -- convert to 0-index
+ A[1] = A[1] - 1
+ B[1] = B[1] - 1
+ -- account for encoding.
+ if A[2] > 0 then
+ A = {A[1], util.character_offset(0, A[1], A[2])}
+ end
+ if B[2] > 0 then
+ B = {B[1], util.character_offset(0, B[1], B[2])}
+ end
+ local params = {
+ textDocument = { uri = vim.uri_from_bufnr(0) };
+ range = {
+ start = { line = A[1]; character = A[2]; };
+ ["end"] = { line = B[1]; character = B[2]; };
+ };
+ options = options;
+ }
+ return request('textDocument/rangeFormatting', params)
+end
+
+--- Renames all references to the symbol under the cursor. If {new_name} is not
+--- provided, the user will be prompted for a new name using |input()|.
+function M.rename(new_name)
+ -- TODO(ashkan) use prepareRename
+ -- * result: [`Range`](#range) \| `{ range: Range, placeholder: string }` \| `null` describing the range of the string to rename and optionally a placeholder text of the string content to be renamed. If `null` is returned then it is deemed that a 'textDocument/rename' request is not valid at the given position.
+ local params = util.make_position_params()
+ new_name = new_name or npcall(vfn.input, "New Name: ", vfn.expand('<cword>'))
+ if not (new_name and #new_name > 0) then return end
+ params.newName = new_name
+ request('textDocument/rename', params)
+end
+
+--- Lists all the references to the symbol under the cursor in the quickfix window.
+---
+function M.references(context)
+ validate { context = { context, 't', true } }
+ local params = util.make_position_params()
+ params.context = context or {
+ includeDeclaration = true;
+ }
+ params[vim.type_idx] = vim.types.dictionary
+ request('textDocument/references', params)
+end
+
+--- Lists all symbols in the current buffer in the quickfix window.
+---
+function M.document_symbol()
+ local params = { textDocument = util.make_text_document_params() }
+ request('textDocument/documentSymbol', params)
+end
+
+local function pick_call_hierarchy_item(call_hierarchy_items)
+ if not call_hierarchy_items then return end
+ if #call_hierarchy_items == 1 then
+ return call_hierarchy_items[1]
+ end
+ local items = {}
+ for i, item in ipairs(call_hierarchy_items) do
+ local entry = item.detail or item.name
+ table.insert(items, string.format("%d. %s", i, entry))
+ end
+ local choice = vim.fn.inputlist(items)
+ if choice < 1 or choice > #items then
+ return
+ end
+ return choice
+end
+
+function M.incoming_calls()
+ local params = util.make_position_params()
+ request('textDocument/prepareCallHierarchy', params, function(_, _, result)
+ local call_hierarchy_item = pick_call_hierarchy_item(result)
+ vim.lsp.buf_request(0, 'callHierarchy/incomingCalls', { item = call_hierarchy_item })
+ end)
+end
+
+function M.outgoing_calls()
+ local params = util.make_position_params()
+ request('textDocument/prepareCallHierarchy', params, function(_, _, result)
+ local call_hierarchy_item = pick_call_hierarchy_item(result)
+ vim.lsp.buf_request(0, 'callHierarchy/outgoingCalls', { item = call_hierarchy_item })
+ end)
+end
+
+--- Lists all symbols in the current workspace in the quickfix window.
+---
+--- The list is filtered against the optional argument {query};
+--- if the argument is omitted from the call, the user is prompted to enter a string on the command line.
+--- An empty string means no filtering is done.
+function M.workspace_symbol(query)
+ query = query or npcall(vfn.input, "Query: ")
+ local params = {query = query}
+ request('workspace/symbol', params)
+end
+
+--- Send request to server to resolve document highlights for the
+--- current text document position. This request can be associated
+--- to key mapping or to events such as `CursorHold`, eg:
+---
+--- <pre>
+--- vim.api.nvim_command [[autocmd CursorHold <buffer> lua vim.lsp.buf.document_highlight()]]
+--- vim.api.nvim_command [[autocmd CursorHoldI <buffer> lua vim.lsp.buf.document_highlight()]]
+--- vim.api.nvim_command [[autocmd CursorMoved <buffer> lua vim.lsp.buf.clear_references()]]
+--- </pre>
+function M.document_highlight()
+ local params = util.make_position_params()
+ request('textDocument/documentHighlight', params)
+end
+
+function M.clear_references()
+ util.buf_clear_references()
+end
+
+function M.code_action(context)
+ validate { context = { context, 't', true } }
+ context = context or { diagnostics = util.get_line_diagnostics() }
+ local params = util.make_range_params()
+ params.context = context
+ request('textDocument/codeAction', params)
+end
+
+function M.execute_command(command)
+ validate {
+ command = { command.command, 's' },
+ arguments = { command.arguments, 't', true }
+ }
+ request('workspace/executeCommand', command)
+end
+
+return M
+-- vim:sw=2 ts=2 et
diff --git a/runtime/lua/vim/lsp/callbacks.lua b/runtime/lua/vim/lsp/callbacks.lua
new file mode 100644
index 0000000000..1ed58995d0
--- /dev/null
+++ b/runtime/lua/vim/lsp/callbacks.lua
@@ -0,0 +1,293 @@
+local log = require 'vim.lsp.log'
+local protocol = require 'vim.lsp.protocol'
+local util = require 'vim.lsp.util'
+local vim = vim
+local api = vim.api
+local buf = require 'vim.lsp.buf'
+
+local M = {}
+
+local function err_message(...)
+ api.nvim_err_writeln(table.concat(vim.tbl_flatten{...}))
+ api.nvim_command("redraw")
+end
+
+M['workspace/executeCommand'] = function(err, _)
+ if err then
+ error("Could not execute code action: "..err.message)
+ end
+end
+
+M['textDocument/codeAction'] = function(_, _, actions)
+ if actions == nil or vim.tbl_isempty(actions) then
+ print("No code actions available")
+ return
+ end
+
+ local option_strings = {"Code Actions:"}
+ for i, action in ipairs(actions) do
+ local title = action.title:gsub('\r\n', '\\r\\n')
+ title = title:gsub('\n', '\\n')
+ table.insert(option_strings, string.format("%d. %s", i, title))
+ end
+
+ local choice = vim.fn.inputlist(option_strings)
+ if choice < 1 or choice > #actions then
+ return
+ end
+ local action_chosen = actions[choice]
+ -- textDocument/codeAction can return either Command[] or CodeAction[].
+ -- If it is a CodeAction, it can have either an edit, a command or both.
+ -- Edits should be executed first
+ if action_chosen.edit or type(action_chosen.command) == "table" then
+ if action_chosen.edit then
+ util.apply_workspace_edit(action_chosen.edit)
+ end
+ if type(action_chosen.command) == "table" then
+ buf.execute_command(action_chosen.command)
+ end
+ else
+ buf.execute_command(action_chosen)
+ end
+end
+
+M['workspace/applyEdit'] = function(_, _, workspace_edit)
+ if not workspace_edit then return end
+ -- TODO(ashkan) Do something more with label?
+ if workspace_edit.label then
+ print("Workspace edit", workspace_edit.label)
+ end
+ local status, result = pcall(util.apply_workspace_edit, workspace_edit.edit)
+ return {
+ applied = status;
+ failureReason = result;
+ }
+end
+
+M['textDocument/publishDiagnostics'] = function(_, _, result)
+ if not result then return end
+ local uri = result.uri
+ local bufnr = vim.uri_to_bufnr(uri)
+ if not bufnr then
+ err_message("LSP.publishDiagnostics: Couldn't find buffer for ", uri)
+ return
+ end
+
+ -- Unloaded buffers should not handle diagnostics.
+ -- When the buffer is loaded, we'll call on_attach, which sends textDocument/didOpen.
+ -- This should trigger another publish of the diagnostics.
+ --
+ -- In particular, this stops a ton of spam when first starting a server for current
+ -- unloaded buffers.
+ if not api.nvim_buf_is_loaded(bufnr) then
+ return
+ end
+
+ util.buf_clear_diagnostics(bufnr)
+
+ -- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#diagnostic
+ -- The diagnostic's severity. Can be omitted. If omitted it is up to the
+ -- client to interpret diagnostics as error, warning, info or hint.
+ -- TODO: Replace this with server-specific heuristics to infer severity.
+ for _, diagnostic in ipairs(result.diagnostics) do
+ if diagnostic.severity == nil then
+ diagnostic.severity = protocol.DiagnosticSeverity.Error
+ end
+ end
+
+ util.buf_diagnostics_save_positions(bufnr, result.diagnostics)
+ util.buf_diagnostics_underline(bufnr, result.diagnostics)
+ util.buf_diagnostics_virtual_text(bufnr, result.diagnostics)
+ util.buf_diagnostics_signs(bufnr, result.diagnostics)
+ vim.api.nvim_command("doautocmd User LspDiagnosticsChanged")
+end
+
+M['textDocument/references'] = function(_, _, result)
+ if not result then return end
+ util.set_qflist(util.locations_to_items(result))
+ api.nvim_command("copen")
+ api.nvim_command("wincmd p")
+end
+
+local symbol_callback = function(_, _, result, _, bufnr)
+ if not result or vim.tbl_isempty(result) then return end
+
+ util.set_qflist(util.symbols_to_items(result, bufnr))
+ api.nvim_command("copen")
+ api.nvim_command("wincmd p")
+end
+M['textDocument/documentSymbol'] = symbol_callback
+M['workspace/symbol'] = symbol_callback
+
+M['textDocument/rename'] = function(_, _, result)
+ if not result then return end
+ util.apply_workspace_edit(result)
+end
+
+M['textDocument/rangeFormatting'] = function(_, _, result)
+ if not result then return end
+ util.apply_text_edits(result)
+end
+
+M['textDocument/formatting'] = function(_, _, result)
+ if not result then return end
+ util.apply_text_edits(result)
+end
+
+M['textDocument/completion'] = function(_, _, result)
+ if vim.tbl_isempty(result or {}) then return end
+ local row, col = unpack(api.nvim_win_get_cursor(0))
+ local line = assert(api.nvim_buf_get_lines(0, row-1, row, false)[1])
+ local line_to_cursor = line:sub(col+1)
+ local textMatch = vim.fn.match(line_to_cursor, '\\k*$')
+ local prefix = line_to_cursor:sub(textMatch+1)
+
+ local matches = util.text_document_completion_list_to_complete_items(result, prefix)
+ vim.fn.complete(textMatch+1, matches)
+end
+
+M['textDocument/hover'] = function(_, method, result)
+ util.focusable_float(method, function()
+ if not (result and result.contents) then
+ -- return { 'No information available' }
+ 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
+ -- return { 'No information available' }
+ return
+ end
+ local bufnr, winnr = util.fancy_floating_markdown(markdown_lines, {
+ pad_left = 1; pad_right = 1;
+ })
+ util.close_preview_autocmd({"CursorMoved", "BufHidden", "InsertCharPre"}, winnr)
+ return bufnr, winnr
+ end)
+end
+
+local function location_callback(_, method, result)
+ if result == nil or vim.tbl_isempty(result) then
+ local _ = log.info() and log.info(method, 'No location found')
+ return nil
+ end
+
+ -- textDocument/definition can return Location or Location[]
+ -- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_definition
+
+ if vim.tbl_islist(result) then
+ util.jump_to_location(result[1])
+
+ if #result > 1 then
+ util.set_qflist(util.locations_to_items(result))
+ api.nvim_command("copen")
+ api.nvim_command("wincmd p")
+ end
+ else
+ util.jump_to_location(result)
+ end
+end
+
+M['textDocument/declaration'] = location_callback
+M['textDocument/definition'] = location_callback
+M['textDocument/typeDefinition'] = location_callback
+M['textDocument/implementation'] = location_callback
+
+M['textDocument/signatureHelp'] = function(_, method, result)
+ util.focusable_preview(method, function()
+ if not (result and result.signatures and result.signatures[1]) then
+ return { 'No signature available' }
+ end
+ -- TODO show popup when signatures is empty?
+ local lines = util.convert_signature_help_to_markdown_lines(result)
+ lines = util.trim_empty_lines(lines)
+ if vim.tbl_isempty(lines) then
+ return { 'No signature available' }
+ end
+ return lines, util.try_trim_markdown_code_blocks(lines)
+ end)
+end
+
+M['textDocument/documentHighlight'] = function(_, _, result, _)
+ if not result then return end
+ local bufnr = api.nvim_get_current_buf()
+ util.buf_highlight_references(bufnr, result)
+end
+
+-- direction is "from" for incoming calls and "to" for outgoing calls
+local make_call_hierarchy_callback = function(direction)
+ -- result is a CallHierarchy{Incoming,Outgoing}Call[]
+ return function(_, _, result)
+ if not result then return end
+ local items = {}
+ for _, call_hierarchy_call in pairs(result) do
+ local call_hierarchy_item = call_hierarchy_call[direction]
+ for _, range in pairs(call_hierarchy_call.fromRanges) do
+ table.insert(items, {
+ filename = assert(vim.uri_to_fname(call_hierarchy_item.uri)),
+ text = call_hierarchy_item.name,
+ lnum = range.start.line + 1,
+ col = range.start.character + 1,
+ })
+ end
+ end
+ util.set_qflist(items)
+ api.nvim_command("copen")
+ api.nvim_command("wincmd p")
+ end
+end
+
+M['callHierarchy/incomingCalls'] = make_call_hierarchy_callback('from')
+
+M['callHierarchy/outgoingCalls'] = make_call_hierarchy_callback('to')
+
+M['window/logMessage'] = function(_, _, result, client_id)
+ local message_type = result.type
+ local message = result.message
+ 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 the message")
+ end
+ if message_type == protocol.MessageType.Error then
+ log.error(message)
+ elseif message_type == protocol.MessageType.Warning then
+ log.warn(message)
+ elseif message_type == protocol.MessageType.Info then
+ log.info(message)
+ else
+ log.debug(message)
+ end
+ return result
+end
+
+M['window/showMessage'] = function(_, _, result, client_id)
+ local message_type = result.type
+ local message = result.message
+ 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 the message")
+ end
+ if message_type == protocol.MessageType.Error then
+ err_message("LSP[", client_name, "] ", message)
+ else
+ local message_type_name = protocol.MessageType[message_type]
+ api.nvim_out_write(string.format("LSP[%s][%s] %s\n", client_name, message_type_name, message))
+ end
+ return result
+end
+
+-- Add boilerplate error validation and logging for all of these.
+for k, fn in pairs(M) do
+ M[k] = function(err, method, params, client_id, bufnr)
+ log.debug('default_callback', method, { params = params, client_id = client_id, err = err, bufnr = bufnr })
+ if err then
+ error(tostring(err))
+ end
+ return fn(err, method, params, client_id, bufnr)
+ end
+end
+
+return M
+-- vim:sw=2 ts=2 et
diff --git a/runtime/lua/vim/lsp/log.lua b/runtime/lua/vim/lsp/log.lua
new file mode 100644
index 0000000000..696ce43a59
--- /dev/null
+++ b/runtime/lua/vim/lsp/log.lua
@@ -0,0 +1,94 @@
+-- Logger for language client plugin.
+
+local log = {}
+
+-- 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'
+-- Level numbers begin with 'trace' at 0
+log.levels = {
+ TRACE = 0;
+ DEBUG = 1;
+ INFO = 2;
+ WARN = 3;
+ ERROR = 4;
+}
+
+-- Default log level is warn.
+local current_log_level = log.levels.WARN
+local log_date_format = "%FT%H:%M:%S%z"
+
+do
+ local path_sep = vim.loop.os_uname().sysname == "Windows" and "\\" or "/"
+ local function path_join(...)
+ return table.concat(vim.tbl_flatten{...}, path_sep)
+ end
+ local logfilename = path_join(vim.fn.stdpath('data'), 'lsp.log')
+
+ --- Return the log filename.
+ function log.get_filename()
+ return logfilename
+ end
+
+ vim.fn.mkdir(vim.fn.stdpath('data'), "p")
+ local logfile = assert(io.open(logfilename, "a+"))
+ for level, levelnr in pairs(log.levels) do
+ -- Also export the log level on the root object.
+ log[level] = levelnr
+ -- Set the lowercase name as the main use function.
+ -- If called without arguments, it will check whether the log level is
+ -- greater than or equal to this one. When called with arguments, it will
+ -- log at that level (if applicable, it is checked either way).
+ --
+ -- Recommended usage:
+ -- ```
+ -- local _ = log.warn() and log.warn("123")
+ -- ```
+ --
+ -- This way you can avoid string allocations if the log level isn't high enough.
+ log[level:lower()] = function(...)
+ local argc = select("#", ...)
+ if levelnr < current_log_level then return false end
+ if argc == 0 then return true end
+ local info = debug.getinfo(2, "Sl")
+ local fileinfo = string.format("%s:%s", info.short_src, info.currentline)
+ local parts = { table.concat({"[", level, "]", os.date(log_date_format), "]", fileinfo, "]"}, " ") }
+ for i = 1, argc do
+ local arg = select(i, ...)
+ if arg == nil then
+ table.insert(parts, "nil")
+ else
+ table.insert(parts, vim.inspect(arg, {newline=''}))
+ end
+ end
+ logfile:write(table.concat(parts, '\t'), "\n")
+ logfile:flush()
+ end
+ end
+ -- Add some space to make it easier to distinguish different neovim runs.
+ logfile:write("\n")
+end
+
+-- This is put here on purpose after the loop above so that it doesn't
+-- interfere with iterating the levels
+vim.tbl_add_reverse_lookup(log.levels)
+
+function log.set_level(level)
+ if type(level) == 'string' then
+ current_log_level = assert(log.levels[level:upper()], string.format("Invalid log level: %q", level))
+ else
+ assert(type(level) == 'number', "level must be a number or string")
+ assert(log.levels[level], string.format("Invalid log level: %d", level))
+ current_log_level = level
+ end
+end
+
+-- Return whether the level is sufficient for logging.
+-- @param level number log level
+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
new file mode 100644
index 0000000000..ef5e08680e
--- /dev/null
+++ b/runtime/lua/vim/lsp/protocol.lua
@@ -0,0 +1,988 @@
+-- Protocol for the Microsoft Language Server Protocol (mslsp)
+
+local protocol = {}
+
+local function ifnil(a, b)
+ if a == nil then return b end
+ return a
+end
+
+
+--[=[
+-- Useful for interfacing with:
+-- https://github.com/microsoft/language-server-protocol/raw/gh-pages/_specifications/specification-3-14.md
+function transform_schema_comments()
+ nvim.command [[silent! '<,'>g/\/\*\*\|\*\/\|^$/d]]
+ nvim.command [[silent! '<,'>s/^\(\s*\) \* \=\(.*\)/\1--\2/]]
+end
+function transform_schema_to_table()
+ transform_schema_comments()
+ nvim.command [[silent! '<,'>s/: \S\+//]]
+ nvim.command [[silent! '<,'>s/export const //]]
+ nvim.command [[silent! '<,'>s/export namespace \(\S*\)\s*{/protocol.\1 = {/]]
+ nvim.command [[silent! '<,'>s/namespace \(\S*\)\s*{/protocol.\1 = {/]]
+end
+--]=]
+
+local constants = {
+ DiagnosticSeverity = {
+ -- Reports an error.
+ Error = 1;
+ -- Reports a warning.
+ Warning = 2;
+ -- Reports an information.
+ Information = 3;
+ -- Reports a hint.
+ Hint = 4;
+ };
+
+ MessageType = {
+ -- An error message.
+ Error = 1;
+ -- A warning message.
+ Warning = 2;
+ -- An information message.
+ Info = 3;
+ -- A log message.
+ Log = 4;
+ };
+
+ -- The file event type.
+ FileChangeType = {
+ -- The file got created.
+ Created = 1;
+ -- The file got changed.
+ Changed = 2;
+ -- The file got deleted.
+ Deleted = 3;
+ };
+
+ -- The kind of a completion entry.
+ CompletionItemKind = {
+ Text = 1;
+ Method = 2;
+ Function = 3;
+ Constructor = 4;
+ Field = 5;
+ Variable = 6;
+ Class = 7;
+ Interface = 8;
+ Module = 9;
+ Property = 10;
+ Unit = 11;
+ Value = 12;
+ Enum = 13;
+ Keyword = 14;
+ Snippet = 15;
+ Color = 16;
+ File = 17;
+ Reference = 18;
+ Folder = 19;
+ EnumMember = 20;
+ Constant = 21;
+ Struct = 22;
+ Event = 23;
+ Operator = 24;
+ TypeParameter = 25;
+ };
+
+ -- How a completion was triggered
+ CompletionTriggerKind = {
+ -- Completion was triggered by typing an identifier (24x7 code
+ -- complete), manual invocation (e.g Ctrl+Space) or via API.
+ Invoked = 1;
+ -- Completion was triggered by a trigger character specified by
+ -- the `triggerCharacters` properties of the `CompletionRegistrationOptions`.
+ TriggerCharacter = 2;
+ -- Completion was re-triggered as the current completion list is incomplete.
+ TriggerForIncompleteCompletions = 3;
+ };
+
+ -- A document highlight kind.
+ DocumentHighlightKind = {
+ -- A textual occurrence.
+ Text = 1;
+ -- Read-access of a symbol, like reading a variable.
+ Read = 2;
+ -- Write-access of a symbol, like writing to a variable.
+ Write = 3;
+ };
+
+ -- A symbol kind.
+ SymbolKind = {
+ File = 1;
+ Module = 2;
+ Namespace = 3;
+ Package = 4;
+ Class = 5;
+ Method = 6;
+ Property = 7;
+ Field = 8;
+ Constructor = 9;
+ Enum = 10;
+ Interface = 11;
+ Function = 12;
+ Variable = 13;
+ Constant = 14;
+ String = 15;
+ Number = 16;
+ Boolean = 17;
+ Array = 18;
+ Object = 19;
+ Key = 20;
+ Null = 21;
+ EnumMember = 22;
+ Struct = 23;
+ Event = 24;
+ Operator = 25;
+ TypeParameter = 26;
+ };
+
+ -- Represents reasons why a text document is saved.
+ TextDocumentSaveReason = {
+ -- Manually triggered, e.g. by the user pressing save, by starting debugging,
+ -- or by an API call.
+ Manual = 1;
+ -- Automatic after a delay.
+ AfterDelay = 2;
+ -- When the editor lost focus.
+ FocusOut = 3;
+ };
+
+ ErrorCodes = {
+ -- Defined by JSON RPC
+ ParseError = -32700;
+ InvalidRequest = -32600;
+ MethodNotFound = -32601;
+ InvalidParams = -32602;
+ InternalError = -32603;
+ serverErrorStart = -32099;
+ serverErrorEnd = -32000;
+ ServerNotInitialized = -32002;
+ UnknownErrorCode = -32001;
+ -- Defined by the protocol.
+ RequestCancelled = -32800;
+ ContentModified = -32801;
+ };
+
+ -- 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.
+ MarkupKind = {
+ -- Plain text is supported as a content format
+ PlainText = 'plaintext';
+ -- Markdown is supported as a content format
+ Markdown = 'markdown';
+ };
+
+ ResourceOperationKind = {
+ -- Supports creating new files and folders.
+ Create = 'create';
+ -- Supports renaming existing files and folders.
+ Rename = 'rename';
+ -- Supports deleting existing files and folders.
+ Delete = 'delete';
+ };
+
+ FailureHandlingKind = {
+ -- Applying the workspace change is simply aborted if one of the changes provided
+ -- fails. All operations executed before the failing operation stay executed.
+ Abort = 'abort';
+ -- All operations are executed transactionally. That means they either all
+ -- succeed or no changes at all are applied to the workspace.
+ Transactional = 'transactional';
+ -- If the workspace edit contains only textual file changes they are executed transactionally.
+ -- If resource changes (create, rename or delete file) are part of the change the failure
+ -- handling strategy is abort.
+ TextOnlyTransactional = 'textOnlyTransactional';
+ -- The client tries to undo the operations already executed. But there is no
+ -- guarantee that this succeeds.
+ Undo = 'undo';
+ };
+
+ -- Known error codes for an `InitializeError`;
+ InitializeError = {
+ -- If the protocol version provided by the client can't be handled by the server.
+ -- @deprecated This initialize error got replaced by client capabilities. There is
+ -- no version handshake in version 3.0x
+ unknownProtocolVersion = 1;
+ };
+
+ -- Defines how the host (editor) should sync document changes to the language server.
+ TextDocumentSyncKind = {
+ -- Documents should not be synced at all.
+ None = 0;
+ -- Documents are synced by always sending the full content
+ -- of the document.
+ Full = 1;
+ -- Documents are synced by sending the full content on open.
+ -- After that only incremental updates to the document are
+ -- send.
+ Incremental = 2;
+ };
+
+ WatchKind = {
+ -- Interested in create events.
+ Create = 1;
+ -- Interested in change events
+ Change = 2;
+ -- Interested in delete events
+ Delete = 4;
+ };
+
+ -- Defines whether the insert text in a completion item should be interpreted as
+ -- plain text or a snippet.
+ InsertTextFormat = {
+ -- The primary text to be inserted is treated as a plain string.
+ PlainText = 1;
+ -- The primary text to be inserted is treated as a snippet.
+ --
+ -- 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.
+ Snippet = 2;
+ };
+
+ -- A set of predefined code action kinds
+ CodeActionKind = {
+ -- Empty kind.
+ Empty = '';
+ -- Base kind for quickfix actions
+ QuickFix = 'quickfix';
+ -- Base kind for refactoring actions
+ Refactor = 'refactor';
+ -- Base kind for refactoring extraction actions
+ --
+ -- Example extract actions:
+ --
+ -- - Extract method
+ -- - Extract function
+ -- - Extract variable
+ -- - Extract interface from class
+ -- - ...
+ RefactorExtract = 'refactor.extract';
+ -- Base kind for refactoring inline actions
+ --
+ -- Example inline actions:
+ --
+ -- - Inline function
+ -- - Inline variable
+ -- - Inline constant
+ -- - ...
+ RefactorInline = 'refactor.inline';
+ -- Base kind for refactoring rewrite actions
+ --
+ -- Example rewrite actions:
+ --
+ -- - Convert JavaScript function to class
+ -- - Add or remove parameter
+ -- - Encapsulate field
+ -- - Make method static
+ -- - Move method to base class
+ -- - ...
+ RefactorRewrite = 'refactor.rewrite';
+ -- Base kind for source actions
+ --
+ -- Source code actions apply to the entire file.
+ Source = 'source';
+ -- Base kind for an organize imports source action
+ SourceOrganizeImports = 'source.organizeImports';
+ };
+}
+
+for k, v in pairs(constants) do
+ vim.tbl_add_reverse_lookup(v)
+ protocol[k] = v
+end
+
+--[=[
+--Text document specific client capabilities.
+export interface TextDocumentClientCapabilities {
+ synchronization?: {
+ --Whether text document synchronization supports dynamic registration.
+ dynamicRegistration?: boolean;
+ --The client supports sending will save notifications.
+ 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.
+ willSaveWaitUntil?: boolean;
+ --The client supports did save notifications.
+ didSave?: boolean;
+ }
+ --Capabilities specific to the `textDocument/completion`
+ completion?: {
+ --Whether completion supports dynamic registration.
+ dynamicRegistration?: boolean;
+ --The client supports the following `CompletionItem` specific
+ --capabilities.
+ completionItem?: {
+ --The 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.
+ snippetSupport?: boolean;
+ --The client supports commit characters on a completion item.
+ commitCharactersSupport?: boolean
+ --The client supports the following content formats for the documentation
+ --property. The order describes the preferred format of the client.
+ documentationFormat?: MarkupKind[];
+ --The client supports the deprecated property on a completion item.
+ deprecatedSupport?: boolean;
+ --The client supports the preselect property on a completion item.
+ preselectSupport?: boolean;
+ }
+ completionItemKind?: {
+ --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.
+ valueSet?: CompletionItemKind[];
+ },
+ --The client supports to send additional context information for a
+ --`textDocument/completion` request.
+ contextSupport?: boolean;
+ };
+ --Capabilities specific to the `textDocument/hover`
+ hover?: {
+ --Whether hover supports dynamic registration.
+ dynamicRegistration?: boolean;
+ --The client supports the follow content formats for the content
+ --property. The order describes the preferred format of the client.
+ contentFormat?: MarkupKind[];
+ };
+ --Capabilities specific to the `textDocument/signatureHelp`
+ signatureHelp?: {
+ --Whether signature help supports dynamic registration.
+ dynamicRegistration?: boolean;
+ --The client supports the following `SignatureInformation`
+ --specific properties.
+ signatureInformation?: {
+ --The client supports the follow content formats for the documentation
+ --property. The order describes the preferred format of the client.
+ documentationFormat?: MarkupKind[];
+ --Client capabilities specific to parameter information.
+ parameterInformation?: {
+ --The client supports processing label offsets instead of a
+ --simple label string.
+ --
+ --Since 3.14.0
+ labelOffsetSupport?: boolean;
+ }
+ };
+ };
+ --Capabilities specific to the `textDocument/references`
+ references?: {
+ --Whether references supports dynamic registration.
+ dynamicRegistration?: boolean;
+ };
+ --Capabilities specific to the `textDocument/documentHighlight`
+ documentHighlight?: {
+ --Whether document highlight supports dynamic registration.
+ dynamicRegistration?: boolean;
+ };
+ --Capabilities specific to the `textDocument/documentSymbol`
+ documentSymbol?: {
+ --Whether document symbol supports dynamic registration.
+ dynamicRegistration?: boolean;
+ --Specific capabilities for the `SymbolKind`.
+ symbolKind?: {
+ --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.
+ valueSet?: SymbolKind[];
+ }
+ --The client supports hierarchical document symbols.
+ hierarchicalDocumentSymbolSupport?: boolean;
+ };
+ --Capabilities specific to the `textDocument/formatting`
+ formatting?: {
+ --Whether formatting supports dynamic registration.
+ dynamicRegistration?: boolean;
+ };
+ --Capabilities specific to the `textDocument/rangeFormatting`
+ rangeFormatting?: {
+ --Whether range formatting supports dynamic registration.
+ dynamicRegistration?: boolean;
+ };
+ --Capabilities specific to the `textDocument/onTypeFormatting`
+ onTypeFormatting?: {
+ --Whether on type formatting supports dynamic registration.
+ dynamicRegistration?: boolean;
+ };
+ --Capabilities specific to the `textDocument/declaration`
+ declaration?: {
+ --Whether declaration 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.
+ dynamicRegistration?: boolean;
+ --The client supports additional metadata in the form of declaration links.
+ --
+ --Since 3.14.0
+ linkSupport?: boolean;
+ };
+ --Capabilities specific to the `textDocument/definition`.
+ --
+ --Since 3.14.0
+ definition?: {
+ --Whether definition supports dynamic registration.
+ dynamicRegistration?: boolean;
+ --The client supports additional metadata in the form of definition links.
+ linkSupport?: boolean;
+ };
+ --Capabilities specific to the `textDocument/typeDefinition`
+ --
+ --Since 3.6.0
+ typeDefinition?: {
+ --Whether typeDefinition 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.
+ dynamicRegistration?: boolean;
+ --The client supports additional metadata in the form of definition links.
+ --
+ --Since 3.14.0
+ linkSupport?: boolean;
+ };
+ --Capabilities specific to the `textDocument/implementation`.
+ --
+ --Since 3.6.0
+ implementation?: {
+ --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.
+ dynamicRegistration?: boolean;
+ --The client supports additional metadata in the form of definition links.
+ --
+ --Since 3.14.0
+ linkSupport?: boolean;
+ };
+ --Capabilities specific to the `textDocument/codeAction`
+ codeAction?: {
+ --Whether code action supports dynamic registration.
+ dynamicRegistration?: boolean;
+ --The client support code action literals as a valid
+ --response of the `textDocument/codeAction` request.
+ --
+ --Since 3.8.0
+ codeActionLiteralSupport?: {
+ --The code action kind is support with the following value
+ --set.
+ codeActionKind: {
+ --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.
+ valueSet: CodeActionKind[];
+ };
+ };
+ };
+ --Capabilities specific to the `textDocument/codeLens`
+ codeLens?: {
+ --Whether code lens supports dynamic registration.
+ dynamicRegistration?: boolean;
+ };
+ --Capabilities specific to the `textDocument/documentLink`
+ documentLink?: {
+ --Whether document link supports dynamic registration.
+ dynamicRegistration?: boolean;
+ };
+ --Capabilities specific to the `textDocument/documentColor` and the
+ --`textDocument/colorPresentation` request.
+ --
+ --Since 3.6.0
+ colorProvider?: {
+ --Whether colorProvider supports dynamic registration. If this is set to `true`
+ --the client supports the new `(ColorProviderOptions & TextDocumentRegistrationOptions & StaticRegistrationOptions)`
+ --return value for the corresponding server capability as well.
+ dynamicRegistration?: boolean;
+ }
+ --Capabilities specific to the `textDocument/rename`
+ rename?: {
+ --Whether rename supports dynamic registration.
+ dynamicRegistration?: boolean;
+ --The client supports testing for validity of rename operations
+ --before execution.
+ prepareSupport?: boolean;
+ };
+ --Capabilities specific to `textDocument/publishDiagnostics`.
+ publishDiagnostics?: {
+ --Whether the clients accepts diagnostics with related information.
+ relatedInformation?: boolean;
+ };
+ --Capabilities specific to `textDocument/foldingRange` requests.
+ --
+ --Since 3.10.0
+ foldingRange?: {
+ --Whether implementation supports dynamic registration for folding range providers. If this is set to `true`
+ --the client supports the new `(FoldingRangeProviderOptions & TextDocumentRegistrationOptions & StaticRegistrationOptions)`
+ --return value for the corresponding server capability as well.
+ 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.
+ rangeLimit?: number;
+ --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.
+ lineFoldingOnly?: boolean;
+ };
+}
+--]=]
+
+--[=[
+--Workspace specific client capabilities.
+export interface WorkspaceClientCapabilities {
+ --The client supports applying batch edits to the workspace by supporting
+ --the request 'workspace/applyEdit'
+ applyEdit?: boolean;
+ --Capabilities specific to `WorkspaceEdit`s
+ workspaceEdit?: {
+ --The client supports versioned document changes in `WorkspaceEdit`s
+ documentChanges?: boolean;
+ --The resource operations the client supports. Clients should at least
+ --support 'create', 'rename' and 'delete' files and folders.
+ resourceOperations?: ResourceOperationKind[];
+ --The failure handling strategy of a client if applying the workspace edit
+ --fails.
+ failureHandling?: FailureHandlingKind;
+ };
+ --Capabilities specific to the `workspace/didChangeConfiguration` notification.
+ didChangeConfiguration?: {
+ --Did change configuration notification supports dynamic registration.
+ dynamicRegistration?: boolean;
+ };
+ --Capabilities specific to the `workspace/didChangeWatchedFiles` notification.
+ didChangeWatchedFiles?: {
+ --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.
+ dynamicRegistration?: boolean;
+ };
+ --Capabilities specific to the `workspace/symbol` request.
+ symbol?: {
+ --Symbol request supports dynamic registration.
+ dynamicRegistration?: boolean;
+ --Specific capabilities for the `SymbolKind` in the `workspace/symbol` request.
+ symbolKind?: {
+ --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.
+ valueSet?: SymbolKind[];
+ }
+ };
+ --Capabilities specific to the `workspace/executeCommand` request.
+ executeCommand?: {
+ --Execute command supports dynamic registration.
+ dynamicRegistration?: boolean;
+ };
+ --The client has support for workspace folders.
+ --
+ --Since 3.6.0
+ workspaceFolders?: boolean;
+ --The client supports `workspace/configuration` requests.
+ --
+ --Since 3.6.0
+ configuration?: boolean;
+}
+--]=]
+
+--- Gets a new ClientCapabilities object describing the LSP client
+--- capabilities.
+function protocol.make_client_capabilities()
+ return {
+ textDocument = {
+ synchronization = {
+ dynamicRegistration = false;
+
+ -- TODO(ashkan) Send textDocument/willSave before saving (BufWritePre)
+ willSave = false;
+
+ -- TODO(ashkan) Implement textDocument/willSaveWaitUntil
+ willSaveWaitUntil = false;
+
+ -- Send textDocument/didSave after saving (BufWritePost)
+ didSave = true;
+ };
+ codeAction = {
+ dynamicRegistration = false;
+
+ codeActionLiteralSupport = {
+ codeActionKind = {
+ valueSet = {};
+ };
+ };
+ };
+ completion = {
+ dynamicRegistration = false;
+ completionItem = {
+
+ snippetSupport = true;
+ commitCharactersSupport = false;
+ preselectSupport = false;
+ deprecatedSupport = false;
+ documentationFormat = { protocol.MarkupKind.Markdown; protocol.MarkupKind.PlainText };
+ };
+ completionItemKind = {
+ valueSet = (function()
+ local res = {}
+ for k in pairs(protocol.CompletionItemKind) do
+ if type(k) == 'number' then table.insert(res, k) end
+ end
+ return res
+ end)();
+ };
+
+ -- TODO(tjdevries): Implement this
+ contextSupport = false;
+ };
+ declaration = {
+ linkSupport = true;
+ };
+ definition = {
+ linkSupport = true;
+ };
+ implementation = {
+ linkSupport = true;
+ };
+ typeDefinition = {
+ linkSupport = true;
+ };
+ hover = {
+ dynamicRegistration = false;
+ contentFormat = { protocol.MarkupKind.Markdown; protocol.MarkupKind.PlainText };
+ };
+ signatureHelp = {
+ dynamicRegistration = false;
+ signatureInformation = {
+ documentationFormat = { protocol.MarkupKind.Markdown; protocol.MarkupKind.PlainText };
+ -- parameterInformation = {
+ -- labelOffsetSupport = false;
+ -- };
+ };
+ };
+ references = {
+ dynamicRegistration = false;
+ };
+ documentHighlight = {
+ dynamicRegistration = false
+ };
+ documentSymbol = {
+ dynamicRegistration = false;
+ symbolKind = {
+ valueSet = (function()
+ local res = {}
+ for k in pairs(protocol.SymbolKind) do
+ if type(k) == 'number' then table.insert(res, k) end
+ end
+ return res
+ end)();
+ };
+ hierarchicalDocumentSymbolSupport = true;
+ };
+ };
+ workspace = {
+ symbol = {
+ dynamicRegistration = false;
+ symbolKind = {
+ valueSet = (function()
+ local res = {}
+ for k in pairs(protocol.SymbolKind) do
+ if type(k) == 'number' then table.insert(res, k) end
+ end
+ return res
+ end)();
+ };
+ hierarchicalWorkspaceSymbolSupport = true;
+ };
+ applyEdit = true;
+ };
+ callHierarchy = {
+ dynamicRegistration = false;
+ };
+ experimental = nil;
+ }
+end
+
+--[=[
+export interface DocumentFilter {
+ --A language id, like `typescript`.
+ language?: string;
+ --A Uri [scheme](#Uri.scheme), like `file` or `untitled`.
+ scheme?: string;
+ --A glob pattern, like `*.{ts,js}`.
+ --
+ --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`)
+ pattern?: string;
+}
+--]=]
+
+--[[
+--Static registration options to be returned in the initialize request.
+interface StaticRegistrationOptions {
+ --The id used to register the request. The id can be used to deregister
+ --the request again. See also Registration#id.
+ id?: string;
+}
+
+export interface DocumentFilter {
+ --A language id, like `typescript`.
+ language?: string;
+ --A Uri [scheme](#Uri.scheme), like `file` or `untitled`.
+ scheme?: string;
+ --A glob pattern, like `*.{ts,js}`.
+ --
+ --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`)
+ pattern?: string;
+}
+export type DocumentSelector = DocumentFilter[];
+export interface 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.
+ documentSelector: DocumentSelector | null;
+}
+
+--Code Action options.
+export interface 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.
+ codeActionKinds?: CodeActionKind[];
+}
+
+interface ServerCapabilities {
+ --Defines how text documents are synced. Is either a detailed structure defining each notification or
+ --for backwards compatibility the TextDocumentSyncKind number. If omitted it defaults to `TextDocumentSyncKind.None`.
+ textDocumentSync?: TextDocumentSyncOptions | number;
+ --The server provides hover support.
+ hoverProvider?: boolean;
+ --The server provides completion support.
+ completionProvider?: CompletionOptions;
+ --The server provides signature help support.
+ signatureHelpProvider?: SignatureHelpOptions;
+ --The server provides goto definition support.
+ definitionProvider?: boolean;
+ --The server provides Goto Type Definition support.
+ --
+ --Since 3.6.0
+ typeDefinitionProvider?: boolean | (TextDocumentRegistrationOptions & StaticRegistrationOptions);
+ --The server provides Goto Implementation support.
+ --
+ --Since 3.6.0
+ implementationProvider?: boolean | (TextDocumentRegistrationOptions & StaticRegistrationOptions);
+ --The server provides find references support.
+ referencesProvider?: boolean;
+ --The server provides document highlight support.
+ documentHighlightProvider?: boolean;
+ --The server provides document symbol support.
+ documentSymbolProvider?: boolean;
+ --The server provides workspace symbol support.
+ workspaceSymbolProvider?: boolean;
+ --The server provides code actions. The `CodeActionOptions` return type is only
+ --valid if the client signals code action literal support via the property
+ --`textDocument.codeAction.codeActionLiteralSupport`.
+ codeActionProvider?: boolean | CodeActionOptions;
+ --The server provides code lens.
+ codeLensProvider?: CodeLensOptions;
+ --The server provides document formatting.
+ documentFormattingProvider?: boolean;
+ --The server provides document range formatting.
+ documentRangeFormattingProvider?: boolean;
+ --The server provides document formatting on typing.
+ documentOnTypeFormattingProvider?: DocumentOnTypeFormattingOptions;
+ --The server provides rename support. RenameOptions may only be
+ --specified if the client states that it supports
+ --`prepareSupport` in its initial `initialize` request.
+ renameProvider?: boolean | RenameOptions;
+ --The server provides document link support.
+ documentLinkProvider?: DocumentLinkOptions;
+ --The server provides color provider support.
+ --
+ --Since 3.6.0
+ colorProvider?: boolean | ColorProviderOptions | (ColorProviderOptions & TextDocumentRegistrationOptions & StaticRegistrationOptions);
+ --The server provides folding provider support.
+ --
+ --Since 3.10.0
+ foldingRangeProvider?: boolean | FoldingRangeProviderOptions | (FoldingRangeProviderOptions & TextDocumentRegistrationOptions & StaticRegistrationOptions);
+ --The server provides go to declaration support.
+ --
+ --Since 3.14.0
+ declarationProvider?: boolean | (TextDocumentRegistrationOptions & StaticRegistrationOptions);
+ --The server provides execute command support.
+ executeCommandProvider?: ExecuteCommandOptions;
+ --Workspace specific server capabilities
+ workspace?: {
+ --The server supports workspace folder.
+ --
+ --Since 3.6.0
+ workspaceFolders?: {
+ * The server has support for workspace folders
+ supported?: boolean;
+ * Whether the server wants to receive workspace folder
+ * change notifications.
+ *
+ * If a strings is provided the string is treated as a 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.
+ changeNotifications?: string | boolean;
+ }
+ }
+ --Experimental server capabilities.
+ experimental?: any;
+}
+--]]
+
+--- Creates a normalized object describing LSP server capabilities.
+function protocol.resolve_capabilities(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 = false;
+ text_document_save_include_text = false;
+ }
+ elseif type(textDocumentSync) == 'table' then
+ text_document_sync_properties = {
+ text_document_open_close = ifnil(textDocumentSync.openClose, false);
+ text_document_did_change = ifnil(textDocumentSync.change, TextDocumentSyncKind.None);
+ text_document_will_save = ifnil(textDocumentSync.willSave, false);
+ text_document_will_save_wait_until = ifnil(textDocumentSync.willSaveWaitUntil, false);
+ text_document_save = ifnil(textDocumentSync.save, false);
+ text_document_save_include_text = ifnil(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.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
+
+ if server_capabilities.codeActionProvider == nil then
+ general_properties.code_action = false
+ elseif type(server_capabilities.codeActionProvider) == 'boolean' then
+ general_properties.code_action = server_capabilities.codeActionProvider
+ elseif type(server_capabilities.codeActionProvider) == 'table' then
+ -- TODO(ashkan) support CodeActionKind
+ general_properties.code_action = false
+ 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
+ -- TODO: support more detailed declarationProvider options.
+ general_properties.declaration = false
+ 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
+ -- TODO: support more detailed typeDefinitionProvider options.
+ general_properties.type_definition = false
+ 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
+ -- TODO(ashkan) support more detailed implementation options.
+ general_properties.implementation = false
+ else
+ error("The server sent invalid implementationProvider")
+ 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
+
+ return vim.tbl_extend("error"
+ , text_document_sync_properties
+ , signature_help_properties
+ , general_properties
+ )
+end
+
+return protocol
+-- vim:sw=2 ts=2 et
diff --git a/runtime/lua/vim/lsp/rpc.lua b/runtime/lua/vim/lsp/rpc.lua
new file mode 100644
index 0000000000..81c92bfe05
--- /dev/null
+++ b/runtime/lua/vim/lsp/rpc.lua
@@ -0,0 +1,472 @@
+local vim = vim
+local uv = vim.loop
+local log = require('vim.lsp.log')
+local protocol = require('vim.lsp.protocol')
+local validate, schedule, schedule_wrap = vim.validate, vim.schedule, vim.schedule_wrap
+
+-- TODO replace with a better implementation.
+local function json_encode(data)
+ local status, result = pcall(vim.fn.json_encode, data)
+ if status then
+ return result
+ else
+ return nil, result
+ end
+end
+local function json_decode(data)
+ local status, result = pcall(vim.fn.json_decode, data)
+ if status then
+ return result
+ else
+ return nil, result
+ end
+end
+
+local function is_dir(filename)
+ local stat = vim.loop.fs_stat(filename)
+ return stat and stat.type == 'directory' or false
+end
+
+local NIL = vim.NIL
+local function convert_NIL(v)
+ if v == NIL then return nil end
+ return v
+end
+
+--- 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>
+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
+
+local function format_message_with_content_length(encoded_message)
+ return table.concat {
+ 'Content-Length: '; tostring(#encoded_message); '\r\n\r\n';
+ encoded_message;
+ }
+end
+
+--- Parse an LSP Message's header
+-- @param header: The header to parse.
+local function parse_headers(header)
+ if type(header) ~= 'string' then
+ return nil
+ end
+ local headers = {}
+ for line in vim.gsplit(header, '\r\n', true) do
+ if line == '' then
+ break
+ end
+ local key, value = line:match("^%s*(%S+)%s*:%s*(.+)%s*$")
+ if key then
+ key = key:lower():gsub('%-', '_')
+ headers[key] = value
+ else
+ local _ = log.error() and log.error("invalid header line %q", line)
+ error(string.format("invalid header line %q", line))
+ end
+ end
+ headers.content_length = tonumber(headers.content_length)
+ or error(string.format("Content-Length not found in headers. %q", header))
+ return headers
+end
+
+-- This is the start of any possible header patterns. The gsub converts it to a
+-- case insensitive pattern.
+local header_start_pattern = ("content"):gsub("%w", function(c) return "["..c..c:upper().."]" end)
+
+local function request_parser_loop()
+ local buffer = ''
+ while true do
+ -- A message can only be complete if it has a double CRLF and also the full
+ -- payload, so first let's check for the CRLFs
+ local start, finish = buffer:find('\r\n\r\n', 1, true)
+ -- Start parsing the headers
+ if start then
+ -- This is a workaround for servers sending initial garbage before
+ -- sending headers, such as if a bash script sends stdout. It assumes
+ -- that we know all of the headers ahead of time. At this moment, the
+ -- only valid headers start with "Content-*", so that's the thing we will
+ -- be searching for.
+ -- TODO(ashkan) I'd like to remove this, but it seems permanent :(
+ local buffer_start = buffer:find(header_start_pattern)
+ local headers = parse_headers(buffer:sub(buffer_start, start-1))
+ buffer = buffer:sub(finish+1)
+ local content_length = headers.content_length
+ -- Keep waiting for data until we have enough.
+ while #buffer < content_length do
+ buffer = buffer..(coroutine.yield()
+ or error("Expected more data for the body. The server may have died.")) -- TODO hmm.
+ end
+ local body = buffer:sub(1, content_length)
+ buffer = buffer:sub(content_length + 1)
+ -- Yield our data.
+ buffer = buffer..(coroutine.yield(headers, body)
+ or error("Expected more data for the body. The server may have died.")) -- TODO hmm.
+ else
+ -- Get more data since we don't have enough.
+ buffer = buffer..(coroutine.yield()
+ or error("Expected more data for the header. The server may have died.")) -- TODO hmm.
+ end
+ end
+end
+
+local client_errors = vim.tbl_add_reverse_lookup {
+ INVALID_SERVER_MESSAGE = 1;
+ INVALID_SERVER_JSON = 2;
+ NO_RESULT_CALLBACK_FOUND = 3;
+ READ_ERROR = 4;
+ NOTIFICATION_HANDLER_ERROR = 5;
+ SERVER_REQUEST_HANDLER_ERROR = 6;
+ SERVER_RESULT_CALLBACK_ERROR = 7;
+}
+
+local function format_rpc_error(err)
+ validate {
+ err = { err, 't' };
+ }
+
+ -- There is ErrorCodes in the LSP specification,
+ -- but in ResponseError.code it is not used and the actual type is number.
+ local code
+ if protocol.ErrorCodes[err.code] then
+ code = string.format("code_name = %s,", protocol.ErrorCodes[err.code])
+ else
+ code = string.format("code_name = unknown, code = %s,", err.code)
+ end
+
+ local message_parts = {"RPC[Error]", code}
+ if err.message then
+ table.insert(message_parts, "message =")
+ table.insert(message_parts, string.format("%q", err.message))
+ end
+ if err.data then
+ table.insert(message_parts, "data =")
+ table.insert(message_parts, vim.inspect(err.data))
+ end
+ return table.concat(message_parts, ' ')
+end
+
+--- Creates an RPC response object/table.
+---
+--@param code RPC error code defined in `vim.lsp.protocol.ErrorCodes`
+--@param message (optional) arbitrary message to send to server
+--@param data (optional) arbitrary data to send to server
+local function 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({
+ code = code;
+ message = message or code_name;
+ data = data;
+ }, {
+ __tostring = format_rpc_error;
+ })
+end
+
+local default_handlers = {}
+function default_handlers.notification(method, params)
+ local _ = log.debug() and log.debug('notification', method, params)
+end
+function default_handlers.server_request(method, params)
+ local _ = log.debug() and log.debug('server_request', method, params)
+ return nil, rpc_response_error(protocol.ErrorCodes.MethodNotFound)
+end
+function default_handlers.on_exit(code, signal)
+ local _ = log.info() and log.info("client exit", { code = code, signal = signal })
+end
+function default_handlers.on_error(code, err)
+ local _ = log.error() and log.error('client_error:', client_errors[code], err)
+end
+
+--- Create and start an RPC client.
+-- @param cmd [
+local function create_and_start_client(cmd, cmd_args, handlers, extra_spawn_params)
+ local _ = log.info() and log.info("Starting RPC client", {cmd = cmd, args = cmd_args, extra = extra_spawn_params})
+ validate {
+ cmd = { cmd, 's' };
+ cmd_args = { cmd_args, 't' };
+ handlers = { handlers, 't', true };
+ }
+
+ if not (vim.fn.executable(cmd) == 1) then
+ error(string.format("The given command %q is not executable.", cmd))
+ end
+ if handlers then
+ local user_handlers = handlers
+ handlers = {}
+ for handle_name, default_handler in pairs(default_handlers) do
+ local user_handler = user_handlers[handle_name]
+ if user_handler then
+ if type(user_handler) ~= 'function' then
+ error(string.format("handler.%s must be a function", handle_name))
+ end
+ -- server_request is wrapped elsewhere.
+ if not (handle_name == 'server_request'
+ or handle_name == 'on_exit') -- TODO this blocks the loop exiting for some reason.
+ then
+ user_handler = schedule_wrap(user_handler)
+ end
+ handlers[handle_name] = user_handler
+ else
+ handlers[handle_name] = default_handler
+ end
+ end
+ else
+ handlers = default_handlers
+ end
+
+ local stdin = uv.new_pipe(false)
+ local stdout = uv.new_pipe(false)
+ local stderr = uv.new_pipe(false)
+
+ local message_index = 0
+ local message_callbacks = {}
+
+ local handle, pid
+ do
+ local function onexit(code, signal)
+ stdin:close()
+ stdout:close()
+ stderr:close()
+ handle:close()
+ -- Make sure that message_callbacks can be gc'd.
+ message_callbacks = nil
+ handlers.on_exit(code, signal)
+ end
+ local spawn_params = {
+ args = cmd_args;
+ stdio = {stdin, stdout, stderr};
+ }
+ if extra_spawn_params then
+ spawn_params.cwd = extra_spawn_params.cwd
+ if spawn_params.cwd then
+ assert(is_dir(spawn_params.cwd), "cwd must be a directory")
+ end
+ spawn_params.env = env_merge(extra_spawn_params.env)
+ end
+ handle, pid = uv.spawn(cmd, spawn_params, onexit)
+ end
+
+ local function encode_and_send(payload)
+ local _ = log.debug() and log.debug("rpc.send.payload", payload)
+ if handle:is_closing() then return false end
+ -- TODO(ashkan) remove this once we have a Lua json_encode
+ schedule(function()
+ local encoded = assert(json_encode(payload))
+ stdin:write(format_message_with_content_length(encoded))
+ end)
+ return true
+ end
+
+ local function send_notification(method, params)
+ local _ = log.debug() and log.debug("rpc.notify", method, params)
+ return encode_and_send {
+ jsonrpc = "2.0";
+ method = method;
+ params = params;
+ }
+ end
+
+ local function send_response(request_id, err, result)
+ return encode_and_send {
+ id = request_id;
+ jsonrpc = "2.0";
+ error = err;
+ result = result;
+ }
+ end
+
+ local function send_request(method, params, callback)
+ validate {
+ callback = { callback, 'f' };
+ }
+ message_index = message_index + 1
+ local message_id = message_index
+ local result = encode_and_send {
+ id = message_id;
+ jsonrpc = "2.0";
+ method = method;
+ params = params;
+ }
+ if result then
+ message_callbacks[message_id] = schedule_wrap(callback)
+ return result, message_id
+ else
+ return false
+ end
+ end
+
+ stderr:read_start(function(_err, chunk)
+ if chunk then
+ local _ = log.error() and log.error("rpc", cmd, "stderr", chunk)
+ end
+ end)
+
+ local function on_error(errkind, ...)
+ assert(client_errors[errkind])
+ -- TODO what to do if this fails?
+ pcall(handlers.on_error, errkind, ...)
+ end
+ local function pcall_handler(errkind, status, head, ...)
+ if not status then
+ on_error(errkind, head, ...)
+ return status, head
+ end
+ return status, head, ...
+ end
+ local function try_call(errkind, fn, ...)
+ return pcall_handler(errkind, pcall(fn, ...))
+ end
+
+ -- TODO periodically check message_callbacks for old requests past a certain
+ -- time and log them. This would require storing the timestamp. I could call
+ -- them with an error then, perhaps.
+
+ local function handle_body(body)
+ local decoded, err = json_decode(body)
+ if not decoded then
+ on_error(client_errors.INVALID_SERVER_JSON, err)
+ return
+ end
+ local _ = log.debug() and log.debug("decoded", decoded)
+
+ if type(decoded.method) == 'string' and decoded.id then
+ -- Server Request
+ decoded.params = convert_NIL(decoded.params)
+ -- Schedule here so that the users functions don't trigger an error and
+ -- we can still use the result.
+ schedule(function()
+ local status, result
+ status, result, err = try_call(client_errors.SERVER_REQUEST_HANDLER_ERROR,
+ handlers.server_request, decoded.method, decoded.params)
+ local _ = log.debug() and log.debug("server_request: callback result", { status = status, result = result, err = err })
+ if status then
+ if not (result or err) then
+ -- TODO this can be a problem if `null` is sent for result. needs vim.NIL
+ error(string.format("method %q: either a result or an error must be sent to the server in response", decoded.method))
+ end
+ if err then
+ assert(type(err) == 'table', "err must be a table. Use rpc_response_error to help format errors.")
+ local code_name = assert(protocol.ErrorCodes[err.code], "Errors must use protocol.ErrorCodes. Use rpc_response_error to help format errors.")
+ err.message = err.message or code_name
+ end
+ else
+ -- On an exception, result will contain the error message.
+ err = rpc_response_error(protocol.ErrorCodes.InternalError, result)
+ result = nil
+ end
+ send_response(decoded.id, err, result)
+ end)
+ -- This works because we are expecting vim.NIL here
+ elseif decoded.id and (decoded.result or decoded.error) then
+ -- Server Result
+ decoded.error = convert_NIL(decoded.error)
+ decoded.result = convert_NIL(decoded.result)
+
+ -- Do not surface RequestCancelled to users, it is RPC-internal.
+ if decoded.error
+ and decoded.error.code == protocol.ErrorCodes.RequestCancelled then
+ local _ = log.debug() and log.debug("Received cancellation ack", decoded)
+ local result_id = tonumber(decoded.id)
+ -- Clear any callback since this is cancelled now.
+ -- This is safe to do assuming that these conditions hold:
+ -- - The server will not send a result callback after this cancellation.
+ -- - If the server sent this cancellation ACK after sending the result, the user of this RPC
+ -- client will ignore the result themselves.
+ if result_id then
+ message_callbacks[result_id] = nil
+ end
+ return
+ end
+
+ -- We sent a number, so we expect a number.
+ local result_id = tonumber(decoded.id)
+ local callback = message_callbacks[result_id]
+ if callback then
+ message_callbacks[result_id] = nil
+ validate {
+ callback = { callback, 'f' };
+ }
+ if decoded.error then
+ decoded.error = setmetatable(decoded.error, {
+ __tostring = format_rpc_error;
+ })
+ end
+ try_call(client_errors.SERVER_RESULT_CALLBACK_ERROR,
+ callback, decoded.error, decoded.result)
+ else
+ on_error(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
+ decoded.params = convert_NIL(decoded.params)
+ try_call(client_errors.NOTIFICATION_HANDLER_ERROR,
+ handlers.notification, decoded.method, decoded.params)
+ else
+ -- Invalid server message
+ on_error(client_errors.INVALID_SERVER_MESSAGE, decoded)
+ end
+ end
+ -- TODO(ashkan) remove this once we have a Lua json_decode
+ handle_body = schedule_wrap(handle_body)
+
+ local request_parser = coroutine.wrap(request_parser_loop)
+ request_parser()
+ stdout:read_start(function(err, chunk)
+ if err then
+ -- TODO better handling. Can these be intermittent errors?
+ on_error(client_errors.READ_ERROR, err)
+ return
+ end
+ -- This should signal that we are done reading from the client.
+ if not chunk then return end
+ -- Flush anything in the parser by looping until we don't get a result
+ -- anymore.
+ while true do
+ local headers, body = request_parser(chunk)
+ -- If we successfully parsed, then handle the response.
+ if headers then
+ handle_body(body)
+ -- Set chunk to empty so that we can call request_parser to get
+ -- anything existing in the parser to flush.
+ chunk = ''
+ else
+ break
+ end
+ end
+ end)
+
+ return {
+ pid = pid;
+ handle = handle;
+ request = send_request;
+ notify = send_notification;
+ }
+end
+
+return {
+ start = create_and_start_client;
+ rpc_response_error = rpc_response_error;
+ format_rpc_error = format_rpc_error;
+ client_errors = client_errors;
+}
+-- vim:sw=2 ts=2 et
diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua
new file mode 100644
index 0000000000..d286f28d0c
--- /dev/null
+++ b/runtime/lua/vim/lsp/util.lua
@@ -0,0 +1,1360 @@
+local protocol = require 'vim.lsp.protocol'
+local vim = vim
+local validate = vim.validate
+local api = vim.api
+local list_extend = vim.list_extend
+local highlight = require 'vim.highlight'
+
+local M = {}
+
+--- Diagnostics received from the server via `textDocument/publishDiagnostics`
+-- by buffer.
+--
+-- {<bufnr>: {diagnostics}}
+--
+-- This contains only entries for active buffers. Entries for detached buffers
+-- are discarded.
+--
+-- If you override the `textDocument/publishDiagnostic` callback,
+-- this will be empty unless you call `buf_diagnostics_save_positions`.
+--
+--
+-- Diagnostic is:
+--
+-- {
+-- range: Range
+-- message: string
+-- severity?: DiagnosticSeverity
+-- code?: number | string
+-- source?: string
+-- tags?: DiagnosticTag[]
+-- relatedInformation?: DiagnosticRelatedInformation[]
+-- }
+M.diagnostics_by_buf = {}
+
+local split = vim.split
+local function split_lines(value)
+ return split(value, '\n', true)
+end
+
+local function ok_or_nil(status, ...)
+ if not status then return end
+ return ...
+end
+local function npcall(fn, ...)
+ return ok_or_nil(pcall(fn, ...))
+end
+
+function M.set_lines(lines, A, B, new_lines)
+ -- 0-indexing to 1-indexing
+ local i_0 = A[1] + 1
+ -- If it extends past the end, truncate it to the end. This is because the
+ -- way the LSP describes the range including the last newline is by
+ -- specifying a line number after what we would call the last line.
+ local i_n = math.min(B[1] + 1, #lines)
+ if not (i_0 >= 1 and i_0 <= #lines and i_n >= 1 and i_n <= #lines) then
+ error("Invalid range: "..vim.inspect{A = A; B = B; #lines, new_lines})
+ end
+ local prefix = ""
+ local suffix = lines[i_n]:sub(B[2]+1)
+ if A[2] > 0 then
+ prefix = lines[i_0]:sub(1, A[2])
+ end
+ local n = i_n - i_0 + 1
+ if n ~= #new_lines then
+ for _ = 1, n - #new_lines do table.remove(lines, i_0) end
+ for _ = 1, #new_lines - n do table.insert(lines, i_0, '') end
+ end
+ for i = 1, #new_lines do
+ lines[i - 1 + i_0] = new_lines[i]
+ end
+ if #suffix > 0 then
+ local i = i_0 + #new_lines - 1
+ lines[i] = lines[i]..suffix
+ end
+ if #prefix > 0 then
+ lines[i_0] = prefix..lines[i_0]
+ end
+ return lines
+end
+
+local function sort_by_key(fn)
+ return function(a,b)
+ local ka, kb = fn(a), fn(b)
+ assert(#ka == #kb)
+ for i = 1, #ka do
+ if ka[i] ~= kb[i] then
+ return ka[i] < kb[i]
+ end
+ end
+ -- every value must have been equal here, which means it's not less than.
+ return false
+ end
+end
+local edit_sort_key = sort_by_key(function(e)
+ return {e.A[1], e.A[2], e.i}
+end)
+
+--- 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
+-- 1-indexed
+local function get_line_byte_from_position(bufnr, position)
+ -- LSP's line and characters are 0-indexed
+ -- Vim's line and columns are 1-indexed
+ local col = position.character
+ -- When on the first character, we can ignore the difference between byte and
+ -- character
+ if col > 0 then
+ local line = position.line
+ local lines = api.nvim_buf_get_lines(bufnr, line, line + 1, false)
+ if #lines > 0 then
+ return vim.str_byteindex(lines[1], col)
+ end
+ end
+ return col
+end
+
+function M.apply_text_edits(text_edits, bufnr)
+ if not next(text_edits) then return end
+ if not api.nvim_buf_is_loaded(bufnr) then
+ vim.fn.bufload(bufnr)
+ end
+ api.nvim_buf_set_option(bufnr, 'buflisted', true)
+ local start_line, finish_line = math.huge, -1
+ local cleaned = {}
+ for i, e in ipairs(text_edits) do
+ -- adjust start and end column for UTF-16 encoding of non-ASCII characters
+ local start_row = e.range.start.line
+ local start_col = get_line_byte_from_position(bufnr, e.range.start)
+ local end_row = e.range["end"].line
+ local end_col = get_line_byte_from_position(bufnr, e.range['end'])
+ start_line = math.min(e.range.start.line, start_line)
+ finish_line = math.max(e.range["end"].line, finish_line)
+ -- TODO(ashkan) sanity check ranges for overlap.
+ table.insert(cleaned, {
+ i = i;
+ A = {start_row; start_col};
+ B = {end_row; end_col};
+ lines = vim.split(e.newText, '\n', true);
+ })
+ end
+
+ -- Reverse sort the orders so we can apply them without interfering with
+ -- eachother. Also add i as a sort key to mimic a stable sort.
+ table.sort(cleaned, edit_sort_key)
+ local lines = api.nvim_buf_get_lines(bufnr, start_line, finish_line + 1, false)
+ local fix_eol = api.nvim_buf_get_option(bufnr, 'fixeol')
+ local set_eol = fix_eol and api.nvim_buf_line_count(bufnr) <= finish_line + 1
+ if set_eol and #lines[#lines] ~= 0 then
+ table.insert(lines, '')
+ end
+
+ for i = #cleaned, 1, -1 do
+ local e = cleaned[i]
+ local A = {e.A[1] - start_line, e.A[2]}
+ local B = {e.B[1] - start_line, e.B[2]}
+ lines = M.set_lines(lines, A, B, e.lines)
+ end
+ if set_eol and #lines[#lines] == 0 then
+ table.remove(lines)
+ end
+ api.nvim_buf_set_lines(bufnr, start_line, finish_line + 1, false, lines)
+end
+
+-- local valid_windows_path_characters = "[^<>:\"/\\|?*]"
+-- local valid_unix_path_characters = "[^/]"
+-- https://github.com/davidm/lua-glob-pattern
+-- https://stackoverflow.com/questions/1976007/what-characters-are-forbidden-in-windows-and-linux-directory-names
+-- function M.glob_to_regex(glob)
+-- end
+
+-- textDocument/completion response returns one of CompletionItem[], CompletionList or null.
+-- https://microsoft.github.io/language-server-protocol/specification#textDocument_completion
+function M.extract_completion_items(result)
+ if type(result) == 'table' and result.items then
+ return result.items
+ elseif result ~= nil then
+ return result
+ else
+ return {}
+ end
+end
+
+--- Apply the TextDocumentEdit response.
+-- @params TextDocumentEdit [table] see https://microsoft.github.io/language-server-protocol/specification
+function M.apply_text_document_edit(text_document_edit)
+ local text_document = text_document_edit.textDocument
+ local bufnr = vim.uri_to_bufnr(text_document.uri)
+ if text_document.version then
+ -- `VersionedTextDocumentIdentifier`s version may be null https://microsoft.github.io/language-server-protocol/specification#versionedTextDocumentIdentifier
+ if text_document.version ~= vim.NIL and M.buf_versions[bufnr] ~= nil and M.buf_versions[bufnr] > text_document.version then
+ print("Buffer ", text_document.uri, " newer than edits.")
+ return
+ end
+ end
+ M.apply_text_edits(text_document_edit.edits, bufnr)
+end
+
+function M.get_current_line_to_cursor()
+ local pos = api.nvim_win_get_cursor(0)
+ local line = assert(api.nvim_buf_get_lines(0, pos[1]-1, pos[1], false)[1])
+ return line:sub(pos[2]+1)
+end
+
+local function parse_snippet_rec(input, inner)
+ local res = ""
+
+ local close, closeend = nil, nil
+ if inner then
+ close, closeend = input:find("}", 1, true)
+ while close ~= nil and input:sub(close-1,close-1) == "\\" do
+ close, closeend = input:find("}", closeend+1, true)
+ end
+ end
+
+ local didx = input:find('$', 1, true)
+ if didx == nil and close == nil then
+ return input, ""
+ elseif close ~=nil and (didx == nil or close < didx) then
+ -- No inner placeholders
+ return input:sub(0, close-1), input:sub(closeend+1)
+ end
+
+ res = res .. input:sub(0, didx-1)
+ input = input:sub(didx+1)
+
+ local tabstop, tabstopend = input:find('^%d+')
+ local placeholder, placeholderend = input:find('^{%d+:')
+ local choice, choiceend = input:find('^{%d+|')
+
+ if tabstop then
+ input = input:sub(tabstopend+1)
+ elseif choice then
+ input = input:sub(choiceend+1)
+ close, closeend = input:find("|}", 1, true)
+
+ res = res .. input:sub(0, close-1)
+ input = input:sub(closeend+1)
+ elseif placeholder then
+ -- TODO: add support for variables
+ input = input:sub(placeholderend+1)
+
+ -- placeholders and variables are recursive
+ while input ~= "" do
+ local r, tail = parse_snippet_rec(input, true)
+ r = r:gsub("\\}", "}")
+
+ res = res .. r
+ input = tail
+ end
+ else
+ res = res .. "$"
+ end
+
+ return res, input
+end
+
+-- Parse completion entries, consuming snippet tokens
+function M.parse_snippet(input)
+ local res, _ = parse_snippet_rec(input, false)
+
+ return res
+end
+
+-- Sort by CompletionItem.sortText
+-- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion
+local function sort_completion_items(items)
+ if items[1] and items[1].sortText then
+ table.sort(items, function(a, b) return a.sortText < b.sortText
+ end)
+ end
+end
+
+-- Returns text that should be inserted when selecting completion item. The precedence is as follows:
+-- textEdit.newText > insertText > label
+-- 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 then
+ if protocol.InsertTextFormat[item.insertTextFormat] == "PlainText" then
+ return item.textEdit.newText
+ else
+ return M.parse_snippet(item.textEdit.newText)
+ end
+ elseif item.insertText ~= nil then
+ if protocol.InsertTextFormat[item.insertTextFormat] == "PlainText" then
+ return item.insertText
+ else
+ return M.parse_snippet(item.insertText)
+ end
+ end
+ return item.label
+end
+
+-- 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
+
+-- Acording to LSP spec, if the client set "completionItemKind.valueSet",
+-- the client must handle it properly even if it receives a value outside the specification.
+-- 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"
+end
+
+--- Getting vim complete-items with incomplete flag.
+-- @params CompletionItem[], CompletionList or nil (https://microsoft.github.io/language-server-protocol/specification#textDocument_completion)
+-- @return { matches = complete-items table, incomplete = boolean }
+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
+end
+
+-- @params WorkspaceEdit [table] see https://microsoft.github.io/language-server-protocol/specification
+function M.apply_workspace_edit(workspace_edit)
+ if workspace_edit.documentChanges then
+ for _, change in ipairs(workspace_edit.documentChanges) do
+ if change.kind then
+ -- TODO(ashkan) handle CreateFile/RenameFile/DeleteFile
+ error(string.format("Unsupported change: %q", vim.inspect(change)))
+ else
+ M.apply_text_document_edit(change)
+ end
+ end
+ return
+ end
+
+ local all_changes = workspace_edit.changes
+ if not (all_changes and not vim.tbl_isempty(all_changes)) then
+ return
+ end
+
+ for uri, changes in pairs(all_changes) do
+ local bufnr = vim.uri_to_bufnr(uri)
+ M.apply_text_edits(changes, bufnr)
+ end
+end
+
+--- Convert any of MarkedString | MarkedString[] | MarkupContent into markdown text lines
+-- see https://microsoft.github.io/language-server-protocol/specifications/specification-3-14/#textDocument_hover
+-- Useful for textDocument/hover, textDocument/signatureHelp, and potentially others.
+function M.convert_input_to_markdown_lines(input, contents)
+ contents = contents or {}
+ -- MarkedString variation 1
+ if type(input) == 'string' then
+ list_extend(contents, split_lines(input))
+ else
+ assert(type(input) == 'table', "Expected a table for Hover.contents")
+ -- MarkupContent
+ if input.kind then
+ -- The kind can be either plaintext or markdown. However, either way we
+ -- will just be rendering markdown, so we handle them both the same way.
+ -- TODO these can have escaped/sanitized html codes in markdown. We
+ -- should make sure we handle this correctly.
+
+ -- Some servers send input.value as empty, so let's ignore this :(
+ -- assert(type(input.value) == 'string')
+ list_extend(contents, split_lines(input.value or ''))
+ -- 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, "```")
+ -- By deduction, this must be MarkedString[]
+ else
+ -- Use our existing logic to handle MarkedString
+ for _, marked_string in ipairs(input) do
+ M.convert_input_to_markdown_lines(marked_string, contents)
+ end
+ end
+ end
+ if (contents[1] == '' or contents[1] == nil) and #contents == 1 then
+ return {}
+ end
+ return contents
+end
+
+--- Convert SignatureHelp response to markdown lines.
+-- https://microsoft.github.io/language-server-protocol/specifications/specification-3-14/#textDocument_signatureHelp
+function M.convert_signature_help_to_markdown_lines(signature_help)
+ if not signature_help.signatures then
+ return
+ end
+ --The active signature. If omitted or the value lies outside the range of
+ --`signatures` the value defaults to zero or is ignored if `signatures.length
+ --=== 0`. Whenever possible implementors should make an active decision about
+ --the active signature and shouldn't rely on a default value.
+ local contents = {}
+ local active_signature = signature_help.activeSignature or 0
+ -- If the activeSignature is not inside the valid range, then clip it.
+ if active_signature >= #signature_help.signatures then
+ active_signature = 0
+ end
+ local signature = signature_help.signatures[active_signature + 1]
+ if not signature then
+ return
+ end
+ vim.list_extend(contents, vim.split(signature.label, '\n', true))
+ if signature.documentation then
+ M.convert_input_to_markdown_lines(signature.documentation, contents)
+ end
+ if signature_help.parameters then
+ local active_parameter = signature_help.activeParameter or 0
+ -- If the activeParameter is not inside the valid range, then clip it.
+ if active_parameter >= #signature_help.parameters then
+ active_parameter = 0
+ end
+ local parameter = signature.parameters and signature.parameters[active_parameter]
+ if parameter then
+ --[=[
+ --Represents a parameter of a callable-signature. A parameter can
+ --have a label and a doc-comment.
+ interface 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`.
+ label: string | [number, number];
+ --The human-readable doc-comment of this parameter. Will be shown
+ --in the UI but can be omitted.
+ documentation?: string | MarkupContent;
+ }
+ --]=]
+ -- TODO highlight parameter
+ if parameter.documentation then
+ M.convert_input_help_to_markdown_lines(parameter.documentation, contents)
+ end
+ end
+ end
+ return contents
+end
+
+function M.make_floating_popup_options(width, height, opts)
+ validate {
+ opts = { opts, 't', true };
+ }
+ opts = opts or {}
+ validate {
+ ["opts.offset_x"] = { opts.offset_x, 'n', true };
+ ["opts.offset_y"] = { opts.offset_y, 'n', true };
+ }
+
+ local anchor = ''
+ local row, col
+
+ local lines_above = vim.fn.winline() - 1
+ local lines_below = vim.fn.winheight(0) - lines_above
+
+ if lines_above < lines_below then
+ anchor = anchor..'N'
+ height = math.min(lines_below, height)
+ row = 1
+ else
+ anchor = anchor..'S'
+ height = math.min(lines_above, height)
+ row = 0
+ end
+
+ if vim.fn.wincol() + width <= api.nvim_get_option('columns') then
+ anchor = anchor..'W'
+ col = 0
+ else
+ anchor = anchor..'E'
+ col = 1
+ end
+
+ return {
+ anchor = anchor,
+ col = col + (opts.offset_x or 0),
+ height = height,
+ relative = 'cursor',
+ row = row + (opts.offset_y or 0),
+ style = 'minimal',
+ width = width,
+ }
+end
+
+function M.jump_to_location(location)
+ -- location may be Location or LocationLink
+ local uri = location.uri or location.targetUri
+ if uri == nil then return end
+ local bufnr = vim.uri_to_bufnr(uri)
+ -- Save position in jumplist
+ vim.cmd "normal! m'"
+
+ -- Push a new item into tagstack
+ local from = {vim.fn.bufnr('%'), vim.fn.line('.'), vim.fn.col('.'), 0}
+ local items = {{tagname=vim.fn.expand('<cword>'), from=from}}
+ vim.fn.settagstack(vim.fn.win_getid(), {items=items}, 't')
+
+ --- Jump to new location (adjusting for UTF-16 encoding of characters)
+ api.nvim_set_current_buf(bufnr)
+ api.nvim_buf_set_option(0, 'buflisted', true)
+ local range = location.range or location.targetSelectionRange
+ local row = range.start.line
+ local col = get_line_byte_from_position(0, range.start)
+ api.nvim_win_set_cursor(0, {row + 1, col})
+ return true
+end
+
+--- Preview a location in a floating windows
+---
+--- behavior depends on type of location:
+--- - 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
+--@return bufnr,winnr buffer and window number of floating window or nil
+function M.preview_location(location)
+ -- location may be LocationLink or Location (more useful for the former)
+ local uri = location.targetUri or location.uri
+ if uri == nil then return end
+ local bufnr = vim.uri_to_bufnr(uri)
+ if not api.nvim_buf_is_loaded(bufnr) then
+ vim.fn.bufload(bufnr)
+ 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 filetype = api.nvim_buf_get_option(bufnr, 'filetype')
+ return M.open_floating_preview(contents, filetype)
+end
+
+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
+ return win
+ end
+ end
+end
+
+-- Check if a window with `unique_name` tagged is associated with the current
+-- buffer. If not, make a new preview.
+--
+-- fn()'s return bufnr, winnr
+-- case that a new floating window should be created.
+function M.focusable_float(unique_name, fn)
+ if npcall(api.nvim_win_get_var, 0, unique_name) then
+ return api.nvim_command("wincmd p")
+ end
+ local bufnr = api.nvim_get_current_buf()
+ do
+ local win = find_window_by_var(unique_name, bufnr)
+ if win then
+ api.nvim_set_current_win(win)
+ api.nvim_command("stopinsert")
+ return
+ end
+ end
+ local pbufnr, pwinnr = fn()
+ if pbufnr then
+ api.nvim_win_set_var(pwinnr, unique_name, bufnr)
+ return pbufnr, pwinnr
+ end
+end
+
+-- Check if a window with `unique_name` tagged is associated with the current
+-- buffer. If not, make a new preview.
+--
+-- fn()'s return values will be passed directly to open_floating_preview in the
+-- case that a new floating window should be created.
+function M.focusable_preview(unique_name, fn)
+ return M.focusable_float(unique_name, function()
+ return M.open_floating_preview(fn())
+ end)
+end
+
+--- Trim empty lines from input and pad left and right with spaces
+---
+--@param contents table of lines to trim and pad
+--@param opts dictionary with optional fields
+-- - pad_left number of columns to pad contents at left (default 1)
+-- - pad_right number of columns to pad contents at right (default 1)
+-- - 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_and_pad(contents, opts)
+ validate {
+ contents = { contents, 't' };
+ opts = { opts, 't', true };
+ }
+ opts = opts or {}
+ local left_padding = (" "):rep(opts.pad_left or 1)
+ local right_padding = (" "):rep(opts.pad_right or 1)
+ contents = M.trim_empty_lines(contents)
+ for i, line in ipairs(contents) do
+ contents[i] = string.format('%s%s%s', left_padding, line:gsub("\r", ""), right_padding)
+ end
+ if opts.pad_top then
+ for _ = 1, opts.pad_top do
+ table.insert(contents, 1, "")
+ end
+ end
+ if opts.pad_bottom then
+ for _ = 1, opts.pad_bottom do
+ table.insert(contents, "")
+ end
+ end
+ return contents
+end
+
+
+
+--- Convert markdown into syntax highlighted regions by stripping the code
+--- blocks and converting them into highlighted code.
+--- This will by default insert a blank line separator after those code block
+--- regions to improve readability.
+--- The result is shown in a floating preview
+--- TODO: refactor to separate stripping/converting and make use of open_floating_preview
+---
+--@param contents table of lines to show in window
+--@param opts dictionary 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_left number of columns to pad contents at left
+-- - pad_right number of columns to pad contents at right
+-- - 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
+function M.fancy_floating_markdown(contents, opts)
+ validate {
+ contents = { contents, 't' };
+ opts = { opts, 't', true };
+ }
+ opts = opts or {}
+
+ local stripped = {}
+ local highlights = {}
+ do
+ local i = 1
+ while i <= #contents do
+ local line = contents[i]
+ -- TODO(ashkan): use a more strict regex for filetype?
+ local ft = line:match("^```([a-zA-Z0-9_]*)$")
+ -- local ft = line:match("^```(.*)$")
+ -- TODO(ashkan): validate the filetype here.
+ if ft then
+ local start = #stripped
+ i = i + 1
+ while i <= #contents do
+ line = contents[i]
+ if line == "```" then
+ i = i + 1
+ break
+ end
+ table.insert(stripped, line)
+ i = i + 1
+ end
+ table.insert(highlights, {
+ ft = ft;
+ start = start + 1;
+ finish = #stripped + 1 - 1;
+ })
+ else
+ table.insert(stripped, line)
+ i = i + 1
+ end
+ end
+ end
+ -- Clean up and add padding
+ stripped = M._trim_and_pad(stripped, opts)
+
+ -- 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, height = M._make_floating_popup_size(stripped, opts)
+
+ -- Insert blank line separator after code block
+ local insert_separator = opts.separator or true
+ if insert_separator then
+ for i, h in ipairs(highlights) do
+ h.start = h.start + i - 1
+ h.finish = h.finish + i - 1
+ if h.finish + 1 <= #stripped then
+ table.insert(stripped, h.finish + 1, string.rep("─", width))
+ height = height + 1
+ end
+ end
+ end
+
+ -- Make the floating window.
+ local bufnr = api.nvim_create_buf(false, true)
+ local winnr = api.nvim_open_win(bufnr, false, M.make_floating_popup_options(width, height, opts))
+ vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, stripped)
+ api.nvim_buf_set_option(bufnr, 'modifiable', false)
+
+ -- Switch to the floating window to apply the syntax highlighting.
+ -- This is because the syntax command doesn't accept a target.
+ local cwin = vim.api.nvim_get_current_win()
+ vim.api.nvim_set_current_win(winnr)
+
+ vim.cmd("ownsyntax markdown")
+ local idx = 1
+ local function apply_syntax_to_region(ft, start, finish)
+ if ft == '' then return end
+ local name = ft..idx
+ idx = idx + 1
+ local lang = "@"..ft:upper()
+ -- TODO(ashkan): better validation before this.
+ if not pcall(vim.cmd, string.format("syntax include %s syntax/%s.vim", lang, ft)) then
+ return
+ end
+ vim.cmd(string.format("syntax region %s start=+\\%%%dl+ end=+\\%%%dl+ contains=%s", name, start, finish + 1, lang))
+ end
+ -- Previous highlight region.
+ -- TODO(ashkan): this wasn't working for some reason, but I would like to
+ -- make sure that regions between code blocks are definitely markdown.
+ -- local ph = {start = 0; finish = 1;}
+ for _, h in ipairs(highlights) do
+ -- apply_syntax_to_region('markdown', ph.finish, h.start)
+ apply_syntax_to_region(h.ft, h.start, h.finish)
+ -- ph = h
+ end
+
+ vim.api.nvim_set_current_win(cwin)
+ return bufnr, winnr
+end
+
+function M.close_preview_autocmd(events, winnr)
+ api.nvim_command("autocmd "..table.concat(events, ',').." <buffer> ++once lua pcall(vim.api.nvim_win_close, "..winnr..", true)")
+end
+
+--- Compute 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
+-- - 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
+--@return width,height size of float
+function M._make_floating_popup_size(contents, opts)
+ validate {
+ contents = { contents, 't' };
+ opts = { opts, 't', true };
+ }
+ opts = opts or {}
+
+ local width = opts.width
+ local height = opts.height
+ local wrap_at = opts.wrap_at
+ local max_width = opts.max_width
+ local max_height = opts.max_height
+ local line_widths = {}
+
+ if not width then
+ 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)
+ width = math.max(line_widths[i], width)
+ end
+ end
+ if max_width then
+ width = math.min(width, max_width)
+ wrap_at = math.min(wrap_at or max_width, max_width)
+ end
+
+ if not height then
+ height = #contents
+ if wrap_at and width >= wrap_at then
+ height = 0
+ if vim.tbl_isempty(line_widths) then
+ for _, line in ipairs(contents) do
+ local line_width = vim.fn.strdisplaywidth(line)
+ height = height + math.ceil(line_width/wrap_at)
+ end
+ else
+ for i = 1, #contents do
+ height = height + math.max(1, math.ceil(line_widths[i]/wrap_at))
+ end
+ end
+ end
+ end
+ if max_height then
+ height = math.min(height, max_height)
+ end
+
+ return width, height
+end
+
+--- Show contents in a floating window
+---
+--@param contents table of lines to show in window
+--@param filetype string of filetype to set for opened buffer
+--@param opts dictionary 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_left number of columns to pad contents at left
+-- - pad_right number of columns to pad contents at right
+-- - pad_top number of lines to pad contents at top
+-- - pad_bottom number of lines to pad contents at bottom
+--@return bufnr,winnr buffer and window number of floating window or nil
+function M.open_floating_preview(contents, filetype, opts)
+ validate {
+ contents = { contents, 't' };
+ filetype = { filetype, 's', true };
+ opts = { opts, 't', true };
+ }
+ opts = opts or {}
+
+ -- Clean up input: trim empty lines from the end, pad
+ contents = M._trim_and_pad(contents, opts)
+
+ -- 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, height = M._make_floating_popup_size(contents, opts)
+
+ local floating_bufnr = api.nvim_create_buf(false, true)
+ if filetype then
+ api.nvim_buf_set_option(floating_bufnr, 'filetype', filetype)
+ end
+ local float_option = M.make_floating_popup_options(width, height, opts)
+ local floating_winnr = api.nvim_open_win(floating_bufnr, false, float_option)
+ if filetype == 'markdown' then
+ api.nvim_win_set_option(floating_winnr, 'conceallevel', 2)
+ end
+ api.nvim_buf_set_lines(floating_bufnr, 0, -1, true, contents)
+ api.nvim_buf_set_option(floating_bufnr, 'modifiable', false)
+ M.close_preview_autocmd({"CursorMoved", "CursorMovedI", "BufHidden", "BufLeave"}, floating_winnr)
+ return floating_bufnr, floating_winnr
+end
+
+do
+ local diagnostic_ns = api.nvim_create_namespace("vim_lsp_diagnostics")
+ local reference_ns = api.nvim_create_namespace("vim_lsp_references")
+ local sign_ns = 'vim_lsp_signs'
+ local underline_highlight_name = "LspDiagnosticsUnderline"
+ vim.cmd(string.format("highlight default %s gui=underline cterm=underline", underline_highlight_name))
+ for kind, _ in pairs(protocol.DiagnosticSeverity) do
+ if type(kind) == 'string' then
+ vim.cmd(string.format("highlight default link %s%s %s", underline_highlight_name, kind, underline_highlight_name))
+ end
+ end
+
+ local severity_highlights = {}
+
+ local severity_floating_highlights = {}
+
+ local default_severity_highlight = {
+ [protocol.DiagnosticSeverity.Error] = { guifg = "Red" };
+ [protocol.DiagnosticSeverity.Warning] = { guifg = "Orange" };
+ [protocol.DiagnosticSeverity.Information] = { guifg = "LightBlue" };
+ [protocol.DiagnosticSeverity.Hint] = { guifg = "LightGrey" };
+ }
+
+ -- Initialize default severity highlights
+ for severity, hi_info in pairs(default_severity_highlight) do
+ local severity_name = protocol.DiagnosticSeverity[severity]
+ local highlight_name = "LspDiagnostics"..severity_name
+ local floating_highlight_name = highlight_name.."Floating"
+ -- Try to fill in the foreground color with a sane default.
+ local cmd_parts = {"highlight", "default", highlight_name}
+ for k, v in pairs(hi_info) do
+ table.insert(cmd_parts, k.."="..v)
+ end
+ api.nvim_command(table.concat(cmd_parts, ' '))
+ api.nvim_command('highlight link ' .. highlight_name .. 'Sign ' .. highlight_name)
+ api.nvim_command('highlight link ' .. highlight_name .. 'Floating ' .. highlight_name)
+ severity_highlights[severity] = highlight_name
+ severity_floating_highlights[severity] = floating_highlight_name
+ end
+
+ function M.buf_clear_diagnostics(bufnr)
+ validate { bufnr = {bufnr, 'n', true} }
+ bufnr = bufnr == 0 and api.nvim_get_current_buf() or bufnr
+
+ -- clear sign group
+ vim.fn.sign_unplace(sign_ns, {buffer=bufnr})
+
+ -- clear virtual text namespace
+ api.nvim_buf_clear_namespace(bufnr, diagnostic_ns, 0, -1)
+ end
+
+ function M.get_severity_highlight_name(severity)
+ return severity_highlights[severity]
+ end
+
+ function M.get_line_diagnostics()
+ local bufnr = api.nvim_get_current_buf()
+ local linenr = api.nvim_win_get_cursor(0)[1] - 1
+
+ local buffer_diagnostics = M.diagnostics_by_buf[bufnr]
+
+ if not buffer_diagnostics then
+ return {}
+ end
+
+ local diagnostics_by_line = M.diagnostics_group_by_line(buffer_diagnostics)
+ return diagnostics_by_line[linenr] or {}
+ end
+
+ function M.show_line_diagnostics()
+ -- local marks = api.nvim_buf_get_extmarks(bufnr, diagnostic_ns, {line, 0}, {line, -1}, {})
+ -- if #marks == 0 then
+ -- return
+ -- end
+ local lines = {"Diagnostics:"}
+ local highlights = {{0, "Bold"}}
+ local line_diagnostics = M.get_line_diagnostics()
+ if vim.tbl_isempty(line_diagnostics) then return end
+
+ for i, diagnostic in ipairs(line_diagnostics) do
+ -- for i, mark in ipairs(marks) do
+ -- local mark_id = mark[1]
+ -- local diagnostic = buffer_diagnostics[mark_id]
+
+ -- TODO(ashkan) make format configurable?
+ local prefix = string.format("%d. ", i)
+ local hiname = severity_floating_highlights[diagnostic.severity]
+ assert(hiname, 'unknown severity: ' .. tostring(diagnostic.severity))
+ local message_lines = split_lines(diagnostic.message)
+ table.insert(lines, prefix..message_lines[1])
+ table.insert(highlights, {#prefix + 1, hiname})
+ for j = 2, #message_lines do
+ table.insert(lines, message_lines[j])
+ table.insert(highlights, {0, hiname})
+ end
+ end
+ local popup_bufnr, winnr = M.open_floating_preview(lines, 'plaintext')
+ for i, hi in ipairs(highlights) do
+ local prefixlen, hiname = unpack(hi)
+ -- Start highlight after the prefix
+ api.nvim_buf_add_highlight(popup_bufnr, -1, hiname, i-1, prefixlen, -1)
+ end
+ return popup_bufnr, winnr
+ end
+
+ --- Saves the diagnostics (Diagnostic[]) into diagnostics_by_buf
+ ---
+ --@param bufnr bufnr for which the diagnostics are for.
+ --@param diagnostics Diagnostics[] received from the language server.
+ function M.buf_diagnostics_save_positions(bufnr, diagnostics)
+ validate {
+ bufnr = {bufnr, 'n', true};
+ diagnostics = {diagnostics, 't', true};
+ }
+ if not diagnostics then return end
+ bufnr = bufnr == 0 and api.nvim_get_current_buf() or bufnr
+
+ if not M.diagnostics_by_buf[bufnr] then
+ -- Clean up our data when the buffer unloads.
+ api.nvim_buf_attach(bufnr, false, {
+ on_detach = function(b)
+ M.diagnostics_by_buf[b] = nil
+ end
+ })
+ end
+ M.diagnostics_by_buf[bufnr] = diagnostics
+ end
+
+ function M.buf_diagnostics_underline(bufnr, diagnostics)
+ for _, diagnostic in ipairs(diagnostics) do
+ local start = diagnostic.range["start"]
+ local finish = diagnostic.range["end"]
+
+ local hlmap = {
+ [protocol.DiagnosticSeverity.Error]='Error',
+ [protocol.DiagnosticSeverity.Warning]='Warning',
+ [protocol.DiagnosticSeverity.Information]='Information',
+ [protocol.DiagnosticSeverity.Hint]='Hint',
+ }
+
+ highlight.range(bufnr, diagnostic_ns,
+ underline_highlight_name..hlmap[diagnostic.severity],
+ {start.line, start.character},
+ {finish.line, finish.character}
+ )
+ end
+ end
+
+ function M.buf_clear_references(bufnr)
+ validate { bufnr = {bufnr, 'n', true} }
+ api.nvim_buf_clear_namespace(bufnr, reference_ns, 0, -1)
+ end
+
+ function M.buf_highlight_references(bufnr, references)
+ validate { bufnr = {bufnr, 'n', true} }
+ for _, reference in ipairs(references) do
+ local start_pos = {reference["range"]["start"]["line"], reference["range"]["start"]["character"]}
+ local end_pos = {reference["range"]["end"]["line"], reference["range"]["end"]["character"]}
+ local document_highlight_kind = {
+ [protocol.DocumentHighlightKind.Text] = "LspReferenceText";
+ [protocol.DocumentHighlightKind.Read] = "LspReferenceRead";
+ [protocol.DocumentHighlightKind.Write] = "LspReferenceWrite";
+ }
+ local kind = reference["kind"] or protocol.DocumentHighlightKind.Text
+ highlight.range(bufnr, reference_ns, document_highlight_kind[kind], start_pos, end_pos)
+ end
+ end
+
+ function M.diagnostics_group_by_line(diagnostics)
+ if not diagnostics then return end
+ local diagnostics_by_line = {}
+ for _, diagnostic in ipairs(diagnostics) do
+ local start = diagnostic.range.start
+ local line_diagnostics = diagnostics_by_line[start.line]
+ if not line_diagnostics then
+ line_diagnostics = {}
+ diagnostics_by_line[start.line] = line_diagnostics
+ end
+ table.insert(line_diagnostics, diagnostic)
+ end
+ return diagnostics_by_line
+ end
+
+ function M.buf_diagnostics_virtual_text(bufnr, diagnostics)
+ if not diagnostics then
+ return
+ end
+ local buffer_line_diagnostics = M.diagnostics_group_by_line(diagnostics)
+ for line, line_diags in pairs(buffer_line_diagnostics) do
+ local virt_texts = {}
+ for i = 1, #line_diags - 1 do
+ table.insert(virt_texts, {"â– ", severity_highlights[line_diags[i].severity]})
+ end
+ local last = line_diags[#line_diags]
+ -- TODO(ashkan) use first line instead of subbing 2 spaces?
+ table.insert(virt_texts, {"â–  "..last.message:gsub("\r", ""):gsub("\n", " "), severity_highlights[last.severity]})
+ api.nvim_buf_set_virtual_text(bufnr, diagnostic_ns, line, virt_texts, {})
+ end
+ end
+
+ --- Returns the number of diagnostics of given kind for current buffer.
+ ---
+ --- Useful for showing diagnostic counts in statusline. eg:
+ ---
+ --- <pre>
+ --- function! LspStatus() abort
+ --- let sl = ''
+ --- if luaeval('not vim.tbl_isempty(vim.lsp.buf_get_clients(0))')
+ --- let sl.='%#MyStatuslineLSP#E:'
+ --- let sl.='%#MyStatuslineLSPErrors#%{luaeval("vim.lsp.util.buf_diagnostics_count([[Error]])")}'
+ --- let sl.='%#MyStatuslineLSP# W:'
+ --- let sl.='%#MyStatuslineLSPWarnings#%{luaeval("vim.lsp.util.buf_diagnostics_count([[Warning]])")}'
+ --- else
+ --- let sl.='%#MyStatuslineLSPErrors#off'
+ --- endif
+ --- return sl
+ --- endfunction
+ --- let &l:statusline = '%#MyStatuslineLSP#LSP '.LspStatus()
+ --- </pre>
+ ---
+ --@param kind Diagnostic severity kind: See |vim.lsp.protocol.DiagnosticSeverity|
+ ---
+ --@return Count of diagnostics
+ function M.buf_diagnostics_count(kind)
+ local bufnr = vim.api.nvim_get_current_buf()
+ local diagnostics = M.diagnostics_by_buf[bufnr]
+ if not diagnostics then return end
+ local count = 0
+ for _, diagnostic in pairs(diagnostics) do
+ if protocol.DiagnosticSeverity[kind] == diagnostic.severity then
+ count = count + 1
+ end
+ end
+ return count
+ end
+
+ local diagnostic_severity_map = {
+ [protocol.DiagnosticSeverity.Error] = "LspDiagnosticsErrorSign";
+ [protocol.DiagnosticSeverity.Warning] = "LspDiagnosticsWarningSign";
+ [protocol.DiagnosticSeverity.Information] = "LspDiagnosticsInformationSign";
+ [protocol.DiagnosticSeverity.Hint] = "LspDiagnosticsHintSign";
+ }
+
+ --- Place signs for each diagnostic in the sign column.
+ ---
+ --- Sign characters can be customized with the following commands:
+ ---
+ --- <pre>
+ --- sign define LspDiagnosticsErrorSign text=E texthl=LspDiagnosticsError linehl= numhl=
+ --- sign define LspDiagnosticsWarningSign text=W texthl=LspDiagnosticsWarning linehl= numhl=
+ --- sign define LspDiagnosticsInformationSign text=I texthl=LspDiagnosticsInformation linehl= numhl=
+ --- sign define LspDiagnosticsHintSign text=H texthl=LspDiagnosticsHint linehl= numhl=
+ --- </pre>
+ function M.buf_diagnostics_signs(bufnr, diagnostics)
+ for _, diagnostic in ipairs(diagnostics) do
+ vim.fn.sign_place(0, sign_ns, diagnostic_severity_map[diagnostic.severity], bufnr, {lnum=(diagnostic.range.start.line+1)})
+ end
+ end
+end
+
+local position_sort = sort_by_key(function(v)
+ return {v.start.line, v.start.character}
+end)
+
+-- Returns the items with the byte position calculated correctly and in sorted
+-- order.
+function M.locations_to_items(locations)
+ local items = {}
+ local grouped = setmetatable({}, {
+ __index = function(t, k)
+ local v = {}
+ rawset(t, k, v)
+ return v
+ end;
+ })
+ for _, d in ipairs(locations) do
+ -- 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})
+ end
+
+
+ local keys = vim.tbl_keys(grouped)
+ table.sort(keys)
+ -- TODO(ashkan) I wish we could do this lazily.
+ for _, uri in ipairs(keys) do
+ local rows = grouped[uri]
+ table.sort(rows, position_sort)
+ local bufnr = vim.uri_to_bufnr(uri)
+ vim.fn.bufload(bufnr)
+ local filename = vim.uri_to_fname(uri)
+ for _, temp in ipairs(rows) do
+ local pos = temp.start
+ local row = pos.line
+ local line = (api.nvim_buf_get_lines(bufnr, row, row + 1, false) or {""})[1]
+ local col = M.character_offset(bufnr, row, pos.character)
+ table.insert(items, {
+ filename = filename,
+ lnum = row + 1,
+ col = col + 1;
+ text = line;
+ })
+ end
+ end
+ return items
+end
+
+function M.set_loclist(items)
+ vim.fn.setloclist(0, {}, ' ', {
+ title = 'Language Server';
+ items = items;
+ })
+end
+
+function M.set_qflist(items)
+ vim.fn.setqflist({}, ' ', {
+ title = 'Language Server';
+ items = items;
+ })
+end
+
+-- Acording to LSP spec, if the client set "symbolKind.valueSet",
+-- the client must handle it properly even if it receives a value outside the specification.
+-- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_documentSymbol
+function M._get_symbol_kind_name(symbol_kind)
+ return protocol.SymbolKind[symbol_kind] or "Unknown"
+end
+
+--- Convert symbols to quickfix list items
+---
+--@param symbols DocumentSymbol[] or SymbolInformation[]
+function M.symbols_to_items(symbols, bufnr)
+ local function _symbols_to_items(_symbols, _items, _bufnr)
+ for _, symbol in ipairs(_symbols) do
+ if symbol.location then -- SymbolInformation type
+ local range = symbol.location.range
+ local kind = M._get_symbol_kind_name(symbol.kind)
+ table.insert(_items, {
+ filename = vim.uri_to_fname(symbol.location.uri),
+ lnum = range.start.line + 1,
+ col = range.start.character + 1,
+ kind = kind,
+ text = '['..kind..'] '..symbol.name,
+ })
+ elseif symbol.range then -- DocumentSymbole type
+ local kind = M._get_symbol_kind_name(symbol.kind)
+ table.insert(_items, {
+ -- bufnr = _bufnr,
+ filename = vim.api.nvim_buf_get_name(_bufnr),
+ lnum = symbol.range.start.line + 1,
+ col = symbol.range.start.character + 1,
+ kind = kind,
+ text = '['..kind..'] '..symbol.name
+ })
+ if symbol.children then
+ for _, v in ipairs(_symbols_to_items(symbol.children, _items, _bufnr)) do
+ vim.list_extend(_items, v)
+ end
+ end
+ end
+ end
+ return _items
+ end
+ return _symbols_to_items(symbols, {}, bufnr)
+end
+
+-- Remove empty lines from the beginning and end.
+function M.trim_empty_lines(lines)
+ local start = 1
+ for i = 1, #lines do
+ if #lines[i] > 0 then
+ start = i
+ break
+ end
+ end
+ local finish = 1
+ for i = #lines, 1, -1 do
+ if #lines[i] > 0 then
+ finish = i
+ break
+ end
+ end
+ return vim.list_extend({}, lines, start, finish)
+end
+
+-- Accepts markdown lines and tries to reduce it to a filetype if it is
+-- just a single code block.
+-- Note: This modifies the input.
+--
+-- Returns: 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
+ local has_inner_code_fence = false
+ for i = 2, (#lines - 1) do
+ local line = lines[i]
+ if line:sub(1,3) == '```' then
+ has_inner_code_fence = true
+ break
+ end
+ end
+ -- No inner code fences + starting with code fence = hooray.
+ if not has_inner_code_fence then
+ table.remove(lines, 1)
+ table.remove(lines)
+ return language_id
+ end
+ end
+ return 'markdown'
+end
+
+local str_utfindex = vim.str_utfindex
+local function make_position_param()
+ local row, col = unpack(api.nvim_win_get_cursor(0))
+ row = row - 1
+ local line = api.nvim_buf_get_lines(0, row, row+1, true)[1]
+ col = str_utfindex(line, col)
+ return { line = row; character = col; }
+end
+
+function M.make_position_params()
+ return {
+ textDocument = M.make_text_document_params();
+ position = make_position_param()
+ }
+end
+
+function M.make_range_params()
+ local position = make_position_param()
+ return {
+ textDocument = { uri = vim.uri_from_bufnr(0) },
+ range = { start = position; ["end"] = position; }
+ }
+end
+
+function M.make_text_document_params()
+ return { uri = vim.uri_from_bufnr(0) }
+end
+
+--- Get visual width of tabstop.
+---
+--@see |softtabstop|
+--@param bufnr (optional, number): Buffer handle, defaults to current
+--@returns (number) tabstop visual width
+function M.get_effective_tabstop(bufnr)
+ validate { bufnr = {bufnr, 'n', true} }
+ local bo = bufnr and vim.bo[bufnr] or vim.bo
+ local sts = bo.softtabstop
+ return (sts > 0 and sts) or (sts < 0 and bo.shiftwidth) or bo.tabstop
+end
+
+function M.make_formatting_params(options)
+ validate { options = {options, 't', true} }
+ options = vim.tbl_extend('keep', options or {}, {
+ tabSize = M.get_effective_tabstop();
+ insertSpaces = vim.bo.expandtab;
+ })
+ return {
+ textDocument = { uri = vim.uri_from_bufnr(0) };
+ options = options;
+ }
+end
+
+-- @param buf buffer handle or 0 for current.
+-- @param row 0-indexed line
+-- @param col 0-indexed byte offset in line
+function M.character_offset(buf, row, col)
+ local line = api.nvim_buf_get_lines(buf, row, row+1, true)[1]
+ -- If the col is past the EOL, use the line length.
+ if col > #line then
+ return str_utfindex(line)
+ end
+ return str_utfindex(line, col)
+end
+
+M.buf_versions = {}
+
+return M
+-- vim:sw=2 ts=2 et
diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua
index cd6f8a04d8..6e427665f2 100644
--- a/runtime/lua/vim/shared.lua
+++ b/runtime/lua/vim/shared.lua
@@ -4,34 +4,51 @@
-- test-suite. If, in the future, Nvim itself is used to run the test-suite
-- instead of "vanilla Lua", these functions could move to src/nvim/lua/vim.lua
+local vim = vim or {}
--- 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
+--- same functions as those in the input table. Userdata and threads are not
+--- copied and will throw an error.
---
--@param orig Table to copy
--@returns New table of copied keys and (nested) values.
-local function deepcopy(orig)
- error(orig)
-end
-local function _id(v)
- return v
-end
-local deepcopy_funcs = {
- table = function(orig)
- local copy = {}
- for k, v in pairs(orig) do
- copy[deepcopy(k)] = deepcopy(v)
+function vim.deepcopy(orig) end -- luacheck: no unused
+vim.deepcopy = (function()
+ local function _id(v)
+ return v
+ end
+
+ local deepcopy_funcs = {
+ table = function(orig)
+ local copy = {}
+
+ if vim._empty_dict_mt ~= nil and getmetatable(orig) == vim._empty_dict_mt then
+ copy = vim.empty_dict()
+ end
+
+ for k, v in pairs(orig) do
+ copy[vim.deepcopy(k)] = vim.deepcopy(v)
+ end
+ return copy
+ end,
+ number = _id,
+ string = _id,
+ ['nil'] = _id,
+ boolean = _id,
+ ['function'] = _id,
+ }
+
+ return function(orig)
+ local f = deepcopy_funcs[type(orig)]
+ if f then
+ return f(orig)
+ else
+ error("Cannot deepcopy object of type "..type(orig))
end
- return copy
- end,
- number = _id,
- string = _id,
- ['nil'] = _id,
- boolean = _id,
-}
-deepcopy = function(orig)
- return deepcopy_funcs[type(orig)](orig)
-end
+ end
+end)()
--- Splits a string at each instance of a separator.
---
@@ -43,10 +60,8 @@ end
--@param sep Separator string or pattern
--@param plain If `true` use `sep` literally (passed to String.find)
--@returns Iterator over the split components
-local function gsplit(s, sep, plain)
- assert(type(s) == "string")
- assert(type(sep) == "string")
- assert(type(plain) == "boolean" or type(plain) == "nil")
+function vim.gsplit(s, sep, plain)
+ vim.validate{s={s,'s'},sep={sep,'s'},plain={plain,'b',true}}
local start = 1
local done = false
@@ -64,7 +79,7 @@ local function gsplit(s, sep, plain)
end
return function()
- if done then
+ if done or (s == '' and sep == '') then
return
end
if sep == '' then
@@ -92,20 +107,81 @@ end
--@param sep Separator string or pattern
--@param plain If `true` use `sep` literally (passed to String.find)
--@returns List-like table of the split components.
-local function split(s,sep,plain)
- local t={} for c in gsplit(s, sep, plain) do table.insert(t,c) end
+function vim.split(s,sep,plain)
+ local t={} for c in vim.gsplit(s, sep, plain) do table.insert(t,c) end
return t
end
+--- Return a list of all keys used in a table.
+--- However, the order of the return table of keys is not guaranteed.
+---
+--@see From https://github.com/premake/premake-core/blob/master/src/base/table.lua
+---
+--@param t Table
+--@returns list of keys
+function vim.tbl_keys(t)
+ assert(type(t) == 'table', string.format("Expected table, got %s", type(t)))
+
+ local keys = {}
+ for k, _ in pairs(t) do
+ table.insert(keys, k)
+ end
+ return keys
+end
+
+--- Return a list of all values used in a table.
+--- However, the order of the return table of values is not guaranteed.
+---
+--@param t Table
+--@returns list of values
+function vim.tbl_values(t)
+ assert(type(t) == 'table', string.format("Expected table, got %s", type(t)))
+
+ local values = {}
+ for _, v in pairs(t) do
+ table.insert(values, v)
+ end
+ return values
+end
+
+--- Apply a function to all values of a table.
+---
+--@param func function or callable table
+--@param t table
+function vim.tbl_map(func, t)
+ vim.validate{func={func,'c'},t={t,'t'}}
+
+ local rettab = {}
+ for k, v in pairs(t) do
+ rettab[k] = func(v)
+ end
+ return rettab
+end
+
+--- Filter a table using a predicate function
+---
+--@param func function or callable table
+--@param t table
+function vim.tbl_filter(func, t)
+ vim.validate{func={func,'c'},t={t,'t'}}
+
+ local rettab = {}
+ for _, entry in pairs(t) do
+ if func(entry) then
+ table.insert(rettab, entry)
+ end
+ end
+ return rettab
+end
+
--- Checks if a list-like (vector) table contains `value`.
---
--@param t Table to check
--@param value Value to compare
--@returns true if `t` contains `value`
-local function tbl_contains(t, value)
- if type(t) ~= 'table' then
- error('t must be a table')
- end
+function vim.tbl_contains(t, value)
+ vim.validate{t={t,'t'}}
+
for _,v in ipairs(t) do
if v == value then
return true
@@ -114,25 +190,38 @@ local function tbl_contains(t, value)
return false
end
---- Merges two or more map-like tables.
----
---@see |extend()|
----
---@param behavior Decides what to do if a key is found in more than one map:
---- - "error": raise an error
---- - "keep": use value from the leftmost map
---- - "force": use value from the rightmost map
---@param ... Two or more map-like tables.
-local function tbl_extend(behavior, ...)
+-- Returns true if the table is empty, and contains no indexed or keyed values.
+--
+--@see From https://github.com/premake/premake-core/blob/master/src/base/table.lua
+--
+--@param t Table to check
+function vim.tbl_isempty(t)
+ assert(type(t) == 'table', string.format("Expected table, got %s", type(t)))
+ return next(t) == nil
+end
+
+local function tbl_extend(behavior, deep_extend, ...)
if (behavior ~= 'error' and behavior ~= 'keep' and behavior ~= 'force') then
error('invalid "behavior": '..tostring(behavior))
end
+
+ if select('#', ...) < 2 then
+ error('wrong number of arguments (given '..tostring(1 + select('#', ...))..', expected at least 3)')
+ end
+
local ret = {}
+ if vim._empty_dict_mt ~= nil and getmetatable(select(1, ...)) == vim._empty_dict_mt then
+ ret = vim.empty_dict()
+ end
+
for i = 1, select('#', ...) do
local tbl = select(i, ...)
+ vim.validate{["after the second argument"] = {tbl,'t'}}
if tbl then
for k, v in pairs(tbl) do
- if behavior ~= 'force' and ret[k] ~= nil then
+ if type(v) == 'table' and deep_extend and not vim.tbl_islist(v) then
+ ret[k] = tbl_extend(behavior, true, ret[k] or vim.empty_dict(), v)
+ elseif behavior ~= 'force' and ret[k] ~= nil then
if behavior == 'error' then
error('key found in more than one map: '..k)
end -- Else behavior is "keep".
@@ -145,13 +234,103 @@ local function tbl_extend(behavior, ...)
return ret
end
+--- Merges two or more map-like tables.
+---
+--@see |extend()|
+---
+--@param behavior Decides what to do if a key is found in more than one map:
+--- - "error": raise an error
+--- - "keep": use value from the leftmost map
+--- - "force": use value from the rightmost map
+--@param ... Two or more map-like tables.
+function vim.tbl_extend(behavior, ...)
+ return tbl_extend(behavior, false, ...)
+end
+
+--- Merges recursively two or more map-like tables.
+---
+--@see |tbl_extend()|
+---
+--@param behavior Decides what to do if a key is found in more than one map:
+--- - "error": raise an error
+--- - "keep": use value from the leftmost map
+--- - "force": use value from the rightmost map
+--@param ... Two or more map-like tables.
+function vim.tbl_deep_extend(behavior, ...)
+ return tbl_extend(behavior, true, ...)
+end
+
+--- Deep compare values for equality
+function vim.deep_equal(a, b)
+ if a == b then return true end
+ if type(a) ~= type(b) then return false end
+ if type(a) == 'table' then
+ -- TODO improve this algorithm's performance.
+ for k, v in pairs(a) do
+ if not vim.deep_equal(v, b[k]) then
+ return false
+ end
+ end
+ for k, v in pairs(b) do
+ if not vim.deep_equal(v, a[k]) then
+ return false
+ end
+ end
+ return true
+ end
+ return false
+end
+
+--- Add the reverse lookup values to an existing table.
+--- For example:
+--- `tbl_add_reverse_lookup { A = 1 } == { [1] = 'A', A = 1 }`
+--
+--Do note that it *modifies* the input.
+--@param o table The table to add the reverse to.
+function vim.tbl_add_reverse_lookup(o)
+ local keys = vim.tbl_keys(o)
+ for _, k in ipairs(keys) do
+ local v = o[k]
+ if o[v] then
+ error(string.format("The reverse lookup found an existing value for %q while processing key %q", tostring(v), tostring(k)))
+ end
+ o[v] = k
+ end
+ return o
+end
+
+--- Extends a list-like table with the values of another list-like table.
+---
+--- NOTE: This mutates dst!
+---
+--@see |vim.tbl_extend()|
+---
+--@param dst list which will be modified and appended to.
+--@param src list from which values will be inserted.
+--@param start Start index on src. defaults to 1
+--@param finish Final index on src. defaults to #src
+--@returns dst
+function vim.list_extend(dst, src, start, finish)
+ vim.validate {
+ dst = {dst, 't'};
+ src = {src, 't'};
+ start = {start, 'n', true};
+ finish = {finish, 'n', true};
+ }
+ for i = start or 1, finish or #src do
+ table.insert(dst, src[i])
+ end
+ return dst
+end
+
--- Creates a copy of a list-like table such that any nested tables are
--- "unrolled" and appended to the result.
---
+--@see From https://github.com/premake/premake-core/blob/master/src/base/table.lua
+---
--@param t List-like table
--@returns Flattened copy of the given list-like table.
-local function tbl_flatten(t)
- -- From https://github.com/premake/premake-core/blob/master/src/base/table.lua
+function vim.tbl_flatten(t)
local result = {}
local function _tbl_flatten(_t)
local n = #_t
@@ -168,34 +347,190 @@ local function tbl_flatten(t)
return result
end
+--- Determine whether a Lua table can be treated as an array.
+---
+--- An empty table `{}` will default to being treated as an array.
+--- Use `vim.emtpy_dict()` to create a table treated as an
+--- empty dict. Empty tables returned by `rpcrequest()` and
+--- `vim.fn` functions can be checked using this function
+--- whether they represent empty API arrays and vimL lists.
+---
+--@param t Table
+--@returns `true` if array-like table, else `false`.
+function vim.tbl_islist(t)
+ if type(t) ~= 'table' then
+ return false
+ end
+
+ local count = 0
+
+ for k, _ in pairs(t) do
+ if type(k) == "number" then
+ count = count + 1
+ else
+ return false
+ end
+ end
+
+ if count > 0 then
+ return true
+ else
+ -- 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
+ end
+end
+
+--- Counts the number of non-nil values in table `t`.
+---
+--- <pre>
+--- 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
+--@returns Number that is the number of the value in table
+function vim.tbl_count(t)
+ vim.validate{t={t,'t'}}
+
+ local count = 0
+ for _ in pairs(t) do count = count + 1 end
+ return count
+end
+
--- Trim whitespace (Lua pattern "%s") from both sides of a string.
---
--@see https://www.lua.org/pil/20.2.html
--@param s String to trim
--@returns String with whitespace removed from its beginning and end
-local function trim(s)
- assert(type(s) == 'string', 'Only strings can be trimmed')
+function vim.trim(s)
+ vim.validate{s={s,'s'}}
return s:match('^%s*(.*%S)') or ''
end
---- Escapes magic chars in a Lua pattern string.
+--- Escapes magic chars in a Lua pattern.
---
--@see https://github.com/rxi/lume
--@param s String to escape
--@returns %-escaped pattern string
-local function pesc(s)
- assert(type(s) == 'string')
+function vim.pesc(s)
+ vim.validate{s={s,'s'}}
return s:gsub('[%(%)%.%%%+%-%*%?%[%]%^%$]', '%%%1')
end
-local module = {
- deepcopy = deepcopy,
- gsplit = gsplit,
- pesc = pesc,
- split = split,
- tbl_contains = tbl_contains,
- tbl_extend = tbl_extend,
- tbl_flatten = tbl_flatten,
- trim = trim,
-}
-return module
+--- Tests if `s` starts with `prefix`.
+---
+--@param s (string) a string
+--@param prefix (string) a prefix
+--@return (boolean) true if `prefix` is a prefix of s
+function vim.startswith(s, prefix)
+ vim.validate { s = {s, 's'}; prefix = {prefix, 's'}; }
+ return s:sub(1, #prefix) == prefix
+end
+
+--- Tests if `s` ends with `suffix`.
+---
+--@param s (string) a string
+--@param suffix (string) a suffix
+--@return (boolean) true if `suffix` is a suffix of s
+function vim.endswith(s, suffix)
+ vim.validate { s = {s, 's'}; suffix = {suffix, 's'}; }
+ return #suffix == 0 or s:sub(-#suffix) == suffix
+end
+
+--- Validates a parameter specification (types and values).
+---
+--- Usage example:
+--- <pre>
+--- 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>
+--- 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>
+---
+--@param opt Map of parameter names to validations. 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 type name, one of: ("table", "t", "string",
+--- "s", "number", "n", "boolean", "b", "function", "f", "nil",
+--- "thread", "userdata")
+--- - 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
+--- - msg: (optional) error string if validation fails
+function vim.validate(opt) end -- luacheck: no unused
+vim.validate = (function()
+ local type_names = {
+ t='table', s='string', n='number', b='boolean', f='function', c='callable',
+ ['table']='table', ['string']='string', ['number']='number',
+ ['boolean']='boolean', ['function']='function', ['callable']='callable',
+ ['nil']='nil', ['thread']='thread', ['userdata']='userdata',
+ }
+ local function _type_name(t)
+ local tname = type_names[t]
+ if tname == nil then
+ error(string.format('invalid type name: %s', tostring(t)))
+ end
+ return tname
+ end
+ local function _is_type(val, t)
+ return t == 'callable' and vim.is_callable(val) or type(val) == t
+ end
+
+ return function(opt)
+ assert(type(opt) == 'table', string.format('opt: expected table, got %s', type(opt)))
+ for param_name, spec in pairs(opt) do
+ assert(type(spec) == 'table', string.format('%s: expected table, got %s', param_name, type(spec)))
+
+ local val = spec[1] -- Argument value.
+ local t = spec[2] -- Type name, or callable.
+ local optional = (true == spec[3])
+
+ if not vim.is_callable(t) then -- Check type name.
+ if (not optional or val ~= nil) and not _is_type(val, _type_name(t)) then
+ error(string.format("%s: expected %s, got %s", param_name, _type_name(t), type(val)))
+ end
+ elseif not t(val) then -- Check user-provided validation function.
+ error(string.format("%s: expected %s, got %s", param_name, (spec[3] or '?'), val))
+ end
+ end
+ return true
+ end
+end)()
+
+--- Returns true if object `f` can be called as a function.
+---
+--@param f Any object
+--@return true if `f` is callable, else false
+function vim.is_callable(f)
+ if type(f) == 'function' then return true end
+ local m = getmetatable(f)
+ if m == nil then return false end
+ return type(m.__call) == 'function'
+end
+
+return vim
+-- vim:sw=2 ts=2 et
diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua
new file mode 100644
index 0000000000..927456708c
--- /dev/null
+++ b/runtime/lua/vim/treesitter.lua
@@ -0,0 +1,260 @@
+local a = vim.api
+
+-- TODO(bfredl): currently we retain parsers for the lifetime of the buffer.
+-- Consider use weak references to release parser if all plugins are done with
+-- it.
+local parsers = {}
+
+local Parser = {}
+Parser.__index = Parser
+
+function Parser:parse()
+ if self.valid then
+ return self.tree
+ end
+ local changes
+ self.tree, changes = self._parser:parse_buf(self.bufnr)
+ self.valid = true
+
+ if not vim.tbl_isempty(changes) then
+ for _, cb in ipairs(self.changedtree_cbs) do
+ cb(changes)
+ end
+ end
+
+ return self.tree, changes
+end
+
+function Parser:_on_lines(bufnr, changed_tick, start_row, old_stop_row, stop_row, old_byte_size)
+ local start_byte = a.nvim_buf_get_offset(bufnr,start_row)
+ local stop_byte = a.nvim_buf_get_offset(bufnr,stop_row)
+ local old_stop_byte = start_byte + old_byte_size
+ self._parser:edit(start_byte,old_stop_byte,stop_byte,
+ start_row,0,old_stop_row,0,stop_row,0)
+ self.valid = false
+
+ for _, cb in ipairs(self.lines_cbs) do
+ cb(bufnr, changed_tick, start_row, old_stop_row, stop_row, old_byte_size)
+ end
+end
+
+function Parser:set_included_ranges(ranges)
+ self._parser:set_included_ranges(ranges)
+ -- The buffer will need to be parsed again later
+ self.valid = false
+end
+
+local M = {
+ parse_query = vim._ts_parse_query,
+}
+
+setmetatable(M, {
+ __index = function (t, k)
+ if k == "TSHighlighter" then
+ t[k] = require'vim.tshighlighter'
+ return t[k]
+ end
+ end
+ })
+
+function M.require_language(lang, path)
+ if vim._ts_has_language(lang) then
+ return true
+ end
+ if path == nil then
+ local fname = 'parser/' .. lang .. '.*'
+ local paths = a.nvim_get_runtime_file(fname, false)
+ if #paths == 0 then
+ -- TODO(bfredl): help tag?
+ error("no parser for '"..lang.."' language")
+ end
+ path = paths[1]
+ end
+ vim._ts_add_language(path, lang)
+end
+
+function M.inspect_language(lang)
+ M.require_language(lang)
+ return vim._ts_inspect_language(lang)
+end
+
+function M.create_parser(bufnr, lang, id)
+ M.require_language(lang)
+ if bufnr == 0 then
+ bufnr = a.nvim_get_current_buf()
+ end
+
+ vim.fn.bufload(bufnr)
+
+ local self = setmetatable({bufnr=bufnr, lang=lang, valid=false}, Parser)
+ self._parser = vim._create_ts_parser(lang)
+ self.changedtree_cbs = {}
+ self.lines_cbs = {}
+ self:parse()
+ -- TODO(bfredl): use weakref to self, so that the parser is free'd is no plugin is
+ -- using it.
+ local function lines_cb(_, ...)
+ return self:_on_lines(...)
+ end
+ local detach_cb = nil
+ if id ~= nil then
+ detach_cb = function()
+ if parsers[id] == self then
+ parsers[id] = nil
+ end
+ end
+ end
+ a.nvim_buf_attach(self.bufnr, false, {on_lines=lines_cb, on_detach=detach_cb})
+ return self
+end
+
+function M.get_parser(bufnr, ft, buf_attach_cbs)
+ if bufnr == nil or bufnr == 0 then
+ bufnr = a.nvim_get_current_buf()
+ end
+ if ft == nil then
+ ft = a.nvim_buf_get_option(bufnr, "filetype")
+ end
+ local id = tostring(bufnr)..'_'..ft
+
+ if parsers[id] == nil then
+ parsers[id] = M.create_parser(bufnr, ft, id)
+ end
+
+ if buf_attach_cbs and buf_attach_cbs.on_changedtree then
+ table.insert(parsers[id].changedtree_cbs, buf_attach_cbs.on_changedtree)
+ end
+
+ if buf_attach_cbs and buf_attach_cbs.on_lines then
+ table.insert(parsers[id].lines_cbs, buf_attach_cbs.on_lines)
+ end
+
+ return parsers[id]
+end
+
+-- query: pattern matching on trees
+-- predicate matching is implemented in lua
+local Query = {}
+Query.__index = Query
+
+local magic_prefixes = {['\\v']=true, ['\\m']=true, ['\\M']=true, ['\\V']=true}
+local function check_magic(str)
+ if string.len(str) < 2 or magic_prefixes[string.sub(str,1,2)] then
+ return str
+ end
+ return '\\v'..str
+end
+
+function M.parse_query(lang, query)
+ M.require_language(lang)
+ local self = setmetatable({}, Query)
+ self.query = vim._ts_parse_query(lang, vim.fn.escape(query,'\\'))
+ self.info = self.query:inspect()
+ self.captures = self.info.captures
+ self.regexes = {}
+ for id,preds in pairs(self.info.patterns) do
+ local regexes = {}
+ for i, pred in ipairs(preds) do
+ if (pred[1] == "match?" and type(pred[2]) == "number"
+ and type(pred[3]) == "string") then
+ regexes[i] = vim.regex(check_magic(pred[3]))
+ end
+ end
+ if next(regexes) then
+ self.regexes[id] = regexes
+ end
+ end
+ return self
+end
+
+local function get_node_text(node, bufnr)
+ local start_row, start_col, end_row, end_col = node:range()
+ if start_row ~= end_row then
+ return nil
+ end
+ local line = a.nvim_buf_get_lines(bufnr, start_row, start_row+1, true)[1]
+ return string.sub(line, start_col+1, end_col)
+end
+
+function Query:match_preds(match, pattern, bufnr)
+ local preds = self.info.patterns[pattern]
+ if not preds then
+ return true
+ end
+ local regexes = self.regexes[pattern]
+ for i, pred in pairs(preds) do
+ -- Here we only want to return if a predicate DOES NOT match, and
+ -- 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.
+ if pred[1] == "eq?" then
+ local node = match[pred[2]]
+ local node_text = get_node_text(node, bufnr)
+
+ local str
+ if type(pred[3]) == "string" then
+ -- (#eq? @aa "foo")
+ str = pred[3]
+ else
+ -- (#eq? @aa @bb)
+ str = get_node_text(match[pred[3]], bufnr)
+ end
+
+ if node_text ~= str or str == nil then
+ return false
+ end
+ elseif pred[1] == "match?" then
+ if not regexes or not regexes[i] then
+ return false
+ end
+ local node = match[pred[2]]
+ local start_row, start_col, end_row, end_col = node:range()
+ if start_row ~= end_row then
+ return false
+ end
+ if not regexes[i]:match_line(bufnr, start_row, start_col, end_col) then
+ return false
+ end
+ end
+ end
+ return true
+end
+
+function Query:iter_captures(node, bufnr, start, stop)
+ if bufnr == 0 then
+ bufnr = vim.api.nvim_get_current_buf()
+ end
+ local raw_iter = node:_rawquery(self.query,true,start,stop)
+ local function iter()
+ local capture, captured_node, match = raw_iter()
+ if match ~= nil then
+ local active = self:match_preds(match, match.pattern, bufnr)
+ match.active = active
+ if not active then
+ return iter() -- tail call: try next match
+ end
+ end
+ return capture, captured_node
+ end
+ return iter
+end
+
+function Query:iter_matches(node, bufnr, start, stop)
+ if bufnr == 0 then
+ bufnr = vim.api.nvim_get_current_buf()
+ end
+ local raw_iter = node:_rawquery(self.query,false,start,stop)
+ local function iter()
+ local pattern, match = raw_iter()
+ if match ~= nil then
+ local active = self:match_preds(match, pattern, bufnr)
+ if not active then
+ return iter() -- tail call: try next match
+ end
+ end
+ return pattern, match
+ end
+ return iter
+end
+
+return M
diff --git a/runtime/lua/vim/tshighlighter.lua b/runtime/lua/vim/tshighlighter.lua
new file mode 100644
index 0000000000..6465751ae8
--- /dev/null
+++ b/runtime/lua/vim/tshighlighter.lua
@@ -0,0 +1,116 @@
+local a = vim.api
+
+-- support reload for quick experimentation
+local TSHighlighter = rawget(vim.treesitter, 'TSHighlighter') or {}
+TSHighlighter.__index = TSHighlighter
+local ts_hs_ns = a.nvim_create_namespace("treesitter_hl")
+
+-- These are conventions defined by tree-sitter, though it
+-- needs to be user extensible also.
+-- TODO(bfredl): this is very much incomplete, we will need to
+-- go through a few tree-sitter provided queries and decide
+-- on translations that makes the most sense.
+TSHighlighter.hl_map = {
+ keyword="Keyword",
+ string="String",
+ type="Type",
+ comment="Comment",
+ constant="Constant",
+ operator="Operator",
+ number="Number",
+ label="Label",
+ ["function"]="Function",
+ ["function.special"]="Function",
+}
+
+function TSHighlighter.new(query, bufnr, ft)
+ local self = setmetatable({}, TSHighlighter)
+ self.parser = vim.treesitter.get_parser(
+ bufnr,
+ ft,
+ {
+ on_changedtree = function(...) self:on_changedtree(...) end,
+ on_lines = function() self.root = self.parser:parse():root() end
+ }
+ )
+
+ self.buf = self.parser.bufnr
+
+ local tree = self.parser:parse()
+ self.root = tree:root()
+ self:set_query(query)
+ self.edit_count = 0
+ self.redraw_count = 0
+ self.line_count = {}
+ a.nvim_buf_set_option(self.buf, "syntax", "")
+
+ -- Tricky: if syntax hasn't been enabled, we need to reload color scheme
+ -- but use synload.vim rather than syntax.vim to not enable
+ -- syntax FileType autocmds. Later on we should integrate with the
+ -- `:syntax` and `set syntax=...` machinery properly.
+ if vim.g.syntax_on ~= 1 then
+ vim.api.nvim_command("runtime! syntax/synload.vim")
+ end
+ return self
+end
+
+local function is_highlight_name(capture_name)
+ local firstc = string.sub(capture_name, 1, 1)
+ return firstc ~= string.lower(firstc)
+end
+
+function TSHighlighter:get_hl_from_capture(capture)
+
+ local name = self.query.captures[capture]
+
+ if is_highlight_name(name) then
+ -- From "Normal.left" only keep "Normal"
+ return vim.split(name, '.', true)[1]
+ else
+ -- Default to false to avoid recomputing
+ return TSHighlighter.hl_map[name]
+ end
+end
+
+function TSHighlighter:set_query(query)
+ if type(query) == "string" then
+ query = vim.treesitter.parse_query(self.parser.lang, query)
+ end
+ self.query = query
+
+ self.hl_cache = setmetatable({}, {
+ __index = function(table, capture)
+ local hl = self:get_hl_from_capture(capture)
+ rawset(table, capture, hl)
+
+ return hl
+ end
+ })
+
+ self:on_changedtree({{self.root:range()}})
+end
+
+function TSHighlighter:on_changedtree(changes)
+ -- Get a fresh root
+ self.root = self.parser.tree:root()
+
+ for _, ch in ipairs(changes or {}) do
+ -- Try to be as exact as possible
+ local changed_node = self.root:descendant_for_range(ch[1], ch[2], ch[3], ch[4])
+
+ a.nvim_buf_clear_namespace(self.buf, ts_hs_ns, ch[1], ch[3])
+
+ for capture, node in self.query:iter_captures(changed_node, self.buf, ch[1], ch[3] + 1) do
+ local start_row, start_col, end_row, end_col = node:range()
+ local hl = self.hl_cache[capture]
+ if hl then
+ a.nvim__buf_add_decoration(self.buf, ts_hs_ns, hl,
+ start_row, start_col,
+ end_row, end_col,
+ {})
+ end
+ end
+ end
+end
+
+return TSHighlighter
diff --git a/runtime/lua/vim/uri.lua b/runtime/lua/vim/uri.lua
new file mode 100644
index 0000000000..9c3535c676
--- /dev/null
+++ b/runtime/lua/vim/uri.lua
@@ -0,0 +1,112 @@
+--- TODO: This is implemented only for files now.
+-- 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 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
+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
+ 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
+end
+
+
+local function is_windows_file_uri(uri)
+ return uri:match('^file:///[a-zA-Z]:') ~= nil
+end
+
+local function uri_from_fname(path)
+ local volume_path, fname = path:match("^([a-zA-Z]:)(.*)")
+ local is_windows = volume_path ~= nil
+ if is_windows then
+ path = volume_path..uri_encode(fname:gsub("\\", "/"))
+ else
+ path = uri_encode(path)
+ end
+ local uri_parts = {"file://"}
+ if is_windows then
+ table.insert(uri_parts, "/")
+ end
+ table.insert(uri_parts, path)
+ return table.concat(uri_parts)
+end
+
+local URI_SCHEME_PATTERN = '^([a-zA-Z]+[a-zA-Z0-9+-.]*)://.*'
+
+local function uri_from_bufnr(bufnr)
+ local fname = vim.api.nvim_buf_get_name(bufnr)
+ local scheme = fname:match(URI_SCHEME_PATTERN)
+ if scheme then
+ return fname
+ else
+ return uri_from_fname(fname)
+ end
+end
+
+local function 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.
+ if is_windows_file_uri(uri) then
+ uri = uri:gsub('^file:///', '')
+ uri = uri:gsub('/', '\\')
+ else
+ uri = uri:gsub('^file://', '')
+ end
+ return uri
+end
+
+-- Return or create a buffer for a uri.
+local function uri_to_bufnr(uri)
+ local scheme = assert(uri:match(URI_SCHEME_PATTERN), 'URI must contain a scheme: ' .. uri)
+ if scheme == 'file' then
+ return vim.fn.bufadd(uri_to_fname(uri))
+ else
+ return vim.fn.bufadd(uri)
+ end
+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
diff --git a/runtime/makemenu.vim b/runtime/makemenu.vim
index ff29d59063..27fa46fe25 100644
--- a/runtime/makemenu.vim
+++ b/runtime/makemenu.vim
@@ -1,6 +1,6 @@
" Script to define the syntax menu in synmenu.vim
" Maintainer: Bram Moolenaar <Bram@vim.org>
-" Last Change: 2018 May 17
+" Last Change: 2019 Dec 07
" This is used by "make menu" in the src directory.
edit <sfile>:p:h/synmenu.vim
@@ -101,6 +101,7 @@ SynMenu AB.AYacc:ayacc
SynMenu AB.B:b
SynMenu AB.Baan:baan
+SynMenu AB.Bash:bash
SynMenu AB.Basic.FreeBasic:freebasic
SynMenu AB.Basic.IBasic:ibasic
SynMenu AB.Basic.QBasic:basic
@@ -128,8 +129,9 @@ SynMenu C.Century\ Term:cterm
SynMenu C.CH\ script:ch
SynMenu C.ChaiScript:chaiscript
SynMenu C.ChangeLog:changelog
-SynMenu C.Cheetah\ template:cheetah
SynMenu C.CHILL:chill
+SynMenu C.Cheetah\ template:cheetah
+SynMenu C.Chicken:chicken
SynMenu C.ChordPro:chordpro
SynMenu C.Clean:clean
SynMenu C.Clever:cl
@@ -160,6 +162,7 @@ SynMenu C.Cyn++:cynpp
SynMenu C.Cynlib:cynlib
SynMenu DE.D:d
+SynMenu DE.Dart:dart
SynMenu DE.Datascript:datascript
SynMenu DE.Debian.Debian\ ChangeLog:debchangelog
SynMenu DE.Debian.Debian\ Control:debcontrol
@@ -192,12 +195,14 @@ SynMenu DE.DTD:dtd
SynMenu DE.DTML\ (Zope):dtml
SynMenu DE.DTrace:dtrace
SynMenu DE.Dts/dtsi:dts
+SynMenu DE.Dune:dune
SynMenu DE.Dylan.Dylan:dylan
SynMenu DE.Dylan.Dylan\ interface:dylanintr
SynMenu DE.Dylan.Dylan\ lid:dylanlid
SynMenu DE.EDIF:edif
SynMenu DE.Eiffel:eiffel
+SynMenu DE.Eight:8th
SynMenu DE.Elinks\ config:elinks
SynMenu DE.Elm\ filter\ rules:elmfilt
SynMenu DE.Embedix\ Component\ Description:ecd
@@ -307,6 +312,7 @@ SynMenu HIJK.Java.JavaCC:javacc
SynMenu HIJK.Java.Java\ Server\ Pages:jsp
SynMenu HIJK.Java.Java\ Properties:jproperties
SynMenu HIJK.JavaScript:javascript
+SynMenu HIJK.JavaScriptReact:javascriptreact
SynMenu HIJK.Jess:jess
SynMenu HIJK.Jgraph:jgraph
SynMenu HIJK.Jovial:jovial
@@ -365,6 +371,7 @@ SynMenu M.Mathematica:mma
SynMenu M.Matlab:matlab
SynMenu M.Maxima:maxima
SynMenu M.MEL\ (for\ Maya):mel
+SynMenu M.Meson:meson
SynMenu M.Messages\ (/var/log):messages
SynMenu M.Metafont:mf
SynMenu M.MetaPost:mp
@@ -467,6 +474,7 @@ SynMenu R.R.R\ help:rhelp
SynMenu R.R.R\ noweb:rnoweb
SynMenu R.Racc\ input:racc
SynMenu R.Radiance:radiance
+SynMenu R.Raml:raml
SynMenu R.Ratpoison:ratpoison
SynMenu R.RCS.RCS\ log\ output:rcslog
SynMenu R.RCS.RCS\ file:rcs
@@ -609,6 +617,8 @@ SynMenu T.Trustees:trustees
SynMenu T.TSS.Command\ Line:tsscl
SynMenu T.TSS.Geometry:tssgm
SynMenu T.TSS.Optics:tssop
+SynMenu T.Typescript:typescript
+SynMenu T.TypescriptReact:typescriptreact
SynMenu UV.Udev\ config:udevconf
SynMenu UV.Udev\ permissions:udevperm
@@ -637,6 +647,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.Webmacro:webmacro
SynMenu WXYZ.Website\ MetaLanguage:wml
SynMenu WXYZ.wDiff:wdiff
diff --git a/runtime/nvim.appdata.xml b/runtime/nvim.appdata.xml
index 32d3c523c6..025de1b5a9 100644
--- a/runtime/nvim.appdata.xml
+++ b/runtime/nvim.appdata.xml
@@ -26,6 +26,10 @@
</screenshots>
<releases>
+ <release date="2019-11-06" version="0.4.3"/>
+ <release date="2019-09-15" version="0.4.2"/>
+ <release date="2019-09-15" version="0.4.1"/>
+ <release date="2019-09-15" version="0.4.0"/>
<release date="2019-07-03" version="0.3.8"/>
<release date="2019-04-29" version="0.3.5"/>
<release date="2019-01-13" version="0.3.4"/>
diff --git a/runtime/optwin.vim b/runtime/optwin.vim
index 51b2bbb583..6b3328a5d4 100644
--- a/runtime/optwin.vim
+++ b/runtime/optwin.vim
@@ -300,6 +300,11 @@ call append("$", "tagstack\ta :tag command will use the tagstack")
call <SID>BinOptionG("tgst", &tgst)
call append("$", "showfulltag\twhen completing tags in Insert mode show more info")
call <SID>BinOptionG("sft", &sft)
+if has("eval")
+ call append("$", "tagfunc\ta function to be used to perform tag searches")
+ call append("$", "\t(local to buffer)")
+ call <SID>OptionL("tfu")
+endif
if has("cscope")
call append("$", "cscopeprg\tcommand for executing cscope")
call <SID>OptionG("csprg", &csprg)
diff --git a/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim b/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim
index a97461ad69..6870bcec75 100644
--- a/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim
+++ b/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim
@@ -37,7 +37,7 @@
" For neovim compatibility, the vim specific calls were replaced with neovim
" specific calls:
" term_start -> term_open
-" term_sendkeys -> jobsend
+" term_sendkeys -> chansend
" term_getline -> getbufline
" job_info && term_getjob -> using linux command ps to get the tty
" balloon -> nvim floating window
@@ -47,8 +47,6 @@
" https://github.com/autozimu/LanguageClient-neovim/blob/0ed9b69dca49c415390a8317b19149f97ae093fa/autoload/LanguageClient.vim#L304
"
" Neovim terminal also works seamlessly on windows, which is why the ability
-" to use the prompt buffer was removed.
-"
" Author: Bram Moolenaar
" Copyright: Vim license applies, see ":help license"
@@ -57,6 +55,12 @@ if exists(':Termdebug')
finish
endif
+" The terminal feature does not work with gdb on win32.
+if !has('win32')
+ let s:way = 'terminal'
+else
+ let s:way = 'prompt'
+endif
let s:keepcpo = &cpo
set cpo&vim
@@ -67,8 +71,8 @@ command -nargs=* -complete=file -bang Termdebug call s:StartDebug(<bang>0, <f-ar
command -nargs=+ -complete=file -bang TermdebugCommand call s:StartDebugCommand(<bang>0, <f-args>)
" Name of the gdb command, defaults to "gdb".
-if !exists('termdebugger')
- let termdebugger = 'gdb'
+if !exists('g:termdebugger')
+ let g:termdebugger = 'gdb'
endif
let s:pc_id = 12
@@ -106,9 +110,14 @@ endfunc
func s:StartDebug_internal(dict)
if exists('s:gdbwin')
- echoerr 'Terminal debugger already running'
+ echoerr 'Terminal debugger already running, cannot run two'
+ return
+ endif
+ if !executable(g:termdebugger)
+ echoerr 'Cannot execute debugger program "' .. g:termdebugger .. '"'
return
endif
+
let s:ptywin = 0
let s:pid = 0
@@ -133,7 +142,19 @@ func s:StartDebug_internal(dict)
let s:vertical = 0
endif
- call s:StartDebug_term(a:dict)
+ " Override using a terminal window by setting g:termdebug_use_prompt to 1.
+ let use_prompt = exists('g:termdebug_use_prompt') && g:termdebug_use_prompt
+ if !has('win32') && !use_prompt
+ let s:way = 'terminal'
+ else
+ let s:way = 'prompt'
+ endif
+
+ if s:way == 'prompt'
+ call s:StartDebug_prompt(a:dict)
+ else
+ call s:StartDebug_term(a:dict)
+ endif
endfunc
" Use when debugger didn't start or ended.
@@ -209,11 +230,11 @@ func s:StartDebug_term(dict)
" Set arguments to be run
if len(proc_args)
- call jobsend(s:gdb_job_id, 'set args ' . join(proc_args) . "\r")
+ call chansend(s:gdb_job_id, 'set args ' . join(proc_args) . "\r")
endif
" Connect gdb to the communication pty, using the GDB/MI interface
- call jobsend(s:gdb_job_id, 'new-ui mi ' . commpty . "\r")
+ call chansend(s:gdb_job_id, 'new-ui mi ' . commpty . "\r")
" Wait for the response to show up, users may not notice the error and wonder
" why the debugger doesn't work.
@@ -226,10 +247,12 @@ func s:StartDebug_term(dict)
endif
let response = ''
- for lnum in range(1,200)
- if len(getbufline(s:gdbbuf, lnum)) > 0 && getbufline(s:gdbbuf, lnum)[0] =~ 'new-ui mi '
+ for lnum in range(1, 200)
+ let line1 = get(getbufline(s:gdbbuf, lnum), 0, '')
+ let line2 = get(getbufline(s:gdbbuf, lnum + 1), 0, '')
+ if line1 =~ 'new-ui mi '
" response can be in the same line or the next line
- let response = getbufline(s:gdbbuf, lnum)[0] . getbufline(s:gdbbuf, lnum + 1)[0]
+ let response = line1 . line2
if response =~ 'Undefined command'
echoerr 'Sorry, your gdb is too old, gdb 7.12 is required'
call s:CloseBuffers()
@@ -239,10 +262,9 @@ func s:StartDebug_term(dict)
" Success!
break
endif
- if response =~ 'Reading symbols from' && response !~ 'new-ui'
- " Reading symbols might take a while
- let try_count -= 1
- endif
+ elseif line1 =~ 'Reading symbols from' && line2 !~ 'new-ui mi '
+ " Reading symbols might take a while, try more times
+ let try_count -= 1
endif
endfor
if response =~ 'New UI allocated'
@@ -270,6 +292,100 @@ func s:StartDebug_term(dict)
call s:StartDebugCommon(a:dict)
endfunc
+func s:StartDebug_prompt(dict)
+ " Open a window with a prompt buffer to run gdb in.
+ if s:vertical
+ vertical new
+ else
+ new
+ endif
+ let s:gdbwin = win_getid(winnr())
+ let s:promptbuf = bufnr('')
+ call prompt_setprompt(s:promptbuf, 'gdb> ')
+ set buftype=prompt
+ file gdb
+ call prompt_setcallback(s:promptbuf, function('s:PromptCallback'))
+ call prompt_setinterrupt(s:promptbuf, function('s:PromptInterrupt'))
+
+ 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.
+ exe (&columns / 2 - 1) . "wincmd |"
+ endif
+
+ " Add -quiet to avoid the intro message causing a hit-enter prompt.
+ let gdb_args = get(a:dict, 'gdb_args', [])
+ let proc_args = get(a:dict, 'proc_args', [])
+
+ let cmd = [g:termdebugger, '-quiet', '--interpreter=mi2'] + gdb_args
+ "call ch_log('executing "' . join(cmd) . '"')
+
+ let s:gdbjob = jobstart(cmd, {
+ \ 'on_exit': function('s:EndPromptDebug'),
+ \ 'on_stdout': function('s:GdbOutCallback'),
+ \ })
+ if s:gdbjob == 0
+ 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:CloseBuffers()
+ return
+ endif
+
+ " Interpret commands while the target is running. This should usualy only
+ " be exec-interrupt, since many commands don't work properly while the
+ " target is running.
+ call s:SendCommand('-gdb-set mi-async on')
+ " Older gdb uses a different command.
+ call s:SendCommand('-gdb-set target-async on')
+
+ let s:ptybuf = 0
+ if has('win32')
+ " MS-Windows: run in a new console window for maximum compatibility
+ call s:SendCommand('set new-console on')
+ 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')
+ if s:pty_job_id == 0
+ 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'
+ 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())
+ call s:SendCommand('tty ' . pty)
+
+ " Since GDB runs in a prompt window, the environment has not been set to
+ " match a terminal window, need to do that now.
+ call s:SendCommand('set env TERM = xterm-color')
+ call s:SendCommand('set env ROWS = ' . winheight(s:ptywin))
+ call s:SendCommand('set env LINES = ' . winheight(s:ptywin))
+ call s:SendCommand('set env COLUMNS = ' . winwidth(s:ptywin))
+ call s:SendCommand('set env COLORS = ' . &t_Co)
+ call s:SendCommand('set env VIM_TERMINAL = ' . v:version)
+ endif
+ call s:SendCommand('set print pretty on')
+ call s:SendCommand('set breakpoint pending on')
+ " Disable pagination, it causes everything to stop at the gdb
+ call s:SendCommand('set pagination off')
+
+ " Set arguments to be run
+ if len(proc_args)
+ call s:SendCommand('set args ' . join(proc_args))
+ endif
+
+ call s:StartDebugCommon(a:dict)
+ startinsert
+endfunc
func s:StartDebugCommon(dict)
" Sign used to highlight the line where the program has stopped.
@@ -311,23 +427,104 @@ endfunc
" Send a command to gdb. "cmd" is the string without line terminator.
func s:SendCommand(cmd)
"call ch_log('sending to gdb: ' . a:cmd)
- call jobsend(s:comm_job_id, a:cmd . "\r")
+ if s:way == 'prompt'
+ call chansend(s:gdbjob, a:cmd . "\n")
+ else
+ call chansend(s:comm_job_id, a:cmd . "\r")
+ endif
endfunc
" This is global so that a user can create their mappings with this.
func TermDebugSendCommand(cmd)
- let do_continue = 0
- if !s:stopped
- let do_continue = 1
- call s:SendCommand('-exec-interrupt')
- sleep 10m
+ if s:way == 'prompt'
+ call chansend(s:gdbjob, a:cmd . "\n")
+ else
+ let do_continue = 0
+ if !s:stopped
+ let do_continue = 1
+ if s:way == 'prompt'
+ " Need to send a signal to get the UI to listen. Strangely this is only
+ " needed once.
+ call jobstop(s:gdbjob)
+ else
+ call s:SendCommand('-exec-interrupt')
+ endif
+ sleep 10m
+ endif
+ call chansend(s:gdb_job_id, a:cmd . "\r")
+ if do_continue
+ Continue
+ endif
endif
- call jobsend(s:gdb_job_id, a:cmd . "\r")
- if do_continue
- Continue
+endfunc
+
+" Function called when entering a line in the prompt buffer.
+func s:PromptCallback(text)
+ call s:SendCommand(a:text)
+endfunc
+
+" Function called when pressing CTRL-C in the prompt buffer and when placing a
+" breakpoint.
+func s:PromptInterrupt()
+ " call ch_log('Interrupting gdb')
+ if has('win32')
+ " 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'
+ else
+ call debugbreak(s:pid)
+ endif
+ else
+ call jobstop(s:gdbjob)
endif
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 lines = []
+ let index = 0
+
+ for msg in a:msgs
+ if msg =~ '^^error,msg='
+ if exists('s:evalexpr')
+ \ && s:DecodeMessage(msg[11:])
+ \ =~ '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)
+ continue
+ endif
+ let index += 1
+ endfor
+
+ let curwinid = win_getid(winnr())
+ call win_gotoid(s:gdbwin)
+
+ " Add the output above the current prompt.
+ for line in lines
+ call append(line('$') - 1, line)
+ endfor
+ if !empty(lines)
+ set modified
+ endif
+
+ call win_gotoid(curwinid)
+ call s:CommOutput(a:job_id, a:msgs, a:event)
+endfunc
+
" Decode a message from gdb. quotedText starts with a ", return the text up
" to the next ", unescaping characters.
func s:DecodeMessage(quotedText)
@@ -391,6 +588,19 @@ func s:EndDebugCommon()
au! TermDebug
endfunc
+func s:EndPromptDebug(job_id, exit_code, event)
+ let curwinid = win_getid(winnr())
+ call win_gotoid(s:gdbwin)
+ close
+ if curwinid != s:gdbwin
+ call win_gotoid(curwinid)
+ endif
+
+ call s:EndDebugCommon()
+ unlet s:gdbwin
+ "call ch_log("Returning from EndPromptDebug()")
+endfunc
+
func s:CommOutput(job_id, msgs, event)
for msg in a:msgs
@@ -431,7 +641,11 @@ func s:InstallCommands()
command Stop call s:SendCommand('-exec-interrupt')
" using -exec-continue results in CTRL-C in gdb window not working
- command Continue call jobsend(s:gdb_job_id, "continue\r")
+ if s:way == 'prompt'
+ command Continue call s:SendCommand('continue')
+ else
+ command Continue call chansend(s:gdb_job_id, "continue\r")
+ endif
command -range -nargs=* Evaluate call s:Evaluate(<range>, <q-args>)
command Gdb call win_gotoid(s:gdbwin)
@@ -445,7 +659,20 @@ func s:InstallCommands()
let &cpo = save_cpo
endfunc
-let s:winbar_winids = []
+" let s:winbar_winids = []
+
+" Install the window toolbar in the current window.
+func s:InstallWinbar()
+ " if has('menu') && &mouse != ''
+ " nnoremenu WinBar.Step :Step<CR>
+ " nnoremenu WinBar.Next :Over<CR>
+ " nnoremenu WinBar.Finish :Finish<CR>
+ " nnoremenu WinBar.Cont :Continue<CR>
+ " nnoremenu WinBar.Stop :Stop<CR>
+ " nnoremenu WinBar.Eval :Evaluate<CR>
+ " call add(s:winbar_winids, win_getid(winnr()))
+ " endif
+endfunc
" Delete installed debugger commands in the current window.
func s:DeleteCommands()
@@ -489,7 +716,11 @@ func s:SetBreakpoint()
let do_continue = 0
if !s:stopped
let do_continue = 1
- call s:SendCommand('-exec-interrupt')
+ if s:way == 'prompt'
+ call s:PromptInterrupt()
+ else
+ call s:SendCommand('-exec-interrupt')
+ endif
sleep 10m
endif
" Use the fname:lnum format, older gdb can't handle --source.
@@ -578,6 +809,7 @@ func s:HandleEvaluate(msg)
endif
let s:evalFromBalloonExprResult = split(s:evalFromBalloonExprResult, '\\n')
call s:OpenHoverPreview(s:evalFromBalloonExprResult, v:null)
+ let s:evalFromBalloonExprResult = ''
else
echomsg '"' . s:evalexpr . '": ' . value
endif
diff --git a/runtime/plugin/man.vim b/runtime/plugin/man.vim
index e18a5528bb..e762eb3664 100644
--- a/runtime/plugin/man.vim
+++ b/runtime/plugin/man.vim
@@ -1,4 +1,4 @@
-" Maintainer: Anmol Sethi <anmol@aubble.com>
+" Maintainer: Anmol Sethi <hi@nhooyr.io>
if exists('g:loaded_man')
finish
diff --git a/runtime/scripts.vim b/runtime/scripts.vim
index a690431014..6aae2b1ec3 100644
--- a/runtime/scripts.vim
+++ b/runtime/scripts.vim
@@ -1,7 +1,7 @@
" Vim support file to detect file types in scripts
"
" Maintainer: Bram Moolenaar <Bram@vim.org>
-" Last change: 2019 Jun 25
+" Last change: 2020 Jun 07
" This file is called by an autocommand for every file that has just been
" loaded into a buffer. It checks if the type of file can be recognized by
@@ -35,10 +35,12 @@ let s:line1 = getline(1)
if s:line1 =~# "^#!"
" A script that starts with "#!".
- " Check for a line like "#!/usr/bin/env VAR=val bash". Turn it into
+ " Check for a line like "#!/usr/bin/env {options} bash". Turn it into
" "#!/usr/bin/bash" to make matching easier.
+ " Recognize only a few {options} that are commonly used.
if s:line1 =~# '^#!\s*\S*\<env\s'
let s:line1 = substitute(s:line1, '\S\+=\S\+', '', 'g')
+ let s:line1 = substitute(s:line1, '\(-[iS]\|--ignore-environment\|--split-string\)', '', '')
let s:line1 = substitute(s:line1, '\<env\s\+', '', '')
endif
@@ -376,6 +378,10 @@ else
elseif s:line1 =~? '-\*-.*erlang.*-\*-'
set ft=erlang
+ " YAML
+ elseif s:line1 =~# '^%YAML'
+ set ft=yaml
+
" CVS diff
else
let s:lnum = 1
diff --git a/runtime/synmenu.vim b/runtime/synmenu.vim
index 70e0e83ba7..3367c68d3b 100644
--- a/runtime/synmenu.vim
+++ b/runtime/synmenu.vim
@@ -87,19 +87,20 @@ an 50.10.580 &Syntax.AB.Awk :cal SetSyn("awk")<CR>
an 50.10.590 &Syntax.AB.AYacc :cal SetSyn("ayacc")<CR>
an 50.10.610 &Syntax.AB.B :cal SetSyn("b")<CR>
an 50.10.620 &Syntax.AB.Baan :cal SetSyn("baan")<CR>
-an 50.10.630 &Syntax.AB.Basic.FreeBasic :cal SetSyn("freebasic")<CR>
-an 50.10.640 &Syntax.AB.Basic.IBasic :cal SetSyn("ibasic")<CR>
-an 50.10.650 &Syntax.AB.Basic.QBasic :cal SetSyn("basic")<CR>
-an 50.10.660 &Syntax.AB.Basic.Visual\ Basic :cal SetSyn("vb")<CR>
-an 50.10.670 &Syntax.AB.Bazaar\ commit\ file :cal SetSyn("bzr")<CR>
-an 50.10.680 &Syntax.AB.Bazel :cal SetSyn("bzl")<CR>
-an 50.10.690 &Syntax.AB.BC\ calculator :cal SetSyn("bc")<CR>
-an 50.10.700 &Syntax.AB.BDF\ font :cal SetSyn("bdf")<CR>
-an 50.10.710 &Syntax.AB.BibTeX.Bibliography\ database :cal SetSyn("bib")<CR>
-an 50.10.720 &Syntax.AB.BibTeX.Bibliography\ Style :cal SetSyn("bst")<CR>
-an 50.10.730 &Syntax.AB.BIND.BIND\ config :cal SetSyn("named")<CR>
-an 50.10.740 &Syntax.AB.BIND.BIND\ zone :cal SetSyn("bindzone")<CR>
-an 50.10.750 &Syntax.AB.Blank :cal SetSyn("blank")<CR>
+an 50.10.630 &Syntax.AB.Bash :cal SetSyn("bash")<CR>
+an 50.10.640 &Syntax.AB.Basic.FreeBasic :cal SetSyn("freebasic")<CR>
+an 50.10.650 &Syntax.AB.Basic.IBasic :cal SetSyn("ibasic")<CR>
+an 50.10.660 &Syntax.AB.Basic.QBasic :cal SetSyn("basic")<CR>
+an 50.10.670 &Syntax.AB.Basic.Visual\ Basic :cal SetSyn("vb")<CR>
+an 50.10.680 &Syntax.AB.Bazaar\ commit\ file :cal SetSyn("bzr")<CR>
+an 50.10.690 &Syntax.AB.Bazel :cal SetSyn("bzl")<CR>
+an 50.10.700 &Syntax.AB.BC\ calculator :cal SetSyn("bc")<CR>
+an 50.10.710 &Syntax.AB.BDF\ font :cal SetSyn("bdf")<CR>
+an 50.10.720 &Syntax.AB.BibTeX.Bibliography\ database :cal SetSyn("bib")<CR>
+an 50.10.730 &Syntax.AB.BibTeX.Bibliography\ Style :cal SetSyn("bst")<CR>
+an 50.10.740 &Syntax.AB.BIND.BIND\ config :cal SetSyn("named")<CR>
+an 50.10.750 &Syntax.AB.BIND.BIND\ zone :cal SetSyn("bindzone")<CR>
+an 50.10.760 &Syntax.AB.Blank :cal SetSyn("blank")<CR>
an 50.20.100 &Syntax.C.C :cal SetSyn("c")<CR>
an 50.20.110 &Syntax.C.C++ :cal SetSyn("cpp")<CR>
an 50.20.120 &Syntax.C.C# :cal SetSyn("cs")<CR>
@@ -113,89 +114,93 @@ an 50.20.190 &Syntax.C.Century\ Term :cal SetSyn("cterm")<CR>
an 50.20.200 &Syntax.C.CH\ script :cal SetSyn("ch")<CR>
an 50.20.210 &Syntax.C.ChaiScript :cal SetSyn("chaiscript")<CR>
an 50.20.220 &Syntax.C.ChangeLog :cal SetSyn("changelog")<CR>
-an 50.20.230 &Syntax.C.Cheetah\ template :cal SetSyn("cheetah")<CR>
-an 50.20.240 &Syntax.C.CHILL :cal SetSyn("chill")<CR>
-an 50.20.250 &Syntax.C.ChordPro :cal SetSyn("chordpro")<CR>
-an 50.20.260 &Syntax.C.Clean :cal SetSyn("clean")<CR>
-an 50.20.270 &Syntax.C.Clever :cal SetSyn("cl")<CR>
-an 50.20.280 &Syntax.C.Clipper :cal SetSyn("clipper")<CR>
-an 50.20.290 &Syntax.C.Clojure :cal SetSyn("clojure")<CR>
-an 50.20.300 &Syntax.C.Cmake :cal SetSyn("cmake")<CR>
-an 50.20.310 &Syntax.C.Cmod :cal SetSyn("cmod")<CR>
-an 50.20.320 &Syntax.C.Cmusrc :cal SetSyn("cmusrc")<CR>
-an 50.20.330 &Syntax.C.Cobol :cal SetSyn("cobol")<CR>
-an 50.20.340 &Syntax.C.Coco/R :cal SetSyn("coco")<CR>
-an 50.20.350 &Syntax.C.Cold\ Fusion :cal SetSyn("cf")<CR>
-an 50.20.360 &Syntax.C.Conary\ Recipe :cal SetSyn("conaryrecipe")<CR>
-an 50.20.370 &Syntax.C.Config.Cfg\ Config\ file :cal SetSyn("cfg")<CR>
-an 50.20.380 &Syntax.C.Config.Configure\.in :cal SetSyn("config")<CR>
-an 50.20.390 &Syntax.C.Config.Generic\ Config\ file :cal SetSyn("conf")<CR>
-an 50.20.400 &Syntax.C.CRM114 :cal SetSyn("crm")<CR>
-an 50.20.410 &Syntax.C.Crontab :cal SetSyn("crontab")<CR>
-an 50.20.420 &Syntax.C.CSDL :cal SetSyn("csdl")<CR>
-an 50.20.430 &Syntax.C.CSP :cal SetSyn("csp")<CR>
-an 50.20.440 &Syntax.C.Ctrl-H :cal SetSyn("ctrlh")<CR>
-an 50.20.450 &Syntax.C.Cucumber :cal SetSyn("cucumber")<CR>
-an 50.20.460 &Syntax.C.CUDA :cal SetSyn("cuda")<CR>
-an 50.20.470 &Syntax.C.CUPL.CUPL :cal SetSyn("cupl")<CR>
-an 50.20.480 &Syntax.C.CUPL.Simulation :cal SetSyn("cuplsim")<CR>
-an 50.20.490 &Syntax.C.CVS.commit\ file :cal SetSyn("cvs")<CR>
-an 50.20.500 &Syntax.C.CVS.cvsrc :cal SetSyn("cvsrc")<CR>
-an 50.20.510 &Syntax.C.Cyn++ :cal SetSyn("cynpp")<CR>
-an 50.20.520 &Syntax.C.Cynlib :cal SetSyn("cynlib")<CR>
+an 50.20.230 &Syntax.C.CHILL :cal SetSyn("chill")<CR>
+an 50.20.240 &Syntax.C.Cheetah\ template :cal SetSyn("cheetah")<CR>
+an 50.20.250 &Syntax.C.Chicken :cal SetSyn("chicken")<CR>
+an 50.20.260 &Syntax.C.ChordPro :cal SetSyn("chordpro")<CR>
+an 50.20.270 &Syntax.C.Clean :cal SetSyn("clean")<CR>
+an 50.20.280 &Syntax.C.Clever :cal SetSyn("cl")<CR>
+an 50.20.290 &Syntax.C.Clipper :cal SetSyn("clipper")<CR>
+an 50.20.300 &Syntax.C.Clojure :cal SetSyn("clojure")<CR>
+an 50.20.310 &Syntax.C.Cmake :cal SetSyn("cmake")<CR>
+an 50.20.320 &Syntax.C.Cmod :cal SetSyn("cmod")<CR>
+an 50.20.330 &Syntax.C.Cmusrc :cal SetSyn("cmusrc")<CR>
+an 50.20.340 &Syntax.C.Cobol :cal SetSyn("cobol")<CR>
+an 50.20.350 &Syntax.C.Coco/R :cal SetSyn("coco")<CR>
+an 50.20.360 &Syntax.C.Cold\ Fusion :cal SetSyn("cf")<CR>
+an 50.20.370 &Syntax.C.Conary\ Recipe :cal SetSyn("conaryrecipe")<CR>
+an 50.20.380 &Syntax.C.Config.Cfg\ Config\ file :cal SetSyn("cfg")<CR>
+an 50.20.390 &Syntax.C.Config.Configure\.in :cal SetSyn("config")<CR>
+an 50.20.400 &Syntax.C.Config.Generic\ Config\ file :cal SetSyn("conf")<CR>
+an 50.20.410 &Syntax.C.CRM114 :cal SetSyn("crm")<CR>
+an 50.20.420 &Syntax.C.Crontab :cal SetSyn("crontab")<CR>
+an 50.20.430 &Syntax.C.CSDL :cal SetSyn("csdl")<CR>
+an 50.20.440 &Syntax.C.CSP :cal SetSyn("csp")<CR>
+an 50.20.450 &Syntax.C.Ctrl-H :cal SetSyn("ctrlh")<CR>
+an 50.20.460 &Syntax.C.Cucumber :cal SetSyn("cucumber")<CR>
+an 50.20.470 &Syntax.C.CUDA :cal SetSyn("cuda")<CR>
+an 50.20.480 &Syntax.C.CUPL.CUPL :cal SetSyn("cupl")<CR>
+an 50.20.490 &Syntax.C.CUPL.Simulation :cal SetSyn("cuplsim")<CR>
+an 50.20.500 &Syntax.C.CVS.commit\ file :cal SetSyn("cvs")<CR>
+an 50.20.510 &Syntax.C.CVS.cvsrc :cal SetSyn("cvsrc")<CR>
+an 50.20.520 &Syntax.C.Cyn++ :cal SetSyn("cynpp")<CR>
+an 50.20.530 &Syntax.C.Cynlib :cal SetSyn("cynlib")<CR>
an 50.30.100 &Syntax.DE.D :cal SetSyn("d")<CR>
-an 50.30.110 &Syntax.DE.Datascript :cal SetSyn("datascript")<CR>
-an 50.30.120 &Syntax.DE.Debian.Debian\ ChangeLog :cal SetSyn("debchangelog")<CR>
-an 50.30.130 &Syntax.DE.Debian.Debian\ Control :cal SetSyn("debcontrol")<CR>
-an 50.30.140 &Syntax.DE.Debian.Debian\ Copyright :cal SetSyn("debcopyright")<CR>
-an 50.30.150 &Syntax.DE.Debian.Debian\ Sources\.list :cal SetSyn("debsources")<CR>
-an 50.30.160 &Syntax.DE.Denyhosts :cal SetSyn("denyhosts")<CR>
-an 50.30.170 &Syntax.DE.Desktop :cal SetSyn("desktop")<CR>
-an 50.30.180 &Syntax.DE.Dict\ config :cal SetSyn("dictconf")<CR>
-an 50.30.190 &Syntax.DE.Dictd\ config :cal SetSyn("dictdconf")<CR>
-an 50.30.200 &Syntax.DE.Diff :cal SetSyn("diff")<CR>
-an 50.30.210 &Syntax.DE.Digital\ Command\ Lang :cal SetSyn("dcl")<CR>
-an 50.30.220 &Syntax.DE.Dircolors :cal SetSyn("dircolors")<CR>
-an 50.30.230 &Syntax.DE.Dirpager :cal SetSyn("dirpager")<CR>
-an 50.30.240 &Syntax.DE.Django\ template :cal SetSyn("django")<CR>
-an 50.30.250 &Syntax.DE.DNS/BIND\ zone :cal SetSyn("bindzone")<CR>
-an 50.30.260 &Syntax.DE.Dnsmasq\ config :cal SetSyn("dnsmasq")<CR>
-an 50.30.270 &Syntax.DE.DocBook.auto-detect :cal SetSyn("docbk")<CR>
-an 50.30.280 &Syntax.DE.DocBook.SGML :cal SetSyn("docbksgml")<CR>
-an 50.30.290 &Syntax.DE.DocBook.XML :cal SetSyn("docbkxml")<CR>
-an 50.30.300 &Syntax.DE.Dockerfile :cal SetSyn("dockerfile")<CR>
-an 50.30.310 &Syntax.DE.Dot :cal SetSyn("dot")<CR>
-an 50.30.320 &Syntax.DE.Doxygen.C\ with\ doxygen :cal SetSyn("c.doxygen")<CR>
-an 50.30.330 &Syntax.DE.Doxygen.C++\ with\ doxygen :cal SetSyn("cpp.doxygen")<CR>
-an 50.30.340 &Syntax.DE.Doxygen.IDL\ with\ doxygen :cal SetSyn("idl.doxygen")<CR>
-an 50.30.350 &Syntax.DE.Doxygen.Java\ with\ doxygen :cal SetSyn("java.doxygen")<CR>
-an 50.30.360 &Syntax.DE.Doxygen.DataScript\ with\ doxygen :cal SetSyn("datascript.doxygen")<CR>
-an 50.30.370 &Syntax.DE.Dracula :cal SetSyn("dracula")<CR>
-an 50.30.380 &Syntax.DE.DSSSL :cal SetSyn("dsl")<CR>
-an 50.30.390 &Syntax.DE.DTD :cal SetSyn("dtd")<CR>
-an 50.30.400 &Syntax.DE.DTML\ (Zope) :cal SetSyn("dtml")<CR>
-an 50.30.410 &Syntax.DE.DTrace :cal SetSyn("dtrace")<CR>
-an 50.30.420 &Syntax.DE.Dts/dtsi :cal SetSyn("dts")<CR>
-an 50.30.430 &Syntax.DE.Dylan.Dylan :cal SetSyn("dylan")<CR>
-an 50.30.440 &Syntax.DE.Dylan.Dylan\ interface :cal SetSyn("dylanintr")<CR>
-an 50.30.450 &Syntax.DE.Dylan.Dylan\ lid :cal SetSyn("dylanlid")<CR>
-an 50.30.470 &Syntax.DE.EDIF :cal SetSyn("edif")<CR>
-an 50.30.480 &Syntax.DE.Eiffel :cal SetSyn("eiffel")<CR>
-an 50.30.490 &Syntax.DE.Elinks\ config :cal SetSyn("elinks")<CR>
-an 50.30.500 &Syntax.DE.Elm\ filter\ rules :cal SetSyn("elmfilt")<CR>
-an 50.30.510 &Syntax.DE.Embedix\ Component\ Description :cal SetSyn("ecd")<CR>
-an 50.30.520 &Syntax.DE.ERicsson\ LANGuage :cal SetSyn("erlang")<CR>
-an 50.30.530 &Syntax.DE.ESMTP\ rc :cal SetSyn("esmtprc")<CR>
-an 50.30.540 &Syntax.DE.ESQL-C :cal SetSyn("esqlc")<CR>
-an 50.30.550 &Syntax.DE.Essbase\ script :cal SetSyn("csc")<CR>
-an 50.30.560 &Syntax.DE.Esterel :cal SetSyn("esterel")<CR>
-an 50.30.570 &Syntax.DE.Eterm\ config :cal SetSyn("eterm")<CR>
-an 50.30.580 &Syntax.DE.Euphoria\ 3 :cal SetSyn("euphoria3")<CR>
-an 50.30.590 &Syntax.DE.Euphoria\ 4 :cal SetSyn("euphoria4")<CR>
-an 50.30.600 &Syntax.DE.Eviews :cal SetSyn("eviews")<CR>
-an 50.30.610 &Syntax.DE.Exim\ conf :cal SetSyn("exim")<CR>
-an 50.30.620 &Syntax.DE.Expect :cal SetSyn("expect")<CR>
-an 50.30.630 &Syntax.DE.Exports :cal SetSyn("exports")<CR>
+an 50.30.110 &Syntax.DE.Dart :cal SetSyn("dart")<CR>
+an 50.30.120 &Syntax.DE.Datascript :cal SetSyn("datascript")<CR>
+an 50.30.130 &Syntax.DE.Debian.Debian\ ChangeLog :cal SetSyn("debchangelog")<CR>
+an 50.30.140 &Syntax.DE.Debian.Debian\ Control :cal SetSyn("debcontrol")<CR>
+an 50.30.150 &Syntax.DE.Debian.Debian\ Copyright :cal SetSyn("debcopyright")<CR>
+an 50.30.160 &Syntax.DE.Debian.Debian\ Sources\.list :cal SetSyn("debsources")<CR>
+an 50.30.170 &Syntax.DE.Denyhosts :cal SetSyn("denyhosts")<CR>
+an 50.30.180 &Syntax.DE.Desktop :cal SetSyn("desktop")<CR>
+an 50.30.190 &Syntax.DE.Dict\ config :cal SetSyn("dictconf")<CR>
+an 50.30.200 &Syntax.DE.Dictd\ config :cal SetSyn("dictdconf")<CR>
+an 50.30.210 &Syntax.DE.Diff :cal SetSyn("diff")<CR>
+an 50.30.220 &Syntax.DE.Digital\ Command\ Lang :cal SetSyn("dcl")<CR>
+an 50.30.230 &Syntax.DE.Dircolors :cal SetSyn("dircolors")<CR>
+an 50.30.240 &Syntax.DE.Dirpager :cal SetSyn("dirpager")<CR>
+an 50.30.250 &Syntax.DE.Django\ template :cal SetSyn("django")<CR>
+an 50.30.260 &Syntax.DE.DNS/BIND\ zone :cal SetSyn("bindzone")<CR>
+an 50.30.270 &Syntax.DE.Dnsmasq\ config :cal SetSyn("dnsmasq")<CR>
+an 50.30.280 &Syntax.DE.DocBook.auto-detect :cal SetSyn("docbk")<CR>
+an 50.30.290 &Syntax.DE.DocBook.SGML :cal SetSyn("docbksgml")<CR>
+an 50.30.300 &Syntax.DE.DocBook.XML :cal SetSyn("docbkxml")<CR>
+an 50.30.310 &Syntax.DE.Dockerfile :cal SetSyn("dockerfile")<CR>
+an 50.30.320 &Syntax.DE.Dot :cal SetSyn("dot")<CR>
+an 50.30.330 &Syntax.DE.Doxygen.C\ with\ doxygen :cal SetSyn("c.doxygen")<CR>
+an 50.30.340 &Syntax.DE.Doxygen.C++\ with\ doxygen :cal SetSyn("cpp.doxygen")<CR>
+an 50.30.350 &Syntax.DE.Doxygen.IDL\ with\ doxygen :cal SetSyn("idl.doxygen")<CR>
+an 50.30.360 &Syntax.DE.Doxygen.Java\ with\ doxygen :cal SetSyn("java.doxygen")<CR>
+an 50.30.370 &Syntax.DE.Doxygen.DataScript\ with\ doxygen :cal SetSyn("datascript.doxygen")<CR>
+an 50.30.380 &Syntax.DE.Dracula :cal SetSyn("dracula")<CR>
+an 50.30.390 &Syntax.DE.DSSSL :cal SetSyn("dsl")<CR>
+an 50.30.400 &Syntax.DE.DTD :cal SetSyn("dtd")<CR>
+an 50.30.410 &Syntax.DE.DTML\ (Zope) :cal SetSyn("dtml")<CR>
+an 50.30.420 &Syntax.DE.DTrace :cal SetSyn("dtrace")<CR>
+an 50.30.430 &Syntax.DE.Dts/dtsi :cal SetSyn("dts")<CR>
+an 50.30.440 &Syntax.DE.Dune :cal SetSyn("dune")<CR>
+an 50.30.450 &Syntax.DE.Dylan.Dylan :cal SetSyn("dylan")<CR>
+an 50.30.460 &Syntax.DE.Dylan.Dylan\ interface :cal SetSyn("dylanintr")<CR>
+an 50.30.470 &Syntax.DE.Dylan.Dylan\ lid :cal SetSyn("dylanlid")<CR>
+an 50.30.490 &Syntax.DE.EDIF :cal SetSyn("edif")<CR>
+an 50.30.500 &Syntax.DE.Eiffel :cal SetSyn("eiffel")<CR>
+an 50.30.510 &Syntax.DE.Eight :cal SetSyn("8th")<CR>
+an 50.30.520 &Syntax.DE.Elinks\ config :cal SetSyn("elinks")<CR>
+an 50.30.530 &Syntax.DE.Elm\ filter\ rules :cal SetSyn("elmfilt")<CR>
+an 50.30.540 &Syntax.DE.Embedix\ Component\ Description :cal SetSyn("ecd")<CR>
+an 50.30.550 &Syntax.DE.ERicsson\ LANGuage :cal SetSyn("erlang")<CR>
+an 50.30.560 &Syntax.DE.ESMTP\ rc :cal SetSyn("esmtprc")<CR>
+an 50.30.570 &Syntax.DE.ESQL-C :cal SetSyn("esqlc")<CR>
+an 50.30.580 &Syntax.DE.Essbase\ script :cal SetSyn("csc")<CR>
+an 50.30.590 &Syntax.DE.Esterel :cal SetSyn("esterel")<CR>
+an 50.30.600 &Syntax.DE.Eterm\ config :cal SetSyn("eterm")<CR>
+an 50.30.610 &Syntax.DE.Euphoria\ 3 :cal SetSyn("euphoria3")<CR>
+an 50.30.620 &Syntax.DE.Euphoria\ 4 :cal SetSyn("euphoria4")<CR>
+an 50.30.630 &Syntax.DE.Eviews :cal SetSyn("eviews")<CR>
+an 50.30.640 &Syntax.DE.Exim\ conf :cal SetSyn("exim")<CR>
+an 50.30.650 &Syntax.DE.Expect :cal SetSyn("expect")<CR>
+an 50.30.660 &Syntax.DE.Exports :cal SetSyn("exports")<CR>
an 50.40.100 &Syntax.FG.Falcon :cal SetSyn("falcon")<CR>
an 50.40.110 &Syntax.FG.Fantom :cal SetSyn("fan")<CR>
an 50.40.120 &Syntax.FG.Fetchmail :cal SetSyn("fetchmail")<CR>
@@ -259,43 +264,44 @@ an 50.50.290 &Syntax.HIJK.HTML.XHTML :cal SetSyn("xhtml")<CR>
an 50.50.300 &Syntax.HIJK.Host\.conf :cal SetSyn("hostconf")<CR>
an 50.50.310 &Syntax.HIJK.Hosts\ access :cal SetSyn("hostsaccess")<CR>
an 50.50.320 &Syntax.HIJK.Hyper\ Builder :cal SetSyn("hb")<CR>
-an 50.50.330 &Syntax.HIJK.Icewm\ menu :cal SetSyn("icemenu")<CR>
-an 50.50.340 &Syntax.HIJK.Icon :cal SetSyn("icon")<CR>
-an 50.50.350 &Syntax.HIJK.IDL\Generic\ IDL :cal SetSyn("idl")<CR>
-an 50.50.360 &Syntax.HIJK.IDL\Microsoft\ IDL :cal SetSyn("msidl")<CR>
-an 50.50.370 &Syntax.HIJK.Indent\ profile :cal SetSyn("indent")<CR>
-an 50.50.380 &Syntax.HIJK.Inform :cal SetSyn("inform")<CR>
-an 50.50.390 &Syntax.HIJK.Informix\ 4GL :cal SetSyn("fgl")<CR>
-an 50.50.400 &Syntax.HIJK.Initng :cal SetSyn("initng")<CR>
-an 50.50.410 &Syntax.HIJK.Inittab :cal SetSyn("inittab")<CR>
-an 50.50.420 &Syntax.HIJK.Inno\ setup :cal SetSyn("iss")<CR>
-an 50.50.430 &Syntax.HIJK.Innovation\ Data\ Processing.Upstream\ dat :cal SetSyn("upstreamdat")<CR>
-an 50.50.440 &Syntax.HIJK.Innovation\ Data\ Processing.Upstream\ log :cal SetSyn("upstreamlog")<CR>
-an 50.50.450 &Syntax.HIJK.Innovation\ Data\ Processing.Upstream\ rpt :cal SetSyn("upstreamrpt")<CR>
-an 50.50.460 &Syntax.HIJK.Innovation\ Data\ Processing.Upstream\ Install\ log :cal SetSyn("upstreaminstalllog")<CR>
-an 50.50.470 &Syntax.HIJK.Innovation\ Data\ Processing.Usserver\ log :cal SetSyn("usserverlog")<CR>
-an 50.50.480 &Syntax.HIJK.Innovation\ Data\ Processing.USW2KAgt\ log :cal SetSyn("usw2kagtlog")<CR>
-an 50.50.490 &Syntax.HIJK.InstallShield\ script :cal SetSyn("ishd")<CR>
-an 50.50.500 &Syntax.HIJK.Interactive\ Data\ Lang :cal SetSyn("idlang")<CR>
-an 50.50.510 &Syntax.HIJK.IPfilter :cal SetSyn("ipfilter")<CR>
-an 50.50.530 &Syntax.HIJK.J :cal SetSyn("j")<CR>
-an 50.50.540 &Syntax.HIJK.JAL :cal SetSyn("jal")<CR>
-an 50.50.550 &Syntax.HIJK.JAM :cal SetSyn("jam")<CR>
-an 50.50.560 &Syntax.HIJK.Jargon :cal SetSyn("jargon")<CR>
-an 50.50.570 &Syntax.HIJK.Java.Java :cal SetSyn("java")<CR>
-an 50.50.580 &Syntax.HIJK.Java.JavaCC :cal SetSyn("javacc")<CR>
-an 50.50.590 &Syntax.HIJK.Java.Java\ Server\ Pages :cal SetSyn("jsp")<CR>
-an 50.50.600 &Syntax.HIJK.Java.Java\ Properties :cal SetSyn("jproperties")<CR>
-an 50.50.610 &Syntax.HIJK.JavaScript :cal SetSyn("javascript")<CR>
-an 50.50.620 &Syntax.HIJK.Jess :cal SetSyn("jess")<CR>
-an 50.50.630 &Syntax.HIJK.Jgraph :cal SetSyn("jgraph")<CR>
-an 50.50.640 &Syntax.HIJK.Jovial :cal SetSyn("jovial")<CR>
-an 50.50.650 &Syntax.HIJK.JSON :cal SetSyn("json")<CR>
-an 50.50.670 &Syntax.HIJK.Kconfig :cal SetSyn("kconfig")<CR>
-an 50.50.680 &Syntax.HIJK.KDE\ script :cal SetSyn("kscript")<CR>
-an 50.50.690 &Syntax.HIJK.Kimwitu++ :cal SetSyn("kwt")<CR>
-an 50.50.700 &Syntax.HIJK.Kivy :cal SetSyn("kivy")<CR>
-an 50.50.710 &Syntax.HIJK.KixTart :cal SetSyn("kix")<CR>
+an 50.50.340 &Syntax.HIJK.Icewm\ menu :cal SetSyn("icemenu")<CR>
+an 50.50.350 &Syntax.HIJK.Icon :cal SetSyn("icon")<CR>
+an 50.50.360 &Syntax.HIJK.IDL\Generic\ IDL :cal SetSyn("idl")<CR>
+an 50.50.370 &Syntax.HIJK.IDL\Microsoft\ IDL :cal SetSyn("msidl")<CR>
+an 50.50.380 &Syntax.HIJK.Indent\ profile :cal SetSyn("indent")<CR>
+an 50.50.390 &Syntax.HIJK.Inform :cal SetSyn("inform")<CR>
+an 50.50.400 &Syntax.HIJK.Informix\ 4GL :cal SetSyn("fgl")<CR>
+an 50.50.410 &Syntax.HIJK.Initng :cal SetSyn("initng")<CR>
+an 50.50.420 &Syntax.HIJK.Inittab :cal SetSyn("inittab")<CR>
+an 50.50.430 &Syntax.HIJK.Inno\ setup :cal SetSyn("iss")<CR>
+an 50.50.440 &Syntax.HIJK.Innovation\ Data\ Processing.Upstream\ dat :cal SetSyn("upstreamdat")<CR>
+an 50.50.450 &Syntax.HIJK.Innovation\ Data\ Processing.Upstream\ log :cal SetSyn("upstreamlog")<CR>
+an 50.50.460 &Syntax.HIJK.Innovation\ Data\ Processing.Upstream\ rpt :cal SetSyn("upstreamrpt")<CR>
+an 50.50.470 &Syntax.HIJK.Innovation\ Data\ Processing.Upstream\ Install\ log :cal SetSyn("upstreaminstalllog")<CR>
+an 50.50.480 &Syntax.HIJK.Innovation\ Data\ Processing.Usserver\ log :cal SetSyn("usserverlog")<CR>
+an 50.50.490 &Syntax.HIJK.Innovation\ Data\ Processing.USW2KAgt\ log :cal SetSyn("usw2kagtlog")<CR>
+an 50.50.500 &Syntax.HIJK.InstallShield\ script :cal SetSyn("ishd")<CR>
+an 50.50.510 &Syntax.HIJK.Interactive\ Data\ Lang :cal SetSyn("idlang")<CR>
+an 50.50.520 &Syntax.HIJK.IPfilter :cal SetSyn("ipfilter")<CR>
+an 50.50.540 &Syntax.HIJK.J :cal SetSyn("j")<CR>
+an 50.50.550 &Syntax.HIJK.JAL :cal SetSyn("jal")<CR>
+an 50.50.560 &Syntax.HIJK.JAM :cal SetSyn("jam")<CR>
+an 50.50.570 &Syntax.HIJK.Jargon :cal SetSyn("jargon")<CR>
+an 50.50.580 &Syntax.HIJK.Java.Java :cal SetSyn("java")<CR>
+an 50.50.590 &Syntax.HIJK.Java.JavaCC :cal SetSyn("javacc")<CR>
+an 50.50.600 &Syntax.HIJK.Java.Java\ Server\ Pages :cal SetSyn("jsp")<CR>
+an 50.50.610 &Syntax.HIJK.Java.Java\ Properties :cal SetSyn("jproperties")<CR>
+an 50.50.620 &Syntax.HIJK.JavaScript :cal SetSyn("javascript")<CR>
+an 50.50.630 &Syntax.HIJK.JavaScriptReact :cal SetSyn("javascriptreact")<CR>
+an 50.50.640 &Syntax.HIJK.Jess :cal SetSyn("jess")<CR>
+an 50.50.650 &Syntax.HIJK.Jgraph :cal SetSyn("jgraph")<CR>
+an 50.50.660 &Syntax.HIJK.Jovial :cal SetSyn("jovial")<CR>
+an 50.50.670 &Syntax.HIJK.JSON :cal SetSyn("json")<CR>
+an 50.50.690 &Syntax.HIJK.Kconfig :cal SetSyn("kconfig")<CR>
+an 50.50.700 &Syntax.HIJK.KDE\ script :cal SetSyn("kscript")<CR>
+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.120 &Syntax.L.Latte :cal SetSyn("latte")<CR>
@@ -343,34 +349,35 @@ an 50.70.240 &Syntax.M.Mathematica :cal SetSyn("mma")<CR>
an 50.70.250 &Syntax.M.Matlab :cal SetSyn("matlab")<CR>
an 50.70.260 &Syntax.M.Maxima :cal SetSyn("maxima")<CR>
an 50.70.270 &Syntax.M.MEL\ (for\ Maya) :cal SetSyn("mel")<CR>
-an 50.70.280 &Syntax.M.Messages\ (/var/log) :cal SetSyn("messages")<CR>
-an 50.70.290 &Syntax.M.Metafont :cal SetSyn("mf")<CR>
-an 50.70.300 &Syntax.M.MetaPost :cal SetSyn("mp")<CR>
-an 50.70.310 &Syntax.M.MGL :cal SetSyn("mgl")<CR>
-an 50.70.320 &Syntax.M.MIX :cal SetSyn("mix")<CR>
-an 50.70.330 &Syntax.M.MMIX :cal SetSyn("mmix")<CR>
-an 50.70.340 &Syntax.M.Modconf :cal SetSyn("modconf")<CR>
-an 50.70.350 &Syntax.M.Model :cal SetSyn("model")<CR>
-an 50.70.360 &Syntax.M.Modsim\ III :cal SetSyn("modsim3")<CR>
-an 50.70.370 &Syntax.M.Modula\ 2 :cal SetSyn("modula2")<CR>
-an 50.70.380 &Syntax.M.Modula\ 3 :cal SetSyn("modula3")<CR>
-an 50.70.390 &Syntax.M.Monk :cal SetSyn("monk")<CR>
-an 50.70.400 &Syntax.M.Motorola\ S-Record :cal SetSyn("srec")<CR>
-an 50.70.410 &Syntax.M.Mplayer\ config :cal SetSyn("mplayerconf")<CR>
-an 50.70.420 &Syntax.M.MOO :cal SetSyn("moo")<CR>
-an 50.70.430 &Syntax.M.Mrxvtrc :cal SetSyn("mrxvtrc")<CR>
-an 50.70.440 &Syntax.M.MS-DOS/Windows.4DOS\ \.bat\ file :cal SetSyn("btm")<CR>
-an 50.70.450 &Syntax.M.MS-DOS/Windows.\.bat\/\.cmd\ file :cal SetSyn("dosbatch")<CR>
-an 50.70.460 &Syntax.M.MS-DOS/Windows.\.ini\ file :cal SetSyn("dosini")<CR>
-an 50.70.470 &Syntax.M.MS-DOS/Windows.Message\ text :cal SetSyn("msmessages")<CR>
-an 50.70.480 &Syntax.M.MS-DOS/Windows.Module\ Definition :cal SetSyn("def")<CR>
-an 50.70.490 &Syntax.M.MS-DOS/Windows.Registry :cal SetSyn("registry")<CR>
-an 50.70.500 &Syntax.M.MS-DOS/Windows.Resource\ file :cal SetSyn("rc")<CR>
-an 50.70.510 &Syntax.M.Msql :cal SetSyn("msql")<CR>
-an 50.70.520 &Syntax.M.MuPAD :cal SetSyn("mupad")<CR>
-an 50.70.530 &Syntax.M.Murphi :cal SetSyn("murphi")<CR>
-an 50.70.540 &Syntax.M.MUSHcode :cal SetSyn("mush")<CR>
-an 50.70.550 &Syntax.M.Muttrc :cal SetSyn("muttrc")<CR>
+an 50.70.280 &Syntax.M.Meson :cal SetSyn("meson")<CR>
+an 50.70.290 &Syntax.M.Messages\ (/var/log) :cal SetSyn("messages")<CR>
+an 50.70.300 &Syntax.M.Metafont :cal SetSyn("mf")<CR>
+an 50.70.310 &Syntax.M.MetaPost :cal SetSyn("mp")<CR>
+an 50.70.320 &Syntax.M.MGL :cal SetSyn("mgl")<CR>
+an 50.70.330 &Syntax.M.MIX :cal SetSyn("mix")<CR>
+an 50.70.340 &Syntax.M.MMIX :cal SetSyn("mmix")<CR>
+an 50.70.350 &Syntax.M.Modconf :cal SetSyn("modconf")<CR>
+an 50.70.360 &Syntax.M.Model :cal SetSyn("model")<CR>
+an 50.70.370 &Syntax.M.Modsim\ III :cal SetSyn("modsim3")<CR>
+an 50.70.380 &Syntax.M.Modula\ 2 :cal SetSyn("modula2")<CR>
+an 50.70.390 &Syntax.M.Modula\ 3 :cal SetSyn("modula3")<CR>
+an 50.70.400 &Syntax.M.Monk :cal SetSyn("monk")<CR>
+an 50.70.410 &Syntax.M.Motorola\ S-Record :cal SetSyn("srec")<CR>
+an 50.70.420 &Syntax.M.Mplayer\ config :cal SetSyn("mplayerconf")<CR>
+an 50.70.430 &Syntax.M.MOO :cal SetSyn("moo")<CR>
+an 50.70.440 &Syntax.M.Mrxvtrc :cal SetSyn("mrxvtrc")<CR>
+an 50.70.450 &Syntax.M.MS-DOS/Windows.4DOS\ \.bat\ file :cal SetSyn("btm")<CR>
+an 50.70.460 &Syntax.M.MS-DOS/Windows.\.bat\/\.cmd\ file :cal SetSyn("dosbatch")<CR>
+an 50.70.470 &Syntax.M.MS-DOS/Windows.\.ini\ file :cal SetSyn("dosini")<CR>
+an 50.70.480 &Syntax.M.MS-DOS/Windows.Message\ text :cal SetSyn("msmessages")<CR>
+an 50.70.490 &Syntax.M.MS-DOS/Windows.Module\ Definition :cal SetSyn("def")<CR>
+an 50.70.500 &Syntax.M.MS-DOS/Windows.Registry :cal SetSyn("registry")<CR>
+an 50.70.510 &Syntax.M.MS-DOS/Windows.Resource\ file :cal SetSyn("rc")<CR>
+an 50.70.520 &Syntax.M.Msql :cal SetSyn("msql")<CR>
+an 50.70.530 &Syntax.M.MuPAD :cal SetSyn("mupad")<CR>
+an 50.70.540 &Syntax.M.Murphi :cal SetSyn("murphi")<CR>
+an 50.70.550 &Syntax.M.MUSHcode :cal SetSyn("mush")<CR>
+an 50.70.560 &Syntax.M.Muttrc :cal SetSyn("muttrc")<CR>
an 50.80.100 &Syntax.NO.N1QL :cal SetSyn("n1ql")<CR>
an 50.80.110 &Syntax.NO.Nanorc :cal SetSyn("nanorc")<CR>
an 50.80.120 &Syntax.NO.Nastran\ input/DMAP :cal SetSyn("nastran")<CR>
@@ -442,25 +449,26 @@ an 50.100.110 &Syntax.R.R.R\ help :cal SetSyn("rhelp")<CR>
an 50.100.120 &Syntax.R.R.R\ noweb :cal SetSyn("rnoweb")<CR>
an 50.100.130 &Syntax.R.Racc\ input :cal SetSyn("racc")<CR>
an 50.100.140 &Syntax.R.Radiance :cal SetSyn("radiance")<CR>
-an 50.100.150 &Syntax.R.Ratpoison :cal SetSyn("ratpoison")<CR>
-an 50.100.160 &Syntax.R.RCS.RCS\ log\ output :cal SetSyn("rcslog")<CR>
-an 50.100.170 &Syntax.R.RCS.RCS\ file :cal SetSyn("rcs")<CR>
-an 50.100.180 &Syntax.R.Readline\ config :cal SetSyn("readline")<CR>
-an 50.100.190 &Syntax.R.Rebol :cal SetSyn("rebol")<CR>
-an 50.100.200 &Syntax.R.ReDIF :cal SetSyn("redif")<CR>
-an 50.100.210 &Syntax.R.Relax\ NG :cal SetSyn("rng")<CR>
-an 50.100.220 &Syntax.R.Remind :cal SetSyn("remind")<CR>
-an 50.100.230 &Syntax.R.Relax\ NG\ compact :cal SetSyn("rnc")<CR>
-an 50.100.240 &Syntax.R.Renderman.Renderman\ Shader\ Lang :cal SetSyn("sl")<CR>
-an 50.100.250 &Syntax.R.Renderman.Renderman\ Interface\ Bytestream :cal SetSyn("rib")<CR>
-an 50.100.260 &Syntax.R.Resolv\.conf :cal SetSyn("resolv")<CR>
-an 50.100.270 &Syntax.R.Reva\ Forth :cal SetSyn("reva")<CR>
-an 50.100.280 &Syntax.R.Rexx :cal SetSyn("rexx")<CR>
-an 50.100.290 &Syntax.R.Robots\.txt :cal SetSyn("robots")<CR>
-an 50.100.300 &Syntax.R.RockLinux\ package\ desc\. :cal SetSyn("desc")<CR>
-an 50.100.310 &Syntax.R.Rpcgen :cal SetSyn("rpcgen")<CR>
-an 50.100.320 &Syntax.R.RPL/2 :cal SetSyn("rpl")<CR>
-an 50.100.330 &Syntax.R.ReStructuredText :cal SetSyn("rst")<CR>
+an 50.100.150 &Syntax.R.Raml :cal SetSyn("raml")<CR>
+an 50.100.160 &Syntax.R.Ratpoison :cal SetSyn("ratpoison")<CR>
+an 50.100.170 &Syntax.R.RCS.RCS\ log\ output :cal SetSyn("rcslog")<CR>
+an 50.100.180 &Syntax.R.RCS.RCS\ file :cal SetSyn("rcs")<CR>
+an 50.100.190 &Syntax.R.Readline\ config :cal SetSyn("readline")<CR>
+an 50.100.200 &Syntax.R.Rebol :cal SetSyn("rebol")<CR>
+an 50.100.210 &Syntax.R.ReDIF :cal SetSyn("redif")<CR>
+an 50.100.220 &Syntax.R.Relax\ NG :cal SetSyn("rng")<CR>
+an 50.100.230 &Syntax.R.Remind :cal SetSyn("remind")<CR>
+an 50.100.240 &Syntax.R.Relax\ NG\ compact :cal SetSyn("rnc")<CR>
+an 50.100.250 &Syntax.R.Renderman.Renderman\ Shader\ Lang :cal SetSyn("sl")<CR>
+an 50.100.260 &Syntax.R.Renderman.Renderman\ Interface\ Bytestream :cal SetSyn("rib")<CR>
+an 50.100.270 &Syntax.R.Resolv\.conf :cal SetSyn("resolv")<CR>
+an 50.100.280 &Syntax.R.Reva\ Forth :cal SetSyn("reva")<CR>
+an 50.100.290 &Syntax.R.Rexx :cal SetSyn("rexx")<CR>
+an 50.100.300 &Syntax.R.Robots\.txt :cal SetSyn("robots")<CR>
+an 50.100.310 &Syntax.R.RockLinux\ package\ desc\. :cal SetSyn("desc")<CR>
+an 50.100.320 &Syntax.R.Rpcgen :cal SetSyn("rpcgen")<CR>
+an 50.100.330 &Syntax.R.RPL/2 :cal SetSyn("rpl")<CR>
+an 50.100.340 &Syntax.R.ReStructuredText :cal SetSyn("rst")<CR>
an 50.110.100 &Syntax.M.ReStructuredText\ with\ R\ statements :cal SetSyn("rrst")<CR>
an 50.120.100 &Syntax.R.RTF :cal SetSyn("rtf")<CR>
an 50.120.110 &Syntax.R.Ruby :cal SetSyn("ruby")<CR>
@@ -581,6 +589,8 @@ an 50.150.370 &Syntax.T.Trustees :cal SetSyn("trustees")<CR>
an 50.150.380 &Syntax.T.TSS.Command\ Line :cal SetSyn("tsscl")<CR>
an 50.150.390 &Syntax.T.TSS.Geometry :cal SetSyn("tssgm")<CR>
an 50.150.400 &Syntax.T.TSS.Optics :cal SetSyn("tssop")<CR>
+an 50.150.410 &Syntax.T.Typescript :cal SetSyn("typescript")<CR>
+an 50.150.420 &Syntax.T.TypescriptReact :cal SetSyn("typescriptreact")<CR>
an 50.160.100 &Syntax.UV.Udev\ config :cal SetSyn("udevconf")<CR>
an 50.160.110 &Syntax.UV.Udev\ permissions :cal SetSyn("udevperm")<CR>
an 50.160.120 &Syntax.UV.Udev\ rules :cal SetSyn("udevrules")<CR>
@@ -607,32 +617,33 @@ 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.Webmacro :cal SetSyn("webmacro")<CR>
-an 50.170.140 &Syntax.WXYZ.Website\ MetaLanguage :cal SetSyn("wml")<CR>
-an 50.170.160 &Syntax.WXYZ.wDiff :cal SetSyn("wdiff")<CR>
-an 50.170.180 &Syntax.WXYZ.Wget\ config :cal SetSyn("wget")<CR>
-an 50.170.190 &Syntax.WXYZ.Whitespace\ (add) :cal SetSyn("whitespace")<CR>
-an 50.170.200 &Syntax.WXYZ.WildPackets\ EtherPeek\ Decoder :cal SetSyn("dcd")<CR>
-an 50.170.210 &Syntax.WXYZ.WinBatch/Webbatch :cal SetSyn("winbatch")<CR>
-an 50.170.220 &Syntax.WXYZ.Windows\ Scripting\ Host :cal SetSyn("wsh")<CR>
-an 50.170.230 &Syntax.WXYZ.WSML :cal SetSyn("wsml")<CR>
-an 50.170.240 &Syntax.WXYZ.WvDial :cal SetSyn("wvdial")<CR>
-an 50.170.260 &Syntax.WXYZ.X\ Keyboard\ Extension :cal SetSyn("xkb")<CR>
-an 50.170.270 &Syntax.WXYZ.X\ Pixmap :cal SetSyn("xpm")<CR>
-an 50.170.280 &Syntax.WXYZ.X\ Pixmap\ (2) :cal SetSyn("xpm2")<CR>
-an 50.170.290 &Syntax.WXYZ.X\ resources :cal SetSyn("xdefaults")<CR>
-an 50.170.300 &Syntax.WXYZ.XBL :cal SetSyn("xbl")<CR>
-an 50.170.310 &Syntax.WXYZ.Xinetd\.conf :cal SetSyn("xinetd")<CR>
-an 50.170.320 &Syntax.WXYZ.Xmodmap :cal SetSyn("xmodmap")<CR>
-an 50.170.330 &Syntax.WXYZ.Xmath :cal SetSyn("xmath")<CR>
-an 50.170.340 &Syntax.WXYZ.XML :cal SetSyn("xml")<CR>
-an 50.170.350 &Syntax.WXYZ.XML\ Schema\ (XSD) :cal SetSyn("xsd")<CR>
-an 50.170.360 &Syntax.WXYZ.XQuery :cal SetSyn("xquery")<CR>
-an 50.170.370 &Syntax.WXYZ.Xslt :cal SetSyn("xslt")<CR>
-an 50.170.380 &Syntax.WXYZ.XFree86\ Config :cal SetSyn("xf86conf")<CR>
-an 50.170.400 &Syntax.WXYZ.YAML :cal SetSyn("yaml")<CR>
-an 50.170.410 &Syntax.WXYZ.Yacc :cal SetSyn("yacc")<CR>
-an 50.170.430 &Syntax.WXYZ.Zimbu :cal SetSyn("zimbu")<CR>
+an 50.170.130 &Syntax.WXYZ.WebAssembly :cal SetSyn("wast")<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>
+an 50.170.190 &Syntax.WXYZ.Wget\ config :cal SetSyn("wget")<CR>
+an 50.170.200 &Syntax.WXYZ.Whitespace\ (add) :cal SetSyn("whitespace")<CR>
+an 50.170.210 &Syntax.WXYZ.WildPackets\ EtherPeek\ Decoder :cal SetSyn("dcd")<CR>
+an 50.170.220 &Syntax.WXYZ.WinBatch/Webbatch :cal SetSyn("winbatch")<CR>
+an 50.170.230 &Syntax.WXYZ.Windows\ Scripting\ Host :cal SetSyn("wsh")<CR>
+an 50.170.240 &Syntax.WXYZ.WSML :cal SetSyn("wsml")<CR>
+an 50.170.250 &Syntax.WXYZ.WvDial :cal SetSyn("wvdial")<CR>
+an 50.170.270 &Syntax.WXYZ.X\ Keyboard\ Extension :cal SetSyn("xkb")<CR>
+an 50.170.280 &Syntax.WXYZ.X\ Pixmap :cal SetSyn("xpm")<CR>
+an 50.170.290 &Syntax.WXYZ.X\ Pixmap\ (2) :cal SetSyn("xpm2")<CR>
+an 50.170.300 &Syntax.WXYZ.X\ resources :cal SetSyn("xdefaults")<CR>
+an 50.170.310 &Syntax.WXYZ.XBL :cal SetSyn("xbl")<CR>
+an 50.170.320 &Syntax.WXYZ.Xinetd\.conf :cal SetSyn("xinetd")<CR>
+an 50.170.330 &Syntax.WXYZ.Xmodmap :cal SetSyn("xmodmap")<CR>
+an 50.170.340 &Syntax.WXYZ.Xmath :cal SetSyn("xmath")<CR>
+an 50.170.350 &Syntax.WXYZ.XML :cal SetSyn("xml")<CR>
+an 50.170.360 &Syntax.WXYZ.XML\ Schema\ (XSD) :cal SetSyn("xsd")<CR>
+an 50.170.370 &Syntax.WXYZ.XQuery :cal SetSyn("xquery")<CR>
+an 50.170.380 &Syntax.WXYZ.Xslt :cal SetSyn("xslt")<CR>
+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>
" The End Of The Syntax Menu
diff --git a/runtime/syntax/man.vim b/runtime/syntax/man.vim
index 6afe56a6e3..7ac02c3f63 100644
--- a/runtime/syntax/man.vim
+++ b/runtime/syntax/man.vim
@@ -1,4 +1,4 @@
-" Maintainer: Anmol Sethi <anmol@aubble.com>
+" Maintainer: Anmol Sethi <hi@nhooyr.io>
" Previous Maintainer: SungHyun Nam <goweol@gmail.com>
if exists('b:current_syntax')
@@ -30,6 +30,7 @@ endif
if !exists('b:man_sect')
call man#init_pager()
endif
+
if b:man_sect =~# '^[023]'
syntax case match
syntax include @c $VIMRUNTIME/syntax/c.vim
diff --git a/runtime/syntax/tex.vim b/runtime/syntax/tex.vim
index 363c781d0f..b3a8f96b6b 100644
--- a/runtime/syntax/tex.vim
+++ b/runtime/syntax/tex.vim
@@ -1,8 +1,8 @@
" Vim syntax file
" Language: TeX
-" Maintainer: Charles E. Campbell <NdrchipO@ScampbellPfamily.AbizM>
-" Last Change: May 14, 2019
-" Version: 114
+" Maintainer: Charles E. Campbell <NcampObell@SdrPchip.AorgM-NOSPAM>
+" Last Change: Jun 07, 2020
+" Version: 118
" URL: http://www.drchip.org/astronaut/vim/index.html#SYNTAX_TEX
"
" Notes: {{{1
@@ -147,6 +147,11 @@ if exists("g:tex_nospell") && g:tex_nospell
else
let s:tex_nospell = 0
endif
+if exists("g:tex_excludematcher")
+ let s:tex_excludematcher= g:tex_excludematcher
+else
+ let s:tex_excludematcher= 0
+endif
" Clusters: {{{1
" --------
@@ -156,8 +161,12 @@ if !s:tex_no_error
endif
syn cluster texEnvGroup contains=texMatcher,texMathDelim,texSpecialChar,texStatement
syn cluster texFoldGroup contains=texAccent,texBadMath,texComment,texDefCmd,texDelimiter,texDocType,texInput,texInputFile,texLength,texLigature,texMatcher,texMathZoneV,texMathZoneW,texMathZoneX,texMathZoneY,texMathZoneZ,texNewCmd,texNewEnv,texOnlyMath,texOption,texParen,texRefZone,texSection,texBeginEnd,texSectionZone,texSpaceCode,texSpecialChar,texStatement,texString,texTypeSize,texTypeStyle,texZone,@texMathZones,texTitle,texAbstract,texBoldStyle,texItalStyle,texEmphStyle,texNoSpell
-syn cluster texBoldGroup contains=texAccent,texBadMath,texComment,texDefCmd,texDelimiter,texDocType,texInput,texInputFile,texLength,texLigature,texMatcher,texMathZoneV,texMathZoneW,texMathZoneX,texMathZoneY,texMathZoneZ,texNewCmd,texNewEnv,texOnlyMath,texOption,texParen,texRefZone,texSection,texBeginEnd,texSectionZone,texSpaceCode,texSpecialChar,texStatement,texString,texTypeSize,texTypeStyle,texZone,@texMathZones,texTitle,texAbstract,texBoldStyle,texBoldItalStyle,texNoSpell
-syn cluster texItalGroup contains=texAccent,texBadMath,texComment,texDefCmd,texDelimiter,texDocType,texInput,texInputFile,texLength,texLigature,texMatcher,texMathZoneV,texMathZoneW,texMathZoneX,texMathZoneY,texMathZoneZ,texNewCmd,texNewEnv,texOnlyMath,texOption,texParen,texRefZone,texSection,texBeginEnd,texSectionZone,texSpaceCode,texSpecialChar,texStatement,texString,texTypeSize,texTypeStyle,texZone,@texMathZones,texTitle,texAbstract,texItalStyle,texEmphStyle,texItalBoldStyle,texNoSpell
+syn cluster texBoldGroup contains=texAccent,texBadMath,texComment,texDefCmd,texDelimiter,texDocType,texInput,texInputFile,texLength,texLigature,texMathZoneV,texMathZoneW,texMathZoneX,texMathZoneY,texMathZoneZ,texNewCmd,texNewEnv,texOnlyMath,texOption,texParen,texRefZone,texSection,texBeginEnd,texSectionZone,texSpaceCode,texSpecialChar,texStatement,texString,texTypeSize,texTypeStyle,texZone,@texMathZones,texTitle,texAbstract,texBoldStyle,texBoldItalStyle,texNoSpell
+syn cluster texItalGroup contains=texAccent,texBadMath,texComment,texDefCmd,texDelimiter,texDocType,texInput,texInputFile,texLength,texLigature,texMathZoneV,texMathZoneW,texMathZoneX,texMathZoneY,texMathZoneZ,texNewCmd,texNewEnv,texOnlyMath,texOption,texParen,texRefZone,texSection,texBeginEnd,texSectionZone,texSpaceCode,texSpecialChar,texStatement,texString,texTypeSize,texTypeStyle,texZone,@texMathZones,texTitle,texAbstract,texItalStyle,texEmphStyle,texItalBoldStyle,texNoSpell
+if !s:tex_excludematcher
+ syn cluster texBoldGroup add=texMatcher
+ syn cluster texItalGroup add=texMatcher
+endif
if !s:tex_nospell
syn cluster texMatchGroup contains=texAccent,texBadMath,texComment,texDefCmd,texDelimiter,texDocType,texInput,texLength,texLigature,texMatcher,texNewCmd,texNewEnv,texOnlyMath,texParen,texRefZone,texSection,texSpecialChar,texStatement,texString,texTypeSize,texTypeStyle,texBoldStyle,texBoldItalStyle,texItalStyle,texItalBoldStyle,texZone,texInputFile,texOption,@Spell
syn cluster texMatchNMGroup contains=texAccent,texBadMath,texComment,texDefCmd,texDelimiter,texDocType,texInput,texLength,texLigature,texMatcherNM,texNewCmd,texNewEnv,texOnlyMath,texParen,texRefZone,texSection,texSpecialChar,texStatement,texString,texTypeSize,texTypeStyle,texBoldStyle,texBoldItalStyle,texItalStyle,texItalBoldStyle,texZone,texInputFile,texOption,@Spell
@@ -305,11 +314,6 @@ if s:tex_conceal !~# 'b'
endif
syn match texTypeStyle "\\textmd\>"
syn match texTypeStyle "\\textrm\>"
-syn match texTypeStyle "\\textsc\>"
-syn match texTypeStyle "\\textsf\>"
-syn match texTypeStyle "\\textsl\>"
-syn match texTypeStyle "\\texttt\>"
-syn match texTypeStyle "\\textup\>"
syn match texTypeStyle "\\mathbb\>"
syn match texTypeStyle "\\mathbf\>"
@@ -386,12 +390,18 @@ if s:tex_fast =~# 'b'
syn region texItalStyle matchgroup=texTypeStyle start="\\textit\s*{" matchgroup=texTypeStyle end="}" concealends contains=@texItalGroup,@Spell
syn region texItalBoldStyle matchgroup=texTypeStyle start="\\textbf\s*{" matchgroup=texTypeStyle end="}" concealends contains=@texBoldGroup,@Spell
syn region texEmphStyle matchgroup=texTypeStyle start="\\emph\s*{" matchgroup=texTypeStyle end="}" concealends contains=@texItalGroup,@Spell
+ syn region texEmphStyle matchgroup=texTypeStyle start="\\texts[cfl]\s*{" matchgroup=texTypeStyle end="}" concealends contains=@texBoldGroup,@Spell
+ syn region texEmphStyle matchgroup=texTypeStyle start="\\textup\s*{" matchgroup=texTypeStyle end="}" concealends contains=@texBoldGroup,@Spell
+ syn region texEmphStyle matchgroup=texTypeStyle start="\\texttt\s*{" matchgroup=texTypeStyle end="}" concealends contains=@texBoldGroup,@Spell
else
syn region texBoldStyle matchgroup=texTypeStyle start="\\textbf\s*{" matchgroup=texTypeStyle end="}" concealends contains=@texBoldGroup
syn region texBoldItalStyle matchgroup=texTypeStyle start="\\textit\s*{" matchgroup=texTypeStyle end="}" concealends contains=@texItalGroup
syn region texItalStyle matchgroup=texTypeStyle start="\\textit\s*{" matchgroup=texTypeStyle end="}" concealends contains=@texItalGroup
syn region texItalBoldStyle matchgroup=texTypeStyle start="\\textbf\s*{" matchgroup=texTypeStyle end="}" concealends contains=@texBoldGroup
syn region texEmphStyle matchgroup=texTypeStyle start="\\emph\s*{" matchgroup=texTypeStyle end="}" concealends contains=@texItalGroup
+ syn region texEmphStyle matchgroup=texTypeStyle start="\\texts[cfl]\s*{" matchgroup=texTypeStyle end="}" concealends contains=@texEmphGroup
+ syn region texEmphStyle matchgroup=texTypeStyle start="\\textup\s*{" matchgroup=texTypeStyle end="}" concealends contains=@texEmphGroup
+ syn region texEmphStyle matchgroup=texTypeStyle start="\\texttt\s*{" matchgroup=texTypeStyle end="}" concealends contains=@texEmphGroup
endif
endif
endif
@@ -624,7 +634,7 @@ if s:tex_fast =~# 'r'
syn region texRefOption contained matchgroup=Delimiter start='\[' end=']' contains=@texRefGroup,texRefZone nextgroup=texRefOption,texCite
syn region texCite contained matchgroup=Delimiter start='{' end='}' contains=@texRefGroup,texRefZone,texCite
endif
-syn match texRefZone '\\cite\%([tp]\*\=\)\=' nextgroup=texRefOption,texCite
+syn match texRefZone '\\cite\%([tp]\*\=\)\=\>' nextgroup=texRefOption,texCite
" Handle newcommand, newenvironment : {{{1
syn match texNewCmd "\\newcommand\>" nextgroup=texCmdName skipwhite skipnl
@@ -745,6 +755,8 @@ if has("conceal") && &enc == 'utf-8'
\ ['lceil' , '⌈'],
\ ['ldots' , '…'],
\ ['le' , '≤'],
+ \ ['left|' , '|'],
+ \ ['left\|' , '‖'],
\ ['left(' , '('],
\ ['left\[' , '['],
\ ['left\\{' , '{'],
@@ -795,6 +807,8 @@ if has("conceal") && &enc == 'utf-8'
\ ['quad' , ' '],
\ ['qquad' , 'â€'],
\ ['rfloor' , '⌋'],
+ \ ['right|' , '|'],
+ \ ['right\\|' , '‖'],
\ ['right)' , ')'],
\ ['right]' , ']'],
\ ['right\\}' , '}'],
@@ -1047,6 +1061,7 @@ if has("conceal") && &enc == 'utf-8'
call s:SuperSub('texSuperscript','\^','R','á´¿')
call s:SuperSub('texSuperscript','\^','T','áµ€')
call s:SuperSub('texSuperscript','\^','U','áµ')
+ call s:SuperSub('texSuperscript','\^','V','â±½')
call s:SuperSub('texSuperscript','\^','W','ᵂ')
call s:SuperSub('texSuperscript','\^',',','ï¸')
call s:SuperSub('texSuperscript','\^',':','︓')
diff --git a/runtime/syntax/tutor.vim b/runtime/syntax/tutor.vim
index 6305eef734..83ca547fdd 100644
--- a/runtime/syntax/tutor.vim
+++ b/runtime/syntax/tutor.vim
@@ -33,16 +33,16 @@ syn keyword tutorMarks Todo Note Tip Excersise
syn region tutorCodeblock matchgroup=Delimiter start=/^\~\{3}.*$/ end=/^\~\{3}/
-syn region tutorShell matchgroup=Delimiter start=/^\~\{3} sh\s*$/ end=/^\~\{3}/ keepend contains=@TUTORSHELL
+syn region tutorShell matchgroup=Delimiter start=/^\~\{3} sh\s*$/ end=/^\~\{3}/ keepend contains=@TUTORSHELL concealends
syn match tutorShellPrompt /\(^\s*\)\@<=[$#]/ contained containedin=tutorShell
-syn region tutorInlineCode matchgroup=Delimiter start=/\\\@<!`/ end=/\\\@<!\(`{\@!\|`\s\)/
+syn region tutorInlineCode matchgroup=Delimiter start=/\\\@<!`/ end=/\\\@<!\(`{\@!\|`\s\)/ concealends
-syn region tutorCommand matchgroup=Delimiter start=/^\~\{3} cmd\( :\)\?\s*$/ end=/^\~\{3}/ keepend contains=@VIM
-syn region tutorInlineCommand matchgroup=Delimiter start=/\\\@<!`\(.*{vim}\)\@=/ end=/\\\@<!`\({vim}\)\@=/ nextgroup=tutorInlineType contains=@VIM
+syn region tutorCommand matchgroup=Delimiter start=/^\~\{3} cmd\( :\)\?\s*$/ end=/^\~\{3}/ keepend contains=@VIM concealends
+syn region tutorInlineCommand matchgroup=Delimiter start=/\\\@<!`\(.*`{vim}\)\@=/ end=/\\\@<!`\({vim}\)\@=/ nextgroup=tutorInlineType contains=@VIM concealends keepend
-syn region tutorNormal matchgroup=Delimiter start=/^\~\{3} norm\(al\?\)\?\s*$/ end=/^\~\{3}/ contains=@VIMNORMAL
-syn region tutorInlineNormal matchgroup=Delimiter start=/\\\@<!`\(\S*{normal}\)\@=/ end=/\\\@<!`\({normal}\)\@=/ nextgroup=tutorInlineType contains=@VIMNORMAL
+syn region tutorNormal matchgroup=Delimiter start=/^\~\{3} norm\(al\?\)\?\s*$/ end=/^\~\{3}/ contains=@VIMNORMAL concealends
+syn region tutorInlineNormal matchgroup=Delimiter start=/\\\@<!`\(\S*`{normal}\)\@=/ end=/\\\@<!`\({normal}\)\@=/ nextgroup=tutorInlineType contains=@VIMNORMAL concealends keepend
syn match tutorInlineType /{\(normal\|vim\)}/ contained conceal
diff --git a/runtime/syntax/vim.vim b/runtime/syntax/vim.vim
index b177fe641d..6f0818c845 100644
--- a/runtime/syntax/vim.vim
+++ b/runtime/syntax/vim.vim
@@ -558,7 +558,7 @@ syn match vimHiGuiFontname contained "'[a-zA-Z\-* ]\+'"
syn match vimHiGuiRgb contained "#\x\{6}"
" Highlighting: hi group key=arg ... {{{2
-syn cluster vimHiCluster contains=vimGroup,vimHiGroup,vimHiTerm,vimHiCTerm,vimHiStartStop,vimHiCtermFgBg,vimHiGui,vimHiGuiFont,vimHiGuiFgBg,vimHiKeyError,vimNotation
+syn cluster vimHiCluster contains=vimGroup,vimHiBlend,vimHiGroup,vimHiTerm,vimHiCTerm,vimHiStartStop,vimHiCtermFgBg,vimHiGui,vimHiGuiFont,vimHiGuiFgBg,vimHiKeyError,vimNotation
syn region vimHiKeyList contained oneline start="\i\+" skip="\\\\\|\\|" end="$\||" contains=@vimHiCluster
if !exists("g:vimsyn_noerror") && !exists("g:vimsyn_vimhikeyerror")
syn match vimHiKeyError contained "\i\+="he=e-1
@@ -571,6 +571,7 @@ syn match vimHiGui contained "\cgui="he=e-1 nextgroup=vimHiAttribList
syn match vimHiGuiFont contained "\cfont="he=e-1 nextgroup=vimHiFontname
syn match vimHiGuiFgBg contained "\cgui\%([fb]g\|sp\)="he=e-1 nextgroup=vimHiGroup,vimHiGuiFontname,vimHiGuiRgb,vimFgBgAttrib
syn match vimHiTermcap contained "\S\+" contains=vimNotation
+syn match vimHiBlend contained "\cblend="he=e-1 nextgroup=vimHiNmbr
syn match vimHiNmbr contained '\d\+'
" Highlight: clear {{{2
@@ -850,6 +851,7 @@ if !exists("skip_vim_syntax_inits")
hi def link vimGroupSpecial Special
hi def link vimGroup Type
hi def link vimHiAttrib PreProc
+ hi def link vimHiBlend vimHiTerm
hi def link vimHiClear vimHighlight
hi def link vimHiCtermFgBg vimHiTerm
hi def link vimHiCTerm vimHiTerm
diff --git a/runtime/tutor/en/vim-01-beginner.tutor b/runtime/tutor/en/vim-01-beginner.tutor
index 4e6154b24a..5ae0fde0da 100644
--- a/runtime/tutor/en/vim-01-beginner.tutor
+++ b/runtime/tutor/en/vim-01-beginner.tutor
@@ -91,7 +91,7 @@ NOTE: [:q!](:q) <Enter> discards any changes you made. In a few lessons you
** Press `x`{normal} to delete the character under the cursor. **
- 1. Move the cursor to the line below marked --->.
+ 1. Move the cursor to the line below marked ✗.
2. To fix the errors, move the cursor until it is on top of the
character to be deleted.
@@ -111,7 +111,7 @@ NOTE: As you go through this tutor, do not try to memorize, learn by
** Press `i`{normal} to insert text. **
- 1. Move the cursor to the first line below marked --->.
+ 1. Move the cursor to the first line below marked ✗.
2. To make the first line the same as the second, move the cursor on top
of the first character AFTER where the text is to be inserted.
@@ -130,7 +130,7 @@ There is some text missing from this line.
** Press `A`{normal} to append text. **
- 1. Move the cursor to the first line below marked --->.
+ 1. Move the cursor to the first line below marked ✗.
It does not matter on what character the cursor is in that line.
2. Press [A](A) and type in the necessary additions.
@@ -138,7 +138,7 @@ There is some text missing from this line.
3. As the text has been appended press `<Esc>`{normal} to return to Normal
mode.
- 4. Move the cursor to the second line marked ---> and repeat
+ 4. Move the cursor to the second line marked ✗ and repeat
steps 2 and 3 to correct this sentence.
There is some text missing from th
@@ -211,7 +211,7 @@ Now continue with Lesson 2.
1. Press `<Esc>`{normal} to make sure you are in Normal mode.
- 2. Move the cursor to the line below marked --->.
+ 2. Move the cursor to the line below marked ✗.
3. Move the cursor to the beginning of a word that needs to be deleted.
@@ -227,7 +227,7 @@ There are a some words fun that don't belong paper in this sentence.
1. Press `<Esc>`{normal} to make sure you are in Normal mode.
- 2. Move the cursor to the line below marked --->.
+ 2. Move the cursor to the line below marked ✗.
3. Move the cursor to the end of the correct line (AFTER the first . ).
@@ -263,7 +263,7 @@ NOTE: Pressing just the motion while in Normal mode without an operator
** Typing a number before a motion repeats it that many times. **
- 1. Move the cursor to the start of the line marked ---> below.
+ 1. Move the cursor to the start of the line marked ✓ below.
2. Type `2w`{normal} to move the cursor two words forward.
@@ -285,7 +285,7 @@ In the combination of the delete operator and a motion mentioned above you
insert a count before the motion to delete more:
d number motion
- 1. Move the cursor to the first UPPER CASE word in the line marked --->.
+ 1. Move the cursor to the first UPPER CASE word in the line marked ✗.
2. Type `d2w`{normal} to delete the two UPPER CASE words
@@ -318,7 +318,7 @@ it would be easier to simply type two d's to delete a line.
** Press `u`{normal} to undo the last commands, `U`{normal} to fix a whole line. **
- 1. Move the cursor to the line below marked ---> and place it on the
+ 1. Move the cursor to the line below marked ✗ and place it on the
first error.
2. Type `x`{normal} to delete the first unwanted character.
3. Now type `u`{normal} to undo the last command executed.
@@ -359,7 +359,7 @@ Fiix the errors oon thhis line and reeplace them witth undo.
** Type `p`{normal} to put previously deleted text after the cursor. **
- 1. Move the cursor to the first ---> line below.
+ 1. Move the cursor to the first ✓ line below.
2. Type `dd`{normal} to delete the line and store it in a Vim register.
@@ -378,7 +378,7 @@ a) Roses are red,
** Type `rx`{normal} to replace the character at the cursor with x. **
- 1. Move the cursor to the first line below marked --->.
+ 1. Move the cursor to the first line below marked ✗.
2. Move the cursor so that it is on top of the first error.
@@ -397,7 +397,7 @@ NOTE: Remember that you should be learning by doing, not memorization.
** To change until the end of a word, type `ce`{normal}. **
- 1. Move the cursor to the first line below marked --->.
+ 1. Move the cursor to the first line below marked ✗.
2. Place the cursor on the "u" in "lubw".
@@ -423,7 +423,7 @@ Notice that [c](c)e deletes the word and places you in Insert mode.
2. The motions are the same, such as `w`{normal} (word) and `$`{normal} (end of line).
- 3. Move to the first line below marked --->.
+ 3. Move to the first line below marked ✗.
4. Move the cursor to the first error.
@@ -503,7 +503,7 @@ NOTE: When the search reaches the end of the file it will continue at the
** Type `%`{normal} to find a matching ),], or }. **
- 1. Place the cursor on any (, [, or { in the line below marked --->.
+ 1. Place the cursor on any (, [, or { in the line below marked ✓.
2. Now type the [%](%) character.
@@ -521,7 +521,7 @@ NOTE: This is very useful in debugging a program with unmatched parentheses!
** Type `:s/old/new/g` to substitute "new" for "old". **
- 1. Move the cursor to the line below marked --->.
+ 1. Move the cursor to the line below marked ✗.
2. Type
~~~ cmd
@@ -725,7 +725,7 @@ NOTE: You can also read the output of an external command. For example,
** Type `o`{normal} to open a line below the cursor and place you in Insert mode. **
- 1. Move the cursor to the line below marked --->.
+ 1. Move the cursor to the line below marked ✓.
2. Type the lowercase letter `o`{normal} to [open](o) up a line BELOW the
cursor and place you in Insert mode.
@@ -743,7 +743,7 @@ Open up a line above this by typing O while the cursor is on this line.
** Type `a`{normal} to insert text AFTER the cursor. **
- 1. Move the cursor to the start of the line below marked --->.
+ 1. Move the cursor to the start of the line below marked ✗.
2. Press `e`{normal} until the cursor is on the end of "li".
@@ -766,7 +766,7 @@ NOTE: [a](a), [i](i) and [A](A) all go to the same Insert mode, the only
** Type a capital `R`{normal} to replace more than one character. **
- 1. Move the cursor to the first line below marked --->. Move the cursor to
+ 1. Move the cursor to the first line below marked ✗. Move the cursor to
the beginning of the first "xxx".
2. Now press `R`{normal} ([capital R](R)) and type the number below it in the
@@ -787,7 +787,7 @@ NOTE: Replace mode is like Insert mode, but every typed character deletes an
** Use the `y`{normal} operator to copy text and `p`{normal} to paste it. **
- 1. Go to the line marked with ---> below and place the cursor after "a)".
+ 1. Go to the line marked with ✓ below and place the cursor after "a)".
2. Start Visual mode with `v`{normal} and move the cursor to just before
"first".
@@ -805,7 +805,7 @@ NOTE: Replace mode is like Insert mode, but every typed character deletes an
end of the next line with `j$`{normal} and put the text there with `p`{normal}
a) This is the first item.
- b)
+b)
NOTE: you can use `y`{normal} as an operator: `yw`{normal} yanks one word.
diff --git a/runtime/tutor/en/vim-01-beginner.tutor.json b/runtime/tutor/en/vim-01-beginner.tutor.json
index 2f87d7543f..af22cf2aca 100644
--- a/runtime/tutor/en/vim-01-beginner.tutor.json
+++ b/runtime/tutor/en/vim-01-beginner.tutor.json
@@ -1,43 +1,45 @@
{
- "expect": {
- "24": -1,
- "103": "The cow jumped over the moon.",
- "124": "There is some text missing from this line.",
- "125": "There is some text missing from this line.",
- "144": "There is some text missing from this line.",
- "145": "There is some text missing from this line.",
- "146": "There is also some text missing here.",
- "147": "There is also some text missing here.",
- "220": "There are some words that don't belong in this sentence.",
- "236": "Somebody typed the end of this line twice.",
- "276": -1,
- "295": "This line of words is cleaned up.",
- "309": -1,
- "310": -1,
- "311": -1,
- "312": -1,
- "313": -1,
- "314": -1,
- "315": -1,
- "332": "Fix the errors on this line and replace them with undo.",
- "372": -1,
- "373": -1,
- "374": -1,
- "375": -1,
- "389": "When this line was typed in, someone pressed some wrong keys!",
- "390": "When this line was typed in, someone pressed some wrong keys!",
- "411": "This line has a few words that need changing using the change operator.",
- "412": "This line has a few words that need changing using the change operator.",
- "432": "The end of this line needs to be corrected using the c$ command.",
- "433": "The end of this line needs to be corrected using the c$ command.",
- "497": -1,
- "516": -1,
- "541": "Usually the best time to see the flowers is in the spring.",
- "759": "This line will allow you to practice appending text to a line.",
- "760": "This line will allow you to practice appending text to a line.",
- "780": "Adding 123 to 456 gives you 579.",
- "781": "Adding 123 to 456 gives you 579.",
- "807": "a) This is the first item.",
- "808": " b) This is the second item."
- }
+ "expect": {
+ "24": -1,
+ "103": "The cow jumped over the moon.",
+ "124": "There is some text missing from this line.",
+ "125": "There is some text missing from this line.",
+ "144": "There is some text missing from this line.",
+ "145": "There is some text missing from this line.",
+ "146": "There is also some text missing here.",
+ "147": "There is also some text missing here.",
+ "220": "There are some words that don't belong in this sentence.",
+ "236": "Somebody typed the end of this line twice.",
+ "276": -1,
+ "295": "This line of words is cleaned up.",
+ "309": -1,
+ "310": -1,
+ "311": -1,
+ "312": -1,
+ "313": -1,
+ "314": -1,
+ "315": -1,
+ "332": "Fix the errors on this line and replace them with undo.",
+ "372": -1,
+ "373": -1,
+ "374": -1,
+ "375": -1,
+ "389": "When this line was typed in, someone pressed some wrong keys!",
+ "390": "When this line was typed in, someone pressed some wrong keys!",
+ "411": "This line has a few words that need changing using the change operator.",
+ "412": "This line has a few words that need changing using the change operator.",
+ "432": "The end of this line needs to be corrected using the `c$` command.",
+ "433": "The end of this line needs to be corrected using the `c$` command.",
+ "497": -1,
+ "516": -1,
+ "541": "Usually the best time to see the flowers is in the spring.",
+ "735": -1,
+ "740": -1,
+ "759": "This line will allow you to practice appending text to a line.",
+ "760": "This line will allow you to practice appending text to a line.",
+ "780": "Adding 123 to 456 gives you 579.",
+ "781": "Adding 123 to 456 gives you 579.",
+ "807": "a) This is the first item.",
+ "808": "b) This is the second item."
+ }
}
diff --git a/runtime/tutor/tutor.tutor b/runtime/tutor/tutor.tutor
index c937bd686a..b46fcc4836 100644
--- a/runtime/tutor/tutor.tutor
+++ b/runtime/tutor/tutor.tutor
@@ -118,7 +118,7 @@ and are hidden by default. Links to them look like
\[label\]\(\*anchor\*\)
-6. Add the appropiate link:
+6. Add the appropriate link:
A link to the Links section
A link to the [Links](*links*) section
diff --git a/scripts/gen_vimdoc.py b/scripts/gen_vimdoc.py
index 373a58d11e..a61690e99f 100755
--- a/scripts/gen_vimdoc.py
+++ b/scripts/gen_vimdoc.py
@@ -1,5 +1,18 @@
#!/usr/bin/env python3
-"""Generates Nvim help docs from C docstrings, by parsing Doxygen XML.
+"""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(msgpackparse(readfile('runtime/doc/api.mpack')))
+
+
+Flow:
+ main
+ extract_from_xml
+ fmt_node_as_vimhelp \
+ para_as_map } recursive
+ update_params_map /
+ render_node
This would be easier using lxml and XSLT, but:
@@ -9,25 +22,22 @@ This would be easier using lxml and XSLT, but:
2. I wouldn't know how to deal with nested indentation in <para> tags using
XSLT.
-Each function documentation is formatted with the following rules:
+Each function :help block is formatted as follows:
- - Maximum width of 78 characters (`text_width`).
- - Spaces for indentation.
- - Function signature and helptag are on the same line.
- - Helptag is right aligned.
+ - Max width of 78 columns (`text_width`).
+ - Indent with spaces (not tabs).
+ - Indent of 16 columns for body text.
+ - Function signature and helptag (right-aligned) on the same line.
- Signature and helptag must have a minimum of 8 spaces between them.
- - If the signature is too long, it is placed on the line after the
- helptag. The signature wraps at `text_width - 8` characters with
- subsequent lines indented to the open parenthesis.
- - Documentation body will be indented by 16 spaces.
+ - If the signature is too long, it is placed on the line after the helptag.
+ Signature wraps at `text_width - 8` characters with subsequent
+ lines indented to the open parenthesis.
- Subsection bodies are indented an additional 4 spaces.
- - Documentation body consists of the function description, parameter details,
- return description, and C declaration.
+ - Body consists of function description, parameters, return description, and
+ C declaration (`INCLUDE_C_DECL`).
- Parameters are omitted for the `void` and `Error *` types, or if the
parameter is marked as [out].
- Each function documentation is separated by a single line.
-
-The C declaration is added to the end to show actual argument types.
"""
import os
import re
@@ -36,27 +46,33 @@ import shutil
import textwrap
import subprocess
import collections
+import msgpack
from xml.dom import minidom
-if sys.version_info[0] < 3:
- print("use Python 3")
+MIN_PYTHON_VERSION = (3, 5)
+
+if sys.version_info < MIN_PYTHON_VERSION:
+ print("requires Python {}.{}+".format(*MIN_PYTHON_VERSION))
sys.exit(1)
DEBUG = ('DEBUG' in os.environ)
+TARGET = os.environ.get('TARGET', None)
INCLUDE_C_DECL = ('INCLUDE_C_DECL' in os.environ)
INCLUDE_DEPRECATED = ('INCLUDE_DEPRECATED' in os.environ)
+fmt_vimhelp = False # HACK
text_width = 78
script_path = os.path.abspath(__file__)
base_dir = os.path.dirname(os.path.dirname(script_path))
-out_dir = os.path.join(base_dir, 'tmp-{mode}-doc')
+out_dir = os.path.join(base_dir, 'tmp-{target}-doc')
filter_cmd = '%s %s' % (sys.executable, script_path)
seen_funcs = set()
lua2dox_filter = os.path.join(base_dir, 'scripts', 'lua2dox_filter')
CONFIG = {
'api': {
+ 'mode': 'c',
'filename': 'api.txt',
# String used to find the start of the generated part of the doc.
'section_start_token': '*api-global*',
@@ -73,18 +89,25 @@ CONFIG = {
# file patterns used by doxygen
'file_patterns': '*.h *.c',
# Only function with this prefix are considered
- 'func_name_prefix': 'nvim_',
+ 'fn_name_prefix': 'nvim_',
# Section name overrides.
'section_name': {
'vim.c': 'Global',
},
+ # For generated section names.
+ 'section_fmt': lambda name: f'{name} Functions',
+ # Section helptag.
+ 'helptag_fmt': lambda name: f'*api-{name.lower()}*',
+ # Per-function helptag.
+ 'fn_helptag_fmt': lambda fstem, name: f'*{name}()*',
# Module name overrides (for Lua).
'module_override': {},
# Append the docs for these modules, do not start a new section.
'append_only': [],
},
'lua': {
- 'filename': 'if_lua.txt',
+ 'mode': 'lua',
+ 'filename': 'lua.txt',
'section_start_token': '*lua-vim*',
'section_order': [
'vim.lua',
@@ -95,8 +118,13 @@ CONFIG = {
os.path.join(base_dir, 'runtime/lua/vim/shared.lua'),
]),
'file_patterns': '*.lua',
- 'func_name_prefix': '',
- 'section_name': {},
+ 'fn_name_prefix': '',
+ 'section_name': {
+ 'lsp.lua': 'core',
+ },
+ 'section_fmt': lambda name: f'Lua module: {name.lower()}',
+ 'helptag_fmt': lambda name: f'*lua-{name.lower()}*',
+ 'fn_helptag_fmt': lambda fstem, name: f'*{fstem}.{name}()*',
'module_override': {
# `shared` functions are exposed on the `vim` module.
'shared': 'vim',
@@ -105,6 +133,45 @@ CONFIG = {
'shared.lua',
],
},
+ 'lsp': {
+ 'mode': 'lua',
+ 'filename': 'lsp.txt',
+ 'section_start_token': '*lsp-core*',
+ 'section_order': [
+ 'lsp.lua',
+ 'protocol.lua',
+ 'buf.lua',
+ 'callbacks.lua',
+ 'log.lua',
+ 'rpc.lua',
+ 'util.lua'
+ ],
+ 'files': ' '.join([
+ os.path.join(base_dir, 'runtime/lua/vim/lsp'),
+ os.path.join(base_dir, 'runtime/lua/vim/lsp.lua'),
+ ]),
+ 'file_patterns': '*.lua',
+ 'fn_name_prefix': '',
+ 'section_name': {},
+ 'section_fmt': lambda name: (
+ 'Lua module: vim.lsp'
+ if name.lower() == 'lsp'
+ else f'Lua module: vim.lsp.{name.lower()}'),
+ 'helptag_fmt': lambda name: (
+ '*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}()*')),
+ 'module_override': {},
+ 'append_only': [],
+ },
}
param_exclude = (
@@ -122,14 +189,22 @@ annotation_map = {
xrefs = set()
-def debug_this(s, n):
- o = n if isinstance(n, str) else n.toprettyxml(indent=' ', newl='\n')
- name = '' if isinstance(n, str) else n.nodeName
- if s in o:
+# Raises an error with details about `o`, if `cond` is in object `o`,
+# or if `cond()` is callable and returns True.
+def debug_this(cond, o):
+ name = ''
+ if not isinstance(o, str):
+ try:
+ name = o.nodeName
+ o = o.toprettyxml(indent=' ', newl='\n')
+ except Exception:
+ pass
+ if ((callable(cond) and cond())
+ or (not callable(cond) and cond)
+ or (not callable(cond) and cond in o)):
raise RuntimeError('xxx: {}\n{}'.format(name, o))
-# XML Parsing Utilities {{{
def find_first(parent, name):
"""Finds the first matching node within parent."""
sub = parent.getElementsByTagName(name)
@@ -138,20 +213,27 @@ def find_first(parent, name):
return sub[0]
-def get_children(parent, name):
- """Yield matching child nodes within parent."""
+def iter_children(parent, name):
+ """Yields matching child nodes within parent."""
for child in parent.childNodes:
if child.nodeType == child.ELEMENT_NODE and child.nodeName == name:
yield child
def get_child(parent, name):
- """Get the first matching child node."""
- for child in get_children(parent, name):
+ """Gets the first matching child node."""
+ for child in iter_children(parent, name):
return child
return None
+def self_or_child(n):
+ """Gets the first child node, or self."""
+ if len(n.childNodes) == 0:
+ return n
+ return n.childNodes[0]
+
+
def clean_text(text):
"""Cleans text.
@@ -172,18 +254,21 @@ def is_blank(text):
return '' == clean_lines(text)
-def get_text(parent, preformatted=False):
- """Combine all text in a node."""
- if parent.nodeType == parent.TEXT_NODE:
- return parent.data
-
- out = ''
- for node in parent.childNodes:
+def get_text(n, preformatted=False):
+ """Recursively concatenates all text in a node tree."""
+ text = ''
+ if n.nodeType == n.TEXT_NODE:
+ return n.data
+ if n.nodeName == 'computeroutput':
+ for node in n.childNodes:
+ text += get_text(node)
+ return '`{}` '.format(text)
+ for node in n.childNodes:
if node.nodeType == node.TEXT_NODE:
- out += node.data if preformatted else clean_text(node.data)
+ text += node.data if preformatted else clean_text(node.data)
elif node.nodeType == node.ELEMENT_NODE:
- out += ' ' + get_text(node, preformatted)
- return out
+ text += ' ' + get_text(node, preformatted)
+ return text
# Gets the length of the last line in `text`, excluding newline ("\n") char.
@@ -203,6 +288,8 @@ def len_lastline_withoutindent(text, indent):
# Returns True if node `n` contains only inline (not block-level) elements.
def is_inline(n):
+ # if len(n.childNodes) == 0:
+ # return n.nodeType == n.TEXT_NODE or n.nodeName == 'computeroutput'
for c in n.childNodes:
if c.nodeType != c.TEXT_NODE and c.nodeName != 'computeroutput':
return False
@@ -253,83 +340,78 @@ def doc_wrap(text, prefix='', width=70, func=False, indent=None):
return result
-def has_nonexcluded_params(nodes):
- """Returns true if any of the given <parameterlist> elements has at least
- one non-excluded item."""
- for n in nodes:
- if render_params(n) != '':
- return True
+def max_name(names):
+ if len(names) == 0:
+ return 0
+ return max(len(name) for name in names)
-def render_params(parent, width=62):
- """Renders Doxygen <parameterlist> tag as Vim help text."""
- name_length = 0
- items = []
+def update_params_map(parent, ret_map, width=62):
+ """Updates `ret_map` with name:desc key-value pairs extracted
+ from Doxygen XML node `parent`.
+ """
+ params = collections.OrderedDict()
for node in parent.childNodes:
if node.nodeType == node.TEXT_NODE:
continue
-
name_node = find_first(node, 'parametername')
if name_node.getAttribute('direction') == 'out':
continue
-
name = get_text(name_node)
if name in param_exclude:
continue
-
- name = '{%s}' % name
- name_length = max(name_length, len(name) + 2)
- items.append((name.strip(), node))
-
- out = ''
- for name, node in items:
- name = ' {}'.format(name.ljust(name_length))
-
+ params[name.strip()] = node
+ max_name_len = max_name(params.keys()) + 8
+ # `ret_map` is a name:desc map.
+ for name, node in params.items():
desc = ''
desc_node = get_child(node, 'parameterdescription')
if desc_node:
- desc = parse_parblock(desc_node, width=width,
- indent=(' ' * len(name)))
-
- out += '{}{}\n'.format(name, desc)
- return out.rstrip()
+ desc = fmt_node_as_vimhelp(
+ desc_node, width=width, indent=(' ' * max_name_len))
+ ret_map[name] = desc
+ return ret_map
def render_node(n, text, prefix='', indent='', width=62):
"""Renders a node as Vim help text, recursively traversing all descendants."""
+ global fmt_vimhelp
+ global has_seen_preformatted
+
+ def ind(s):
+ return s if fmt_vimhelp else ''
+
text = ''
# space_preceding = (len(text) > 0 and ' ' == text[-1][-1])
# text += (int(not space_preceding) * ' ')
- if n.nodeType == n.TEXT_NODE:
- # `prefix` is NOT sent to doc_wrap, it was already handled by now.
- text += doc_wrap(n.data, indent=indent, width=width)
- elif n.nodeName == 'computeroutput':
- text += ' `{}` '.format(get_text(n))
- elif n.nodeName == 'preformatted':
+ if n.nodeName == 'preformatted':
o = get_text(n, preformatted=True)
ensure_nl = '' if o[-1] == '\n' else '\n'
- text += ' >{}{}\n<'.format(ensure_nl, o)
+ text += '>{}{}\n<'.format(ensure_nl, o)
+
elif is_inline(n):
- for c in n.childNodes:
- text += render_node(c, text)
- text = doc_wrap(text, indent=indent, width=width)
+ text = doc_wrap(get_text(n), 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.
text += ' [verbatim] {}'.format(get_text(n))
elif n.nodeName == 'listitem':
for c in n.childNodes:
- text += (
- indent
- + prefix
- + render_node(c, text, indent=indent + (' ' * len(prefix)), width=width)
+ result = render_node(
+ c,
+ text,
+ indent=indent + (' ' * len(prefix)),
+ width=width
)
+
+ if is_blank(result):
+ continue
+
+ text += indent + prefix + result
elif n.nodeName in ('para', 'heading'):
for c in n.childNodes:
text += render_node(c, text, indent=indent, width=width)
- if is_inline(n):
- text = doc_wrap(text, indent=indent, width=width)
elif n.nodeName == 'itemizedlist':
for c in n.childNodes:
text += '{}\n'.format(render_node(c, text, prefix='• ',
@@ -355,23 +437,33 @@ def render_node(n, text, prefix='', indent='', width=62):
text += '\n'
elif (n.nodeName == 'simplesect'
and n.getAttribute('kind') in ('return', 'see')):
- text += ' '
+ text += ind(' ')
for c in n.childNodes:
text += render_node(c, text, indent=' ', width=width)
else:
raise RuntimeError('unhandled node type: {}\n{}'.format(
n.nodeName, n.toprettyxml(indent=' ', newl='\n')))
+
return text
-def render_para(parent, indent='', width=62):
- """Renders Doxygen <para> containing arbitrary nodes.
+def para_as_map(parent, indent='', width=62):
+ """Extracts a Doxygen XML <para> node to a map.
- NB: Blank lines in a docstring manifest as <para> tags.
+ Keys:
+ 'text': Text from this <para> element
+ 'params': <parameterlist> map
+ 'return': List of @return strings
+ 'seealso': List of @see strings
+ 'xrefs': ?
"""
- if is_inline(parent):
- return clean_lines(doc_wrap(render_node(parent, ''),
- indent=indent, width=width).strip())
+ chunks = {
+ 'text': '',
+ 'params': collections.OrderedDict(),
+ 'return': [],
+ 'seealso': [],
+ 'xrefs': []
+ }
# Ordered dict of ordered lists.
groups = collections.OrderedDict([
@@ -386,75 +478,125 @@ def render_para(parent, indent='', width=62):
text = ''
kind = ''
last = ''
- for child in parent.childNodes:
- if child.nodeName == 'parameterlist':
- groups['params'].append(child)
- 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'):
- groups['return'].append(child)
- elif kind == 'see':
- groups['seealso'].append(child)
- elif kind in ('note', 'warning'):
- text += render_node(child, text, indent=indent, width=width)
+ if is_inline(parent):
+ # Flatten inline text from a tree of non-block nodes.
+ text = doc_wrap(render_node(parent, ""), indent=indent, width=width)
+ else:
+ prev = None # Previous node
+ for child in parent.childNodes:
+ if child.nodeName == 'parameterlist':
+ groups['params'].append(child)
+ 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'):
+ groups['return'].append(child)
+ elif kind == 'see':
+ groups['seealso'].append(child)
+ elif kind in ('note', 'warning'):
+ text += render_node(child, text, indent=indent, width=width)
+ else:
+ raise RuntimeError('unhandled simplesect: {}\n{}'.format(
+ child.nodeName, child.toprettyxml(indent=' ', newl='\n')))
else:
- raise RuntimeError('unhandled simplesect: {}\n{}'.format(
- child.nodeName, child.toprettyxml(indent=' ', newl='\n')))
- else:
- text += render_node(child, text, indent=indent, width=width)
+ if (prev is not None
+ and is_inline(self_or_child(prev))
+ and is_inline(self_or_child(child))
+ and '' != get_text(self_or_child(child)).strip()
+ and ' ' != text[-1]):
+ text += ' '
+
+ text += render_node(child, text, indent=indent, width=width)
+ prev = child
+
+ chunks['text'] += text
- chunks = [text]
- # Generate text from the gathered items.
- if len(groups['params']) > 0 and has_nonexcluded_params(groups['params']):
- chunks.append('\nParameters: ~')
+ # Generate map from the gathered items.
+ if len(groups['params']) > 0:
for child in groups['params']:
- chunks.append(render_params(child, width=width))
- if len(groups['return']) > 0:
- chunks.append('\nReturn: ~')
- for child in groups['return']:
- chunks.append(render_node(
- child, chunks[-1][-1], indent=indent, width=width))
- if len(groups['seealso']) > 0:
- chunks.append('\nSee also: ~')
- for child in groups['seealso']:
- chunks.append(render_node(
- child, chunks[-1][-1], indent=indent, width=width))
+ update_params_map(child, ret_map=chunks['params'], width=width)
+ for child in groups['return']:
+ chunks['return'].append(render_node(
+ child, '', indent=indent, width=width))
+ for child in groups['seealso']:
+ chunks['seealso'].append(render_node(
+ child, '', indent=indent, width=width))
for child in groups['xrefs']:
- title = get_text(get_child(child, 'xreftitle'))
+ # XXX: Add a space (or any char) to `title` here, otherwise xrefs
+ # ("Deprecated" section) acts very weird...
+ title = get_text(get_child(child, 'xreftitle')) + ' '
xrefs.add(title)
- xrefdesc = render_para(get_child(child, 'xrefdescription'), width=width)
- chunks.append(doc_wrap(xrefdesc, prefix='{}: '.format(title),
- width=width) + '\n')
+ xrefdesc = get_text(get_child(child, 'xrefdescription'))
+ chunks['xrefs'].append(doc_wrap(xrefdesc, prefix='{}: '.format(title),
+ width=width) + '\n')
- return clean_lines('\n'.join(chunks).strip())
+ return chunks
-def parse_parblock(parent, prefix='', width=62, indent=''):
- """Renders a nested block of <para> tags as Vim help text."""
- paragraphs = []
- for child in parent.childNodes:
- paragraphs.append(render_para(child, width=width, indent=indent))
- paragraphs.append('')
- return clean_lines('\n'.join(paragraphs).strip())
-# }}}
-
-
-def parse_source_xml(filename, mode):
- """Collects API functions.
+def fmt_node_as_vimhelp(parent, width=62, indent=''):
+ """Renders (nested) Doxygen <para> nodes as Vim :help text.
- Returns two strings:
- 1. API functions
- 2. Deprecated API functions
+ NB: Blank lines in a docstring manifest as <para> tags.
+ """
+ rendered_blocks = []
+
+ def fmt_param_doc(m):
+ """Renders a params map as Vim :help text."""
+ max_name_len = max_name(m.keys()) + 4
+ out = ''
+ for name, desc in m.items():
+ name = ' {}'.format('{{{}}}'.format(name).ljust(max_name_len))
+ out += '{}{}\n'.format(name, desc)
+ return out.rstrip()
+
+ def has_nonexcluded_params(m):
+ """Returns true if any of the given params has at least
+ one non-excluded item."""
+ if fmt_param_doc(m) != '':
+ return True
- Caller decides what to do with the deprecated documentation.
+ for child in parent.childNodes:
+ para = para_as_map(child, indent, width)
+
+ # Generate text from the gathered items.
+ chunks = [para['text']]
+ 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: ~')
+ for s in para['return']:
+ chunks.append(s)
+ if len(para['seealso']) > 0:
+ chunks.append('\nSee also: ~')
+ for s in para['seealso']:
+ chunks.append(s)
+ for s in para['xrefs']:
+ chunks.append(s)
+
+ rendered_blocks.append(clean_lines('\n'.join(chunks).strip()))
+ rendered_blocks.append('')
+
+ return clean_lines('\n'.join(rendered_blocks).strip())
+
+
+def extract_from_xml(filename, target, width):
+ """Extracts Doxygen info as maps without formatting the text.
+
+ Returns two maps:
+ 1. Functions
+ 2. Deprecated functions
+
+ The `fmt_vimhelp` global controls some special cases for use by
+ fmt_doxygen_xml_as_vimhelp(). (TODO: ugly :)
"""
global xrefs
- xrefs = set()
- functions = []
- deprecated_functions = []
+ global fmt_vimhelp
+ xrefs.clear()
+ fns = {} # Map of func_name:docstring.
+ deprecated_fns = {} # Map of func_name:docstring.
dom = minidom.parse(filename)
compoundname = get_text(dom.getElementsByTagName('compoundname')[0])
@@ -489,31 +631,33 @@ def parse_source_xml(filename, mode):
annotations = filter(None, map(lambda x: annotation_map.get(x),
annotations.split()))
- if mode == 'lua':
- fstem = compoundname.split('.')[0]
- fstem = CONFIG[mode]['module_override'].get(fstem, fstem)
- vimtag = '*{}.{}()*'.format(fstem, name)
+ if not fmt_vimhelp:
+ pass
else:
- vimtag = '*{}()*'.format(name)
+ fstem = '?'
+ if '.' in compoundname:
+ fstem = compoundname.split('.')[0]
+ fstem = CONFIG[target]['module_override'].get(fstem, fstem)
+ vimtag = CONFIG[target]['fn_helptag_fmt'](fstem, name)
params = []
type_length = 0
- for param in get_children(member, 'param'):
+ for param in iter_children(member, 'param'):
param_type = get_text(get_child(param, 'type')).strip()
param_name = ''
declname = get_child(param, 'declname')
if declname:
param_name = get_text(declname).strip()
- elif mode == 'lua':
- # that's how it comes out of lua2dox
+ elif CONFIG[target]['mode'] == 'lua':
+ # XXX: this is what lua2dox gives us...
param_name = param_type
param_type = ''
if param_name in param_exclude:
continue
- if param_type.endswith('*'):
+ if fmt_vimhelp and param_type.endswith('*'):
param_type = param_type.strip('* ')
param_name = '*' + param_name
type_length = max(type_length, len(param_type))
@@ -521,41 +665,106 @@ def parse_source_xml(filename, mode):
c_args = []
for param_type, param_name in params:
- c_args.append(' ' + (
+ c_args.append((' ' if fmt_vimhelp else '') + (
'%s %s' % (param_type.ljust(type_length), param_name)).strip())
- c_decl = textwrap.indent('%s %s(\n%s\n);' % (return_type, name,
- ',\n'.join(c_args)),
- ' ')
-
prefix = '%s(' % name
suffix = '%s)' % ', '.join('{%s}' % a[1] for a in params
if a[0] not in ('void', 'Error'))
+ if not fmt_vimhelp:
+ c_decl = '%s %s(%s);' % (return_type, name, ', '.join(c_args))
+ signature = prefix + suffix
+ else:
+ c_decl = textwrap.indent('%s %s(\n%s\n);' % (return_type, name,
+ ',\n'.join(c_args)),
+ ' ')
- # Minimum 8 chars between signature and vimtag
- lhs = (text_width - 8) - len(prefix)
+ # Minimum 8 chars between signature and vimtag
+ lhs = (width - 8) - len(vimtag)
- if len(prefix) + len(suffix) > lhs:
- signature = vimtag.rjust(text_width) + '\n'
- signature += doc_wrap(suffix, width=text_width-8, prefix=prefix,
- func=True)
- else:
- signature = prefix + suffix
- signature += vimtag.rjust(text_width - len(signature))
+ if len(prefix) + len(suffix) > lhs:
+ signature = vimtag.rjust(width) + '\n'
+ signature += doc_wrap(suffix, width=width-8, prefix=prefix,
+ func=True)
+ else:
+ signature = prefix + suffix
+ signature += vimtag.rjust(width - len(signature))
+
+ paras = []
+ brief_desc = find_first(member, 'briefdescription')
+ if brief_desc:
+ for child in brief_desc.childNodes:
+ paras.append(para_as_map(child))
- doc = ''
desc = find_first(member, 'detaileddescription')
if desc:
- doc = parse_parblock(desc)
+ for child in desc.childNodes:
+ paras.append(para_as_map(child))
if DEBUG:
print(textwrap.indent(
re.sub(r'\n\s*\n+', '\n',
desc.toprettyxml(indent=' ', newl='\n')), ' ' * 16))
+ fn = {
+ 'annotations': list(annotations),
+ 'signature': signature,
+ 'parameters': params,
+ 'parameters_doc': collections.OrderedDict(),
+ 'doc': [],
+ 'return': [],
+ 'seealso': [],
+ }
+ if fmt_vimhelp:
+ fn['desc_node'] = desc # HACK :(
+
+ for m in paras:
+ if 'text' in m:
+ if not m['text'] == '':
+ fn['doc'].append(m['text'])
+ if 'params' in m:
+ # Merge OrderedDicts.
+ fn['parameters_doc'].update(m['params'])
+ if 'return' in m and len(m['return']) > 0:
+ fn['return'] += m['return']
+ if 'seealso' in m and len(m['seealso']) > 0:
+ fn['seealso'] += m['seealso']
+
+ if INCLUDE_C_DECL:
+ fn['c_decl'] = c_decl
+
+ if 'Deprecated' in str(xrefs):
+ deprecated_fns[name] = fn
+ elif name.startswith(CONFIG[target]['fn_name_prefix']):
+ fns[name] = fn
+
+ xrefs.clear()
+
+ fns = collections.OrderedDict(sorted(fns.items()))
+ deprecated_fns = collections.OrderedDict(sorted(deprecated_fns.items()))
+ return (fns, deprecated_fns)
+
+
+def fmt_doxygen_xml_as_vimhelp(filename, target):
+ """Entrypoint for generating Vim :help from from Doxygen XML.
+
+ Returns 3 items:
+ 1. Vim help text for functions found in `filename`.
+ 2. Vim help text for deprecated functions.
+ """
+ global fmt_vimhelp
+ fmt_vimhelp = True
+ fns_txt = {} # Map of func_name:vim-help-text.
+ deprecated_fns_txt = {} # Map of func_name:vim-help-text.
+ fns, _ = extract_from_xml(filename, target, width=text_width)
+
+ for name, fn in fns.items():
+ # Generate Vim :help for parameters.
+ if fn['desc_node']:
+ doc = fmt_node_as_vimhelp(fn['desc_node'])
if not doc:
doc = 'TODO: Documentation'
- annotations = '\n'.join(annotations)
+ annotations = '\n'.join(fn['annotations'])
if annotations:
annotations = ('\n\nAttributes: ~\n' +
textwrap.indent(annotations, ' '))
@@ -567,21 +776,51 @@ def parse_source_xml(filename, mode):
if INCLUDE_C_DECL:
doc += '\n\nC Declaration: ~\n>\n'
- doc += c_decl
+ doc += fn['c_decl']
doc += '\n<'
- func_doc = signature + '\n'
+ func_doc = fn['signature'] + '\n'
func_doc += textwrap.indent(clean_lines(doc), ' ' * 16)
+
+ # Verbatim handling.
func_doc = re.sub(r'^\s+([<>])$', r'\1', func_doc, flags=re.M)
+ split_lines = func_doc.split('\n')
+ start = 0
+ while True:
+ try:
+ start = split_lines.index('>', start)
+ except ValueError:
+ break
+
+ try:
+ end = split_lines.index('<', start)
+ except ValueError:
+ break
+
+ split_lines[start + 1:end] = [
+ (' ' + x).rstrip()
+ for x in textwrap.dedent(
+ "\n".join(
+ split_lines[start+1:end]
+ )
+ ).split("\n")
+ ]
+
+ start = end
+
+ func_doc = "\n".join(split_lines)
+
if 'Deprecated' in xrefs:
- deprecated_functions.append(func_doc)
- elif name.startswith(CONFIG[mode]['func_name_prefix']):
- functions.append(func_doc)
+ deprecated_fns_txt[name] = func_doc
+ elif name.startswith(CONFIG[target]['fn_name_prefix']):
+ fns_txt[name] = func_doc
xrefs.clear()
- return '\n\n'.join(functions), '\n\n'.join(deprecated_functions)
+ fmt_vimhelp = False
+ return ('\n\n'.join(list(fns_txt.values())),
+ '\n\n'.join(list(deprecated_fns_txt.values())))
def delete_lines_below(filename, tokenstr):
@@ -590,33 +829,54 @@ def delete_lines_below(filename, tokenstr):
"""
lines = open(filename).readlines()
i = 0
+ found = False
for i, line in enumerate(lines, 1):
if tokenstr in line:
+ found = True
break
+ if not found:
+ raise RuntimeError(f'not found: "{tokenstr}"')
i = max(0, i - 2)
with open(filename, 'wt') as fp:
fp.writelines(lines[0:i])
-def gen_docs(config):
- """Generate documentation.
+def main(config):
+ """Generates:
+
+ 1. Vim :help docs
+ 2. *.mpack files for use by API clients
Doxygen is called and configured through stdin.
"""
- for mode in CONFIG:
- output_dir = out_dir.format(mode=mode)
- p = subprocess.Popen(['doxygen', '-'], stdin=subprocess.PIPE)
+ for target in CONFIG:
+ if TARGET is not None and target != TARGET:
+ continue
+ mpack_file = os.path.join(
+ base_dir, 'runtime', 'doc',
+ CONFIG[target]['filename'].replace('.txt', '.mpack'))
+ if os.path.exists(mpack_file):
+ os.remove(mpack_file)
+
+ output_dir = out_dir.format(target=target)
+ p = subprocess.Popen(
+ ['doxygen', '-'],
+ stdin=subprocess.PIPE,
+ # silence warnings
+ # runtime/lua/vim/lsp.lua:209: warning: argument 'foo' not found
+ stderr=(subprocess.STDOUT if DEBUG else subprocess.DEVNULL))
p.communicate(
config.format(
- input=CONFIG[mode]['files'],
+ input=CONFIG[target]['files'],
output=output_dir,
filter=filter_cmd,
- file_patterns=CONFIG[mode]['file_patterns'])
+ file_patterns=CONFIG[target]['file_patterns'])
.encode('utf8')
)
if p.returncode:
sys.exit(p.returncode)
+ fn_map_full = {} # Collects all functions as each module is processed.
sections = {}
intros = {}
sep = '=' * text_width
@@ -633,11 +893,21 @@ def gen_docs(config):
groupxml = os.path.join(base, '%s.xml' %
compound.getAttribute('refid'))
- desc = find_first(minidom.parse(groupxml), 'detaileddescription')
+ 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 = parse_parblock(desc)
+ doc = fmt_node_as_vimhelp(desc)
+
if doc:
- intros[groupname] = doc
+ doc_list.append(doc)
+
+ intros[groupname] = "\n".join(doc_list)
for compound in dom.getElementsByTagName('compound'):
if compound.getAttribute('kind') != 'file':
@@ -645,59 +915,54 @@ def gen_docs(config):
filename = get_text(find_first(compound, 'name'))
if filename.endswith('.c') or filename.endswith('.lua'):
- functions, deprecated = parse_source_xml(
- os.path.join(base, '%s.xml' %
- compound.getAttribute('refid')), mode)
-
- if not functions and not deprecated:
+ # Extract unformatted (*.mpack).
+ fn_map, _ = extract_from_xml(os.path.join(base, '{}.xml'.format(
+ compound.getAttribute('refid'))), target, width=9999)
+ # Extract formatted (:help).
+ functions_text, deprecated_text = fmt_doxygen_xml_as_vimhelp(
+ os.path.join(base, '{}.xml'.format(
+ compound.getAttribute('refid'))), target)
+
+ if not functions_text and not deprecated_text:
continue
-
- if functions or deprecated:
- name = os.path.splitext(os.path.basename(filename))[0]
- if name == 'ui':
- name = name.upper()
- else:
- name = name.title()
-
+ else:
+ name = os.path.splitext(
+ os.path.basename(filename))[0].lower()
+ sectname = name.upper() if name == 'ui' else name.title()
doc = ''
-
- intro = intros.get('api-%s' % name.lower())
+ intro = intros.get(f'api-{name}')
if intro:
doc += '\n\n' + intro
- if functions:
- doc += '\n\n' + functions
+ if functions_text:
+ doc += '\n\n' + functions_text
- if INCLUDE_DEPRECATED and deprecated:
- doc += '\n\n\nDeprecated %s Functions: ~\n\n' % name
- doc += deprecated
+ if INCLUDE_DEPRECATED and deprecated_text:
+ doc += f'\n\n\nDeprecated {sectname} Functions: ~\n\n'
+ doc += deprecated_text
if doc:
filename = os.path.basename(filename)
- name = CONFIG[mode]['section_name'].get(filename, name)
-
- if mode == 'lua':
- title = 'Lua module: {}'.format(name.lower())
- helptag = '*lua-{}*'.format(name.lower())
- else:
- title = '{} Functions'.format(name)
- helptag = '*api-{}*'.format(name.lower())
+ 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)
+ fn_map_full.update(fn_map)
- if not sections:
- return
+ assert sections
+ if len(sections) > len(CONFIG[target]['section_order']):
+ raise RuntimeError(
+ 'found new modules "{}"; update the "section_order" map'.format(
+ set(sections).difference(CONFIG[target]['section_order'])))
docs = ''
i = 0
- for filename in CONFIG[mode]['section_order']:
- if filename not in sections:
- raise RuntimeError(
- 'found new module "{}"; update the "section_order" map'.format(
- filename))
+ for filename in CONFIG[target]['section_order']:
title, helptag, section_doc = sections.pop(filename)
i += 1
- if filename not in CONFIG[mode]['append_only']:
+ if filename not in CONFIG[target]['append_only']:
docs += sep
docs += '\n%s%s' % (title,
helptag.rjust(text_width - len(title)))
@@ -708,12 +973,16 @@ def gen_docs(config):
docs += ' vim:tw=78:ts=8:ft=help:norl:\n'
doc_file = os.path.join(base_dir, 'runtime', 'doc',
- CONFIG[mode]['filename'])
+ CONFIG[target]['filename'])
- delete_lines_below(doc_file, CONFIG[mode]['section_start_token'])
+ delete_lines_below(doc_file, CONFIG[target]['section_start_token'])
with open(doc_file, 'ab') as fp:
fp.write(docs.encode('utf8'))
+ fn_map_full = collections.OrderedDict(sorted(fn_map_full.items()))
+ with open(mpack_file, 'wb') as fp:
+ fp.write(msgpack.packb(fn_map_full, use_bin_type=True))
+
shutil.rmtree(output_dir)
@@ -732,47 +1001,45 @@ def filter_source(filename):
fp.read(), flags=re.M))
-# Doxygen Config {{{
-Doxyfile = '''
-OUTPUT_DIRECTORY = {output}
-INPUT = {input}
-INPUT_ENCODING = UTF-8
-FILE_PATTERNS = {file_patterns}
-RECURSIVE = YES
-INPUT_FILTER = "{filter}"
-EXCLUDE =
-EXCLUDE_SYMLINKS = NO
-EXCLUDE_PATTERNS = */private/*
-EXCLUDE_SYMBOLS =
-EXTENSION_MAPPING = lua=C
-EXTRACT_PRIVATE = NO
-
-GENERATE_HTML = NO
-GENERATE_DOCSET = NO
-GENERATE_HTMLHELP = NO
-GENERATE_QHP = NO
-GENERATE_TREEVIEW = NO
-GENERATE_LATEX = NO
-GENERATE_RTF = NO
-GENERATE_MAN = NO
-GENERATE_DOCBOOK = NO
-GENERATE_AUTOGEN_DEF = NO
-
-GENERATE_XML = YES
-XML_OUTPUT = xml
-XML_PROGRAMLISTING = NO
-
-ENABLE_PREPROCESSING = YES
-MACRO_EXPANSION = YES
-EXPAND_ONLY_PREDEF = NO
-MARKDOWN_SUPPORT = YES
-'''
-# }}}
+Doxyfile = textwrap.dedent('''
+ OUTPUT_DIRECTORY = {output}
+ INPUT = {input}
+ INPUT_ENCODING = UTF-8
+ FILE_PATTERNS = {file_patterns}
+ RECURSIVE = YES
+ INPUT_FILTER = "{filter}"
+ EXCLUDE =
+ EXCLUDE_SYMLINKS = NO
+ EXCLUDE_PATTERNS = */private/*
+ EXCLUDE_SYMBOLS =
+ EXTENSION_MAPPING = lua=C
+ EXTRACT_PRIVATE = NO
+
+ GENERATE_HTML = NO
+ GENERATE_DOCSET = NO
+ GENERATE_HTMLHELP = NO
+ GENERATE_QHP = NO
+ GENERATE_TREEVIEW = NO
+ GENERATE_LATEX = NO
+ GENERATE_RTF = NO
+ GENERATE_MAN = NO
+ GENERATE_DOCBOOK = NO
+ GENERATE_AUTOGEN_DEF = NO
+
+ GENERATE_XML = YES
+ XML_OUTPUT = xml
+ XML_PROGRAMLISTING = NO
+
+ ENABLE_PREPROCESSING = YES
+ MACRO_EXPANSION = YES
+ EXPAND_ONLY_PREDEF = NO
+ MARKDOWN_SUPPORT = YES
+''')
if __name__ == "__main__":
if len(sys.argv) > 1:
filter_source(sys.argv[1])
else:
- gen_docs(Doxyfile)
+ main(Doxyfile)
-# vim: set ft=python ts=4 sw=4 tw=79 et fdm=marker :
+# vim: set ft=python ts=4 sw=4 tw=79 et :
diff --git a/scripts/lua2dox.lua b/scripts/lua2dox.lua
index 77cdabcc4b..d4e68f9e45 100644
--- a/scripts/lua2dox.lua
+++ b/scripts/lua2dox.lua
@@ -17,61 +17,28 @@
-- Free Software Foundation, Inc., --
-- 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. --
----------------------------------------------------------------------------]]
---[[!
-\file
-\brief a hack lua2dox converter
-]]
--[[!
-\mainpage
-
-Introduction
-------------
-
-A hack lua2dox converter
-Version 0.2
-
-This lets us make Doxygen output some documentation to let
-us develop this code.
+Lua-to-Doxygen converter
-It is partially cribbed from the functionality of lua2dox
-(http://search.cpan.org/~alec/Doxygen-Lua-0.02/lib/Doxygen/Lua.pm).
-Found on CPAN when looking for something else; kinda handy.
-
-Improved from lua2dox to make the doxygen output more friendly.
-Also it runs faster in lua rather than Perl.
-
-Because this Perl based system is called "lua2dox"., I have decided to add ".lua" to the name
-to keep the two separate.
+Partially from lua2dox
+http://search.cpan.org/~alec/Doxygen-Lua-0.02/lib/Doxygen/Lua.pm
Running
-------
-<ol>
-<li> Ensure doxygen is installed on your system and that you are familiar with its use.
-Best is to try to make and document some simple C/C++/PHP to see what it produces.
-You can experiment with the enclosed example code.
-
-<li> Run "doxygen -g" to create a default Doxyfile.
-
-Then alter it to let it recognise lua. Add the two following lines:
+This file "lua2dox.lua" gets called by "lua2dox_filter" (bash).
-\code{.bash}
-FILE_PATTERNS = *.lua
+Doxygen must be on your system. You can experiment like so:
-FILTER_PATTERNS = *.lua=lua2dox_filter
-\endcode
-
-
-Either add them to the end or find the appropriate entry in Doxyfile.
-
-There are other lines that you might like to alter, but see futher documentation for details.
-
-<li> When Doxyfile is edited run "doxygen"
+- Run "doxygen -g" to create a default Doxyfile.
+- Then alter it to let it recognise lua. Add the two following lines:
+ FILE_PATTERNS = *.lua
+ FILTER_PATTERNS = *.lua=lua2dox_filter
+- Then run "doxygen".
The core function reads the input file (filename or stdin) and outputs some pseudo C-ish language.
It only has to be good enough for doxygen to see it as legal.
-Therefore our lua interpreter is fairly limited, but "good enough".
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.
@@ -81,40 +48,8 @@ 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.
The effect is that you will get the function documented, but not with the parameter list you might expect.
-</ol>
-
-Installation
-------------
-
-Here for linux or unix-like, for any other OS you need to refer to other documentation.
-
-This file is "lua2dox.lua". It gets called by "lua2dox_filter"(bash).
-Somewhere in your path (e.g. "~/bin" or "/usr/local/bin") put a link to "lua2dox_filter".
-
-Documentation
--------------
-
-Read the external documentation that should be part of this package.
-For example look for the "README" and some .PDFs.
-
]]
--- we won't use our library code, so this becomes more portable
-
--- require 'elijah_fix_require'
--- require 'elijah_class'
---
---! \brief ``declare'' as class
---!
---! use as:
---! \code{.lua}
---! TWibble = class()
---! function TWibble.init(this,Str)
---! this.str = Str
---! -- more stuff here
---! end
---! \endcode
---!
function class(BaseClass, ClassInitialiser)
local newClass = {} -- a new class newClass
if not ClassInitialiser and type(BaseClass) == 'function' then
@@ -165,8 +100,6 @@ function class(BaseClass, ClassInitialiser)
return newClass
end
--- require 'elijah_clock'
-
--! \class TCore_Clock
--! \brief a clock
TCore_Clock = class()
@@ -201,9 +134,6 @@ function TCore_Clock.getTimeStamp(this,T0)
end
---require 'elijah_io'
-
---! \class TCore_IO
--! \brief io to console
--!
--! pseudo class (no methods, just to keep documentation tidy)
@@ -225,8 +155,6 @@ function TCore_IO_writeln(Str)
end
---require 'elijah_string'
-
--! \brief trims a string
function string_trim(Str)
return Str:match("^%s*(.-)%s*$")
@@ -257,8 +185,6 @@ function string_split(Str, Pattern)
end
---require 'elijah_commandline'
-
--! \class TCore_Commandline
--! \brief reads/parses commandline
TCore_Commandline = class()
@@ -279,9 +205,6 @@ function TCore_Commandline.getRaw(this,Key,Default)
return val
end
-
---require 'elijah_debug'
-
-------------------------------
--! \brief file buffer
--!
@@ -543,7 +466,6 @@ function TLua2DoX_filter.readfile(this,AppStamp,Filename)
local fn = TString_removeCommentFromLine(string_trim(string.sub(line,pos_fn+8)))
if fn_magic then
fn = fn_magic
- fn_magic = nil
end
if string.sub(fn,1,1)=='(' then
@@ -554,49 +476,20 @@ function TLua2DoX_filter.readfile(this,AppStamp,Filename)
-- want to fix for iffy declarations
local open_paren = string.find(fn,'[%({]')
- local fn0 = fn
if open_paren then
- fn0 = string.sub(fn,1,open_paren-1)
-- we might have a missing close paren
if not string.find(fn,'%)') then
fn = fn .. ' ___MissingCloseParenHere___)'
end
end
- local dot = string.find(fn0,'[%.:]')
- if dot then -- it's a method
- local klass = string.sub(fn,1,dot-1)
- local method = string.sub(fn,dot+1)
- --TCore_IO_writeln('function ' .. klass .. '::' .. method .. ftail .. '{}')
- --TCore_IO_writeln(klass .. '::' .. method .. ftail .. '{}')
- outStream:writeln(
- '/*! \\memberof ' .. klass .. ' */ '
- .. method .. '{}'
- )
- else
- -- add vanilla function
-
- outStream:writeln(fn_type .. 'function ' .. fn .. '{}')
- end
+ -- add vanilla function
+ outStream:writeln(fn_type .. 'function ' .. fn .. '{}')
end
else
this:warning(inStream:getLineNo(),'something weird here')
end
fn_magic = nil -- mustn't indavertently use it again
- elseif string.find(line,'=%s*class%(') then
- state = 'in_class' -- it's a class declaration
- local tailComment
- line,tailComment = TString_removeCommentFromLine(line)
- local equals = string.find(line,'=')
- local klass = string_trim(string.sub(line,1,equals-1))
- local tail = string_trim(string.sub(line,equals+1))
- -- class(wibble wibble)
- -- ....v.
- local parent = string.sub(tail,7,-2)
- if #parent>0 then
- parent = ' :public ' .. parent
- end
- outStream:writeln('class ' .. klass .. parent .. '{};')
else
state = '' -- unknown
if #line>0 then -- we don't know what this line means, so just comment it out
diff --git a/scripts/lua2dox_filter b/scripts/lua2dox_filter
index 6cb16ef060..61577527c4 100755
--- a/scripts/lua2dox_filter
+++ b/scripts/lua2dox_filter
@@ -36,7 +36,16 @@ test_executable(){
##! \brief sets the lua interpreter
set_lua(){
- test_executable 'texlua'
+ if test -z "${EXE}"
+ then
+ test_executable 'luajit'
+ fi
+
+ if test -z "${EXE}"
+ then
+ test_executable 'texlua'
+ fi
+
if test -z "${EXE}"
then
test_executable 'lua'
diff --git a/scripts/release.sh b/scripts/release.sh
index 29d61370ce..5b4902a2d7 100755
--- a/scripts/release.sh
+++ b/scripts/release.sh
@@ -80,7 +80,7 @@ _do_bump_commit() {
<release date="'"${__DATE}"'" version="xxx"/>,' runtime/nvim.appdata.xml
rm CMakeLists.txt.bk
rm runtime/nvim.appdata.xml.bk
- nvim +'/NVIM_VERSION' +1new +'exe "norm! iUpdate version numbers!!!\<CR>"' \
+ nvim +'/NVIM_VERSION' +1new +'exe "norm! iUpdate version numbers!!!"' \
-O CMakeLists.txt runtime/nvim.appdata.xml
git add CMakeLists.txt runtime/nvim.appdata.xml
@@ -93,8 +93,8 @@ fi
_do_bump_commit
echo "
Next steps:
- - Double-check NVIM_VERSION_* in CMakeLists.txt
- - Double-check runtime/nvim.appdata.xml
+ - Update runtime/nvim.appdata.xml on _master_
+ - Run tests/CI (version_spec.lua)!
- Push the tag:
git push --follow-tags
- Update the 'stable' tag:
diff --git a/scripts/shadacat.py b/scripts/shadacat.py
index 89846427a5..2b71fc2385 100755
--- a/scripts/shadacat.py
+++ b/scripts/shadacat.py
@@ -66,7 +66,7 @@ except IndexError:
def filt(entry): return True
else:
_filt = filt
- def filt(entry): return eval(_filt, globals(), {'entry': entry})
+ def filt(entry): return eval(_filt, globals(), {'entry': entry}) # noqa
poswidth = len(str(os.stat(fname).st_size or 1000))
diff --git a/scripts/update-ts-runtime.sh b/scripts/update-ts-runtime.sh
new file mode 100755
index 0000000000..1a947e0ac9
--- /dev/null
+++ b/scripts/update-ts-runtime.sh
@@ -0,0 +1,39 @@
+#!/bin/sh
+#
+# This script will update the treesitter runtime to the provided commit.
+# Usage :
+# $0 <tree-sitter commit sha>
+set -e
+
+ts_source_dir="/tmp/tree-sitter"
+ts_url="https://github.com/tree-sitter/tree-sitter.git"
+
+base_dir="$(cd "$(dirname $(dirname $0))" && pwd)"
+ts_dest_dir="$base_dir/src/tree_sitter/"
+ts_current_commit="$ts_dest_dir/treesitter_commit_hash.txt"
+
+echo "Updating treesitter runtime from $(cat "$ts_current_commit") to $1..."
+
+if [ ! -d "$ts_source_dir" ]; then
+ echo "Cloning treesitter..."
+ git clone "$ts_url" "$ts_source_dir"
+else
+ echo "Found a non-empty $ts_source_dir directory..."
+ git -C "$ts_source_dir" fetch
+fi
+
+echo "Checking out $1..."
+git -C "$ts_source_dir" checkout $1
+
+echo "Removing old files..."
+find "$ts_dest_dir" -not -name "LICENSE" -not -name "README.md" -not -type d -delete
+
+echo "Copying files..."
+cp -t "$ts_dest_dir" -r "$ts_source_dir/lib/src"/*
+cp -t "$ts_dest_dir" "$ts_source_dir/lib/include/tree_sitter"/*
+
+echo "$1" > "$ts_current_commit"
+
+make
+TEST_FILE="$base_dir/test/functional/lua/treesitter_spec.lua" make test
+
diff --git a/scripts/update_version_stamp.lua b/scripts/update_version_stamp.lua
new file mode 100755
index 0000000000..11b521fab6
--- /dev/null
+++ b/scripts/update_version_stamp.lua
@@ -0,0 +1,55 @@
+#!/usr/bin/env lua
+--
+-- Script to update the Git version stamp during build.
+-- This is called via the custom update_version_stamp target in
+-- src/nvim/CMakeLists.txt.
+--
+-- arg[1]: file in which to update the version string
+-- arg[2]: prefix to use always ("vX.Y.Z")
+
+local function die(msg)
+ io.stderr:write(string.format('%s: %s\n', arg[0], msg))
+ -- No error, fall back to using generated "-dev" version.
+ os.exit(0)
+end
+
+local function iswin()
+ return package.config:sub(1,1) == '\\'
+end
+
+if #arg ~= 2 then
+ die(string.format("Expected two args, got %d", #arg))
+end
+
+local versiondeffile = arg[1]
+local prefix = arg[2]
+
+local dev_null = iswin() and 'NUL' or '/dev/null'
+local described = io.popen('git describe --first-parent --dirty 2>'..dev_null):read('*l')
+if not described then
+ described = io.popen('git describe --first-parent --tags --always --dirty'):read('*l')
+end
+if not described then
+ io.open(versiondeffile, 'w'):write('\n')
+ die('git-describe failed, using empty include file.')
+end
+
+-- `git describe` annotates the most recent tagged release; for pre-release
+-- builds we must replace that with the unreleased version.
+local with_prefix = described:gsub("^v%d+%.%d+%.%d+", prefix)
+if described == with_prefix then
+ -- Prepend the prefix always, e.g. with "nightly-12208-g4041b62b9".
+ with_prefix = prefix .. "-" .. described
+end
+
+-- Read existing include file.
+local current = io.open(versiondeffile, 'r')
+if current then
+ current = current:read('*l')
+end
+
+-- Write new include file, if different.
+local new = '#define NVIM_VERSION_MEDIUM "'..with_prefix..'"'
+if current ~= new then
+ io.open(versiondeffile, 'w'):write(new .. '\n')
+end
diff --git a/scripts/vim-patch.sh b/scripts/vim-patch.sh
index 2cc32f0dd0..9c4349abca 100755
--- a/scripts/vim-patch.sh
+++ b/scripts/vim-patch.sh
@@ -5,6 +5,12 @@ set -u
# Use privileged mode, which e.g. skips using CDPATH.
set -p
+# Ensure that the user has a bash that supports -A
+if [[ "${BASH_VERSINFO[0]}" -lt 4 ]]; then
+ >&2 echo "error: script requires bash 4+ (you have ${BASH_VERSION})."
+ exit 1
+fi
+
readonly NVIM_SOURCE_DIR="${NVIM_SOURCE_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}"
readonly VIM_SOURCE_DIR_DEFAULT="${NVIM_SOURCE_DIR}/.vim-src"
readonly VIM_SOURCE_DIR="${VIM_SOURCE_DIR:-${VIM_SOURCE_DIR_DEFAULT}}"
@@ -23,6 +29,7 @@ usage() {
echo " -h Show this message and exit."
echo " -l [git-log opts] List missing Vim patches."
echo " -L [git-log opts] List missing Vim patches (for scripts)."
+ echo " -m {vim-revision} List previous (older) missing Vim patches."
echo " -M List all merged patch-numbers (at current v:version)."
echo " -p {vim-revision} Download and generate a Vim patch. vim-revision"
echo " can be a Vim version (8.0.xxx) or a Git hash."
@@ -89,7 +96,7 @@ get_vim_sources() {
echo "Cloning Vim into: ${VIM_SOURCE_DIR}"
git clone https://github.com/vim/vim.git "${VIM_SOURCE_DIR}"
cd "${VIM_SOURCE_DIR}"
- else
+ elif [[ "${1-}" == update ]]; then
cd "${VIM_SOURCE_DIR}"
if ! [ -d ".git" ] \
&& ! [ "$(git rev-parse --show-toplevel)" = "${VIM_SOURCE_DIR}" ]; then
@@ -103,6 +110,8 @@ get_vim_sources() {
else
msg_err "Could not update Vim sources; ignoring error."
fi
+ else
+ cd "${VIM_SOURCE_DIR}"
fi
}
@@ -124,7 +133,7 @@ find_git_remote() {
}
# Assign variables for a given Vim tag, patch version, or commit.
-# Might exit in case it cannot be found.
+# Might exit in case it cannot be found, after updating Vim sources.
assign_commit_details() {
local vim_commit_ref
if [[ ${1} =~ v?[0-9]\.[0-9]\.[0-9]{3,4} ]]; then
@@ -146,9 +155,14 @@ assign_commit_details() {
local munge_commit_line=false
fi
- vim_commit=$(git -C "${VIM_SOURCE_DIR}" log -1 --format="%H" "${vim_commit_ref}" --) || {
- >&2 msg_err "Couldn't find Vim revision '${vim_commit_ref}'."
- exit 3
+ local get_vim_commit_cmd="git -C ${VIM_SOURCE_DIR} log -1 --format=%H ${vim_commit_ref} --"
+ vim_commit=$($get_vim_commit_cmd 2>&1) || {
+ # Update Vim sources.
+ get_vim_sources update
+ vim_commit=$($get_vim_commit_cmd 2>&1) || {
+ >&2 msg_err "Couldn't find Vim revision '${vim_commit_ref}': git error: ${vim_commit}."
+ exit 3
+ }
}
vim_commit_url="https://github.com/vim/vim/commit/${vim_commit}"
@@ -227,7 +241,8 @@ get_vimpatch() {
msg_ok "Saved patch to '${NVIM_SOURCE_DIR}/${patch_file}'."
}
-# shellcheck disable=SC2015 # "Note that A && B || C is not if-then-else."
+# shellcheck disable=SC2015
+# ^ "Note that A && B || C is not if-then-else."
stage_patch() {
get_vimpatch "$1"
local try_apply="${2:-}"
@@ -298,7 +313,8 @@ git_hub_pr() {
git hub pull new -m "$1"
}
-# shellcheck disable=SC2015 # "Note that A && B || C is not if-then-else."
+# shellcheck disable=SC2015
+# ^ "Note that A && B || C is not if-then-else."
submit_pr() {
require_executable git
local push_first
@@ -366,7 +382,7 @@ submit_pr() {
# Gets all Vim commits since the "start" commit.
list_vim_commits() { (
- cd "${VIM_SOURCE_DIR}" && git log --reverse --format='%H' v8.0.0000..HEAD "$@"
+ cd "${VIM_SOURCE_DIR}" && git log --reverse v8.0.0000..HEAD "$@"
) }
# Prints all (sorted) "vim-patch:xxx" tokens found in the Nvim git log.
@@ -388,19 +404,69 @@ list_vimpatch_numbers() {
done
}
+declare -A tokens
+declare -A vim_commit_tags
+
+_set_tokens_and_tags() {
+ set +u # Avoid "unbound variable" with bash < 4.4 below.
+ if [[ -n "${tokens[*]}" ]]; then
+ return
+ fi
+ set -u
+
+ # Find all "vim-patch:xxx" tokens in the Nvim git log.
+ for token in $(list_vimpatch_tokens); do
+ tokens[$token]=1
+ done
+
+ # 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)
+ )"
+ # Exit in case of errors from the above eval (empty vim_commit_tags).
+ if ! (( "${#vim_commit_tags[@]}" )); then
+ msg_err "Could not get Vim commits/tags."
+ exit 1
+ fi
+}
+
# Prints a newline-delimited list of Vim commits, for use by scripts.
+# "$1": use extended format? (with subject)
# "$@" is passed to list_vim_commits, as extra arguments to git-log.
list_missing_vimpatches() {
+ local -a missing_vim_patches=()
+ _set_missing_vimpatches "$@"
+ set +u # Avoid "unbound variable" with bash < 4.4 below.
+ for line in "${missing_vim_patches[@]}"; do
+ printf '%s\n' "$line"
+ done
+ set -u
+}
+
+# Sets / appends to missing_vim_patches (useful to avoid a subshell when
+# used multiple times to cache tokens/vim_commit_tags).
+# "$1": use extended format? (with subject)
+# "$@": extra arguments to git-log.
+_set_missing_vimpatches() {
local token vim_commit vim_tag patch_number
- declare -A tokens
- declare -A vim_commit_tags
declare -a git_log_args
+ local extended_format=$1; shift
+ if [[ "$extended_format" == 1 ]]; then
+ git_log_args=("--format=%H %s")
+ else
+ git_log_args=("--format=%H")
+ fi
+
# Massage arguments for git-log.
declare -A git_log_replacements=(
[^\(.*/\)?src/nvim/\(.*\)]="\${BASH_REMATCH[1]}src/\${BASH_REMATCH[2]}"
[^\(.*/\)?\.vim-src/\(.*\)]="\${BASH_REMATCH[2]}"
)
+ local i j
for i in "$@"; do
for j in "${!git_log_replacements[@]}"; do
if [[ "$i" =~ $j ]]; then
@@ -411,33 +477,31 @@ list_missing_vimpatches() {
git_log_args+=("$i")
done
- # Find all "vim-patch:xxx" tokens in the Nvim git log.
- for token in $(list_vimpatch_tokens); do
- tokens[$token]=1
- done
-
- # Create an associative array mapping Vim commits to tags.
- eval "declare -A vim_commit_tags=(
- $(git -C "${VIM_SOURCE_DIR}" for-each-ref refs/tags \
- --format '[%(objectname)]=%(refname:strip=2)' \
- --sort='-*authordate' \
- --shell)
- )"
- # Exit in case of errors from the above eval (empty vim_commit_tags).
- if ! (( "${#vim_commit_tags[@]}" )); then
- msg_err "Could not get Vim commits/tags."
- exit 1
- fi
+ _set_tokens_and_tags
# Get missing Vim commits
set +u # Avoid "unbound variable" with bash < 4.4 below.
- for vim_commit in $(list_vim_commits "${git_log_args[@]}"); do
+ local vim_commit info
+ while IFS=' ' read -r line; do
# Check for vim-patch:<commit_hash> (usually runtime updates).
- token="vim-patch:${vim_commit:0:7}"
+ token="vim-patch:${line:0:7}"
if [[ "${tokens[$token]-}" ]]; then
continue
fi
+ # Get commit hash, and optional info from line. This is used in
+ # extended mode, and when using e.g. '--format' manually.
+ vim_commit=${line%% *}
+ if [[ "$vim_commit" == "$line" ]]; then
+ info=
+ else
+ info=${line#* }
+ if [[ -n $info ]]; then
+ # Remove any "patch 8.0.0902: " prefixes, and prefix with ": ".
+ info=": ${info#patch*: }"
+ fi
+ fi
+
vim_tag="${vim_commit_tags[$vim_commit]-}"
if [[ -n "$vim_tag" ]]; then
# Check for vim-patch:<tag> (not commit hash).
@@ -445,26 +509,26 @@ list_missing_vimpatches() {
if [[ "${tokens[$patch_number]-}" ]]; then
continue
fi
- echo "$vim_tag"
+ missing_vim_patches+=("$vim_tag$info")
else
- echo "$vim_commit"
+ missing_vim_patches+=("$vim_commit$info")
fi
- done
+ done < <(list_vim_commits "${git_log_args[@]}")
set -u
}
# Prints a human-formatted list of Vim commits, with instructional messages.
# Passes "$@" onto list_missing_vimpatches (args for git-log).
show_vimpatches() {
- get_vim_sources
- printf "\nVim patches missing from Neovim:\n"
+ get_vim_sources update
+ 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
runtime_commits[$commit]=1
done
- list_missing_vimpatches "$@" | while read -r vim_commit; do
+ list_missing_vimpatches 1 "$@" | while read -r vim_commit; do
if [[ "${runtime_commits[$vim_commit]-}" ]]; then
printf ' • %s (+runtime)\n' "${vim_commit}"
else
@@ -472,18 +536,73 @@ show_vimpatches() {
fi
done
- printf "Instructions:
+ cat << EOF
- To port one of the above patches to Neovim, execute
- this script with the patch revision as argument and
- follow the instructions.
-
- Examples: '%s -p 7.4.487'
- '%s -p 1e8ebf870720e7b671f98f22d653009826304c4f'
+Instructions:
+ To port one of the above patches to Neovim, execute this script with the patch revision as argument and follow the instructions, e.g.
+ '${BASENAME} -p v8.0.1234', or '${BASENAME} -P v8.0.1234'
NOTE: Please port the _oldest_ patch if you possibly can.
- Out-of-order patches increase the possibility of bugs.
-" "${BASENAME}" "${BASENAME}"
+ You can use '${BASENAME} -l path/to/file' to see what patches are missing for a file.
+EOF
+}
+
+list_missing_previous_vimpatches_for_patch() {
+ local for_vim_patch="${1}"
+ local vim_commit vim_tag
+ assign_commit_details "${for_vim_patch}"
+
+ local file
+ local -a missing_list
+ local -a fnames
+ while IFS= read -r line ; do
+ fnames+=("$line")
+ done < <(git -C "${VIM_SOURCE_DIR}" diff-tree --no-commit-id --name-only -r "${vim_commit}")
+ local i=0
+ local n=${#fnames[@]}
+ printf '=== getting missing patches for %d files ===\n' "$n"
+ if [[ -z "${vim_tag}" ]]; then
+ printf 'NOTE: "%s" is not a Vim tag - listing all oldest missing patches\n' "${for_vim_patch}" >&2
+ fi
+ for fname in "${fnames[@]}"; do
+ i=$(( i+1 ))
+ printf '[%.*d/%d] %s: ' "${#n}" "$i" "$n" "$fname"
+
+ local -a missing_vim_patches=()
+ _set_missing_vimpatches 1 -- "${fname}"
+
+ set +u # Avoid "unbound variable" with bash < 4.4 below.
+ local missing_vim_commit_info="${missing_vim_patches[0]}"
+ if [[ -z "${missing_vim_commit_info}" ]]; then
+ printf -- "-\n"
+ else
+ local missing_vim_commit="${missing_vim_commit_info%%:*}"
+ if [[ -z "${vim_tag}" ]] || [[ "${missing_vim_commit}" < "${vim_tag}" ]]; then
+ printf -- "%s\n" "$missing_vim_commit_info"
+ missing_list+=("$missing_vim_commit_info")
+ else
+ printf -- "-\n"
+ fi
+ fi
+ set -u
+ done
+
+ set +u # Avoid "unbound variable" with bash < 4.4 below.
+ if [[ -z "${missing_list[*]}" ]]; then
+ msg_ok 'no missing previous Vim patches'
+ set -u
+ return 0
+ fi
+ set -u
+
+ local -a missing_unique
+ while IFS= read -r line; do
+ missing_unique+=("$line")
+ done < <(printf '%s\n' "${missing_list[@]}" | sort -u)
+
+ msg_err "$(printf '%d missing previous Vim patches:' ${#missing_unique[@]})"
+ printf ' - %s\n' "${missing_unique[@]}"
+ return 1
}
review_commit() {
@@ -561,9 +680,11 @@ review_pr() {
echo
echo "Downloading data for pull request #${pr}."
- local pr_commit_urls=(
- "$(curl -Ssf "https://api.github.com/repos/neovim/neovim/pulls/${pr}/commits" \
- | jq -r '.[].html_url')")
+ local -a pr_commit_urls
+ while IFS= read -r pr_commit_url; do
+ pr_commit_urls+=("$pr_commit_url")
+ done < <(curl -Ssf "https://api.github.com/repos/neovim/neovim/pulls/${pr}/commits" \
+ | jq -r '.[].html_url')
echo "Found ${#pr_commit_urls[@]} commit(s)."
@@ -583,7 +704,7 @@ review_pr() {
clean_files
}
-while getopts "hlLMVp:P:g:r:s" opt; do
+while getopts "hlLmMVp:P:g:r:s" opt; do
case ${opt} in
h)
usage
@@ -596,13 +717,18 @@ while getopts "hlLMVp:P:g:r:s" opt; do
;;
L)
shift # remove opt
- list_missing_vimpatches "$@"
+ list_missing_vimpatches 0 "$@"
exit 0
;;
M)
list_vimpatch_numbers
exit 0
;;
+ m)
+ shift # remove opt
+ list_missing_previous_vimpatches_for_patch "$@"
+ exit 0
+ ;;
p)
stage_patch "${OPTARG}"
exit
@@ -624,7 +750,7 @@ while getopts "hlLMVp:P:g:r:s" opt; do
exit 0
;;
V)
- get_vim_sources
+ get_vim_sources update
exit 0
;;
*)
diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml
index 81ffb9adf3..da3e74d3e7 100644
--- a/snap/snapcraft.yaml
+++ b/snap/snapcraft.yaml
@@ -1,5 +1,6 @@
-name: neovim
-version: git
+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:
@@ -9,22 +10,41 @@ description: |
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:
- neovim:
- command: usr/local/bin/nvim
- plugs: [network, network-bind, x11]
+ nvim:
+ command: usr/bin/nvim
environment:
HOME: /home/$USER
- VIM: $SNAP/usr/local/share/nvim/runtime
+ VIM: $SNAP/usr/share/nvim
+ VIMRUNTIME: $SNAP/usr/share/nvim/runtime
+ desktop: usr/share/applications/nvim.desktop
parts:
- neovim:
+ nvim:
source: .
+ override-pull: |
+ snapcraftctl pull
+ 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)}"
+ snapcraftctl set-version "${version_prefix}-${git_described}"
plugin: make
make-parameters:
- CMAKE_BUILD_TYPE=Release
+ - CMAKE_INSTALL_PREFIX=/usr
+ override-build: |
+ snapcraftctl build
+ # 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/pixmaps/nvim.png|' ${SNAPCRAFT_PART_INSTALL}/usr/share/applications/nvim.desktop
build-packages:
- ninja-build
- libtool
@@ -33,9 +53,10 @@ parts:
- automake
- cmake
- g++
+ - git
+ - gettext
- pkg-config
- unzip
- snap:
- - usr/local/bin
- - usr/local/share/nvim
- - -usr/local/share/man
+ prime:
+ - -usr/share/man
+
diff --git a/src/clint.py b/src/clint.py
index 675b67ccef..8dc41fdb93 100755
--- a/src/clint.py
+++ b/src/clint.py
@@ -270,6 +270,8 @@ _line_length = 80
# This is set by --extensions flag.
_valid_extensions = set(['c', 'h'])
+_RE_COMMENTLINE = re.compile(r'^\s*//')
+
def ParseNolintSuppressions(filename, raw_line, linenum, error):
"""Updates the global list of error-suppressions.
@@ -1358,7 +1360,9 @@ def CheckForOldStyleComments(filename, line, linenum, error):
linenum: The number of the line to check.
error: The function to call with any errors found.
"""
- if line.find('/*') >= 0 and line[-1] != '\\':
+ # hack: allow /* inside comment line. Could be extended to allow them inside
+ # any // comment.
+ if line.find('/*') >= 0 and line[-1] != '\\' and not _RE_COMMENTLINE.match(line):
error(filename, linenum, 'readability/old_style_comment', 5,
'/*-style comment found, it should be replaced with //-style. '
'/*-style comments are only allowed inside macros. '
@@ -1769,7 +1773,7 @@ def CheckSpacingForFunctionCall(filename, line, linenum, error):
fncall = match.group(1)
break
- # Except in if/for/while/switch, there should never be space
+ # Except in if/for/while/switch/case, there should never be space
# immediately inside parens (eg "f( 3, 4 )"). We make an exception
# for nested parens ( (a+b) + c ). Likewise, there should never be
# a space before a ( when it's a function argument. I assume it's a
@@ -1783,7 +1787,7 @@ def CheckSpacingForFunctionCall(filename, line, linenum, error):
# Note that we assume the contents of [] to be short enough that
# they'll never need to wrap.
if ( # Ignore control structures.
- not Search(r'\b(if|for|while|switch|return|sizeof)\b', fncall) and
+ not Search(r'\b(if|for|while|switch|case|return|sizeof)\b', fncall) and
# Ignore pointers/references to functions.
not Search(r' \([^)]+\)\([^)]*(\)|,$)', fncall) and
# Ignore pointers/references to arrays.
diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt
index aa8100873b..c7258dde12 100644
--- a/src/nvim/CMakeLists.txt
+++ b/src/nvim/CMakeLists.txt
@@ -23,6 +23,7 @@ endif()
set(TOUCHES_DIR ${PROJECT_BINARY_DIR}/touches)
set(GENERATOR_DIR ${CMAKE_CURRENT_LIST_DIR}/generators)
set(GENERATED_DIR ${PROJECT_BINARY_DIR}/src/nvim/auto)
+set(BINARY_LIB_DIR ${PROJECT_BINARY_DIR}/lib/nvim/)
set(API_DISPATCH_GENERATOR ${GENERATOR_DIR}/gen_api_dispatch.lua)
set(API_UI_EVENTS_GENERATOR ${GENERATOR_DIR}/gen_api_ui_events.lua)
set(GENERATOR_C_GRAMMAR ${GENERATOR_DIR}/c_grammar.lua)
@@ -52,7 +53,8 @@ set(UNICODE_TABLES_GENERATOR ${GENERATOR_DIR}/gen_unicode_tables.lua)
set(UNICODE_DIR ${PROJECT_SOURCE_DIR}/unicode)
set(GENERATED_UNICODE_TABLES ${GENERATED_DIR}/unicode_tables.generated.h)
set(VIM_MODULE_FILE ${GENERATED_DIR}/lua/vim_module.generated.h)
-set(VIM_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/src/nvim/lua/vim.lua)
+set(LUA_VIM_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/src/nvim/lua/vim.lua)
+set(LUA_SHARED_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/runtime/lua/vim/shared.lua)
set(CHAR_BLOB_GENERATOR ${GENERATOR_DIR}/gen_char_blob.lua)
set(LINT_SUPPRESS_FILE ${PROJECT_BINARY_DIR}/errors.json)
set(LINT_SUPPRESS_URL_BASE "https://raw.githubusercontent.com/neovim/doc/gh-pages/reports/clint")
@@ -85,6 +87,10 @@ file(GLOB NVIM_HEADERS *.h)
file(GLOB XDIFF_SOURCES xdiff/*.c)
file(GLOB XDIFF_HEADERS xdiff/*.h)
+file(GLOB TREESITTER_SOURCES ../tree_sitter/*.c)
+file(GLOB TS_SOURCE_AMALGAM ../tree_sitter/lib.c)
+list(REMOVE_ITEM TREESITTER_SOURCES ${TS_SOURCE_AMALGAM})
+
foreach(subdir
os
api
@@ -129,6 +135,12 @@ foreach(sfile ${NVIM_SOURCES})
if(NOT WIN32 AND ${f} MATCHES "^(pty_process_win.c)$")
list(APPEND to_remove ${sfile})
endif()
+ if(NOT WIN32 AND ${f} MATCHES "^(pty_conpty_win.c)$")
+ list(APPEND to_remove ${sfile})
+ endif()
+ if(NOT WIN32 AND ${f} MATCHES "^(os_win_console.c)$")
+ list(APPEND to_remove ${sfile})
+ endif()
endforeach()
list(REMOVE_ITEM NVIM_SOURCES ${to_remove})
@@ -138,9 +150,12 @@ set(CONV_SOURCES
diff.c
edit.c
eval.c
+ eval/funcs.c
+ eval/userfunc.c
ex_cmds.c
ex_docmd.c
fileio.c
+ lua/treesitter.c
mbyte.c
memline.c
message.c
@@ -167,11 +182,14 @@ if(NOT MSVC)
check_c_compiler_flag(-Wstatic-in-inline HAS_WSTATIC_IN_INLINE)
if(HAS_WSTATIC_IN_INLINE)
set_source_files_properties(
- eval.c PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -Wno-static-in-inline -Wno-conversion")
+ eval/funcs.c PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -Wno-static-in-inline -Wno-conversion")
else()
set_source_files_properties(
- eval.c PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -Wno-conversion")
+ eval/funcs.c PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -Wno-conversion")
endif()
+
+ # tree-sitter: inlined external project, we don't maintain it. #10124
+ set_source_files_properties(${TREESITTER_SOURCES} PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -Wno-conversion -Wno-pedantic -Wno-shadow -Wno-missing-prototypes -Wno-unused-variable")
endif()
if(NOT "${MIN_LOG_LEVEL}" MATCHES "^$")
@@ -203,12 +221,40 @@ set(gen_cflags ${gen_cflags} ${C_FLAGS_${build_type}_ARRAY} ${C_FLAGS_ARRAY})
function(get_preproc_output varname iname)
if(MSVC)
- set(${varname} /P /Fi${iname} PARENT_SCOPE)
+ set(${varname} /P /Fi${iname} /nologo PARENT_SCOPE)
else()
set(${varname} -E -o ${iname} PARENT_SCOPE)
endif()
endfunction()
+# Handle generating version from Git.
+set(use_git_version 0)
+if(NVIM_VERSION_MEDIUM)
+ message(STATUS "NVIM_VERSION_MEDIUM: ${NVIM_VERSION_MEDIUM}")
+elseif(${CMAKE_VERSION} VERSION_LESS "3.2.0")
+ message(STATUS "Skipping version-string generation (requires CMake 3.2.0+)")
+elseif(EXISTS ${PROJECT_SOURCE_DIR}/.git)
+ find_program(GIT_EXECUTABLE git)
+ if(GIT_EXECUTABLE)
+ message(STATUS "Using NVIM_VERSION_MEDIUM from Git")
+ set(use_git_version 1)
+ else()
+ message(STATUS "Skipping version-string generation (cannot find git)")
+ endif()
+endif()
+if(use_git_version)
+ # Create a update_version_stamp target to update the version during build.
+ file(RELATIVE_PATH relbuild "${PROJECT_SOURCE_DIR}" "${CMAKE_BINARY_DIR}")
+ add_custom_target(update_version_stamp ALL
+ COMMAND ${LUA_PRG} scripts/update_version_stamp.lua
+ ${relbuild}/config/auto/versiondef_git.h
+ "v${NVIM_VERSION_MAJOR}.${NVIM_VERSION_MINOR}.${NVIM_VERSION_PATCH}"
+ WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
+ BYPRODUCTS ${CMAKE_BINARY_DIR}/config/auto/versiondef_git.h)
+else()
+ file(WRITE ${CMAKE_BINARY_DIR}/config/auto/versiondef_git.h "")
+endif()
+
# 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
@@ -237,12 +283,16 @@ foreach(sfile ${NVIM_SOURCES}
get_preproc_output(PREPROC_OUTPUT ${gf_i})
+ set(depends "${HEADER_GENERATOR}" "${sfile}")
+ if(use_git_version AND "${f}" STREQUAL "version.c")
+ # Ensure auto/versiondef_git.h exists after "make clean".
+ list(APPEND depends update_version_stamp)
+ endif()
add_custom_command(
OUTPUT "${gf_c_h}" "${gf_h_h}"
COMMAND ${CMAKE_C_COMPILER} ${sfile} ${PREPROC_OUTPUT} ${gen_cflags} ${C_FLAGS_ARRAY}
COMMAND "${LUA_PRG}" "${HEADER_GENERATOR}" "${sfile}" "${gf_c_h}" "${gf_h_h}" "${gf_i}"
- DEPENDS "${HEADER_GENERATOR}" "${sfile}"
- )
+ DEPENDS ${depends})
list(APPEND NVIM_GENERATED_FOR_SOURCES "${gf_c_h}")
list(APPEND NVIM_GENERATED_FOR_HEADERS "${gf_h_h}")
if(${d} MATCHES "^api$" AND NOT ${f} MATCHES "^api/helpers.c$")
@@ -277,11 +327,13 @@ add_custom_command(
add_custom_command(
OUTPUT ${VIM_MODULE_FILE}
- COMMAND ${LUA_PRG} ${CHAR_BLOB_GENERATOR} ${VIM_MODULE_SOURCE}
- ${VIM_MODULE_FILE} vim_module
+ COMMAND ${LUA_PRG} ${CHAR_BLOB_GENERATOR} ${VIM_MODULE_FILE}
+ ${LUA_VIM_MODULE_SOURCE} vim_module
+ ${LUA_SHARED_MODULE_SOURCE} shared_module
DEPENDS
${CHAR_BLOB_GENERATOR}
- ${VIM_MODULE_SOURCE}
+ ${LUA_VIM_MODULE_SOURCE}
+ ${LUA_SHARED_MODULE_SOURCE}
)
list(APPEND NVIM_GENERATED_SOURCES
@@ -379,6 +431,7 @@ if(Iconv_LIBRARIES)
endif()
if(WIN32)
+ list(APPEND NVIM_LINK_LIBRARIES netapi32)
list(APPEND NVIM_LINK_LIBRARIES ${WINPTY_LIBRARIES})
endif()
@@ -395,6 +448,7 @@ list(APPEND NVIM_LINK_LIBRARIES
${LIBVTERM_LIBRARIES}
${LIBTERMKEY_LIBRARIES}
${UNIBILIUM_LIBRARIES}
+ ${UTF8PROC_LIBRARIES}
${CMAKE_THREAD_LIBS_INIT}
)
@@ -414,12 +468,13 @@ endif()
add_executable(nvim ${NVIM_GENERATED_FOR_SOURCES} ${NVIM_GENERATED_FOR_HEADERS}
${NVIM_GENERATED_SOURCES} ${NVIM_SOURCES} ${NVIM_HEADERS}
- ${XDIFF_SOURCES} ${XDIFF_HEADERS})
+ ${XDIFF_SOURCES} ${XDIFF_HEADERS} ${TREESITTER_SOURCES})
target_link_libraries(nvim ${NVIM_EXEC_LINK_LIBRARIES})
install_helper(TARGETS nvim)
set_property(TARGET nvim APPEND PROPERTY
INCLUDE_DIRECTORIES ${LUA_PREFERRED_INCLUDE_DIRS})
+set_property(TARGET nvim PROPERTY ENABLE_EXPORTS TRUE)
if(ENABLE_LTO AND (POLICY CMP0069))
include(CheckIPOSupported)
@@ -494,21 +549,37 @@ else()
endif()
set_target_properties(nvim_runtime_deps PROPERTIES FOLDER deps)
+file(MAKE_DIRECTORY ${BINARY_LIB_DIR})
+
+# install treesitter parser if bundled
+if(EXISTS ${DEPS_PREFIX}/lib/nvim/parser)
+ file(COPY ${DEPS_PREFIX}/lib/nvim/parser DESTINATION ${BINARY_LIB_DIR})
+endif()
+
+install(DIRECTORY ${BINARY_LIB_DIR}
+ DESTINATION ${CMAKE_INSTALL_LIBDIR}/nvim/
+ USE_SOURCE_PERMISSIONS)
+
add_library(
libnvim
STATIC
EXCLUDE_FROM_ALL
${NVIM_SOURCES} ${NVIM_GENERATED_SOURCES}
${NVIM_HEADERS} ${NVIM_GENERATED_FOR_SOURCES} ${NVIM_GENERATED_FOR_HEADERS}
- ${XDIFF_SOURCES} ${XDIFF_HEADERS}
+ ${XDIFF_SOURCES} ${XDIFF_HEADERS} ${TREESITTER_SOURCES}
)
set_property(TARGET libnvim APPEND PROPERTY
INCLUDE_DIRECTORIES ${LUA_PREFERRED_INCLUDE_DIRS})
+if(MSVC)
+ set(LIBNVIM_NAME libnvim)
+else()
+ set(LIBNVIM_NAME nvim)
+endif()
set_target_properties(
libnvim
PROPERTIES
POSITION_INDEPENDENT_CODE ON
- OUTPUT_NAME nvim
+ OUTPUT_NAME ${LIBNVIM_NAME}
)
set_property(
TARGET libnvim
@@ -525,7 +596,7 @@ else()
EXCLUDE_FROM_ALL
${NVIM_SOURCES} ${NVIM_GENERATED_SOURCES}
${NVIM_HEADERS} ${NVIM_GENERATED_FOR_SOURCES} ${NVIM_GENERATED_FOR_HEADERS}
- ${XDIFF_SOURCES} ${XDIFF_HEADERS}
+ ${XDIFF_SOURCES} ${XDIFF_HEADERS} ${TREESITTER_SOURCES}
${UNIT_TEST_FIXTURES}
)
target_link_libraries(nvim-test ${NVIM_TEST_LINK_LIBRARIES})
@@ -591,6 +662,8 @@ endfunction()
set(NO_SINGLE_CHECK_HEADERS
os/win_defs.h
os/pty_process_win.h
+ os/pty_conpty_win.h
+ os/os_win_console.h
)
foreach(hfile ${NVIM_HEADERS})
get_test_target(test-includes "${hfile}" relative_path texe)
diff --git a/src/nvim/README.md b/src/nvim/README.md
index 3f7c05757a..d14ba85546 100644
--- a/src/nvim/README.md
+++ b/src/nvim/README.md
@@ -23,14 +23,18 @@ Logs
Low-level log messages sink to `$NVIM_LOG_FILE`.
-Use `LOG_CALLSTACK()` (Linux only) to log the current stacktrace. To log to an
-alternate file (e.g. stderr) use `LOG_CALLSTACK_TO_FILE(FILE*)`.
-
UI events are logged at DEBUG level (`DEBUG_LOG_LEVEL`).
rm -rf build/
make CMAKE_EXTRA_FLAGS="-DMIN_LOG_LEVEL=0"
+Use `LOG_CALLSTACK()` (Linux only) to log the current stacktrace. To log to an
+alternate file (e.g. stderr) use `LOG_CALLSTACK_TO_FILE(FILE*)`. Requires
+`-no-pie` ([ref](https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=860394#15)):
+
+ rm -rf build/
+ make CMAKE_EXTRA_FLAGS="-DMIN_LOG_LEVEL=0 -DCMAKE_C_FLAGS=-no-pie"
+
Many log messages have a shared prefix, such as "UI" or "RPC". Use the shell to
filter the log, e.g. at DEBUG level you might want to exclude UI messages:
diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c
index 3e1209d1b1..8e61976c4b 100644
--- a/src/nvim/api/buffer.c
+++ b/src/nvim/api/buffer.c
@@ -23,7 +23,10 @@
#include "nvim/memory.h"
#include "nvim/misc1.h"
#include "nvim/ex_cmds.h"
+#include "nvim/map_defs.h"
+#include "nvim/map.h"
#include "nvim/mark.h"
+#include "nvim/extmark.h"
#include "nvim/fileio.h"
#include "nvim/move.h"
#include "nvim/syntax.h"
@@ -39,6 +42,8 @@
/// \defgroup api-buffer
///
+/// \brief For more information on buffers, see |buffers|
+///
/// Unloaded Buffers:~
///
/// Buffers may be unloaded by the |:bunload| command or the buffer's
@@ -101,25 +106,50 @@ String buffer_get_line(Buffer buffer, Integer index, Error *err)
return rv;
}
-/// Activates buffer-update events on a channel, or as lua callbacks.
+/// 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>
+/// events = {}
+/// vim.api.nvim_buf_attach(0, false, {
+/// on_lines=function(...) table.insert(events, {...}) end})
+/// </pre>
+///
+/// @see |nvim_buf_detach()|
+/// @see |api-buffer-updates-lua|
///
/// @param channel_id
/// @param buffer Buffer handle, or 0 for current buffer
-/// @param send_buffer Set to true if the initial notification should contain
-/// the whole buffer. If so, the first notification will be a
-/// `nvim_buf_lines_event`. Otherwise, the first notification will be
-/// a `nvim_buf_changedtick_event`. Not used for lua callbacks.
+/// @param send_buffer 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 Optional parameters.
-/// - `on_lines`: lua callback received on change.
-/// - `on_changedtick`: lua callback received on changedtick
-/// increment without text change.
-/// - `utf_sizes`: include UTF-32 and UTF-16 size of
-/// the replaced region.
-/// See |api-buffer-updates-lua| for more information
+/// - 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_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
+/// - utf_sizes: include UTF-32 and UTF-16 size of the replaced
+/// region, as args to `on_lines`.
/// @param[out] err Error details, if any
-/// @return False when updates couldn't be enabled because the buffer isn't
-/// loaded or `opts` contained an invalid key; otherwise True.
-/// TODO: LUA_API_NO_EVAL
+/// @return False if attach failed (invalid parameter, or buffer isn't loaded);
+/// otherwise True. TODO: LUA_API_NO_EVAL
Boolean nvim_buf_attach(uint64_t channel_id,
Buffer buffer,
Boolean send_buffer,
@@ -144,21 +174,29 @@ Boolean nvim_buf_attach(uint64_t channel_id,
goto error;
}
cb.on_lines = v->data.luaref;
- v->data.integer = LUA_NOREF;
+ v->data.luaref = LUA_NOREF;
+ } else if (is_lua && strequal("_on_bytes", k.data)) {
+ // NB: undocumented, untested and incomplete interface!
+ if (v->type != kObjectTypeLuaRef) {
+ api_set_error(err, kErrorTypeValidation, "callback is not a function");
+ goto error;
+ }
+ cb.on_bytes = v->data.luaref;
+ v->data.luaref = LUA_NOREF;
} else if (is_lua && strequal("on_changedtick", k.data)) {
if (v->type != kObjectTypeLuaRef) {
api_set_error(err, kErrorTypeValidation, "callback is not a function");
goto error;
}
cb.on_changedtick = v->data.luaref;
- v->data.integer = LUA_NOREF;
+ v->data.luaref = LUA_NOREF;
} else if (is_lua && strequal("on_detach", k.data)) {
if (v->type != kObjectTypeLuaRef) {
api_set_error(err, kErrorTypeValidation, "callback is not a function");
goto error;
}
cb.on_detach = v->data.luaref;
- v->data.integer = LUA_NOREF;
+ v->data.luaref = LUA_NOREF;
} else if (is_lua && strequal("utf_sizes", k.data)) {
if (v->type != kObjectTypeBoolean) {
api_set_error(err, kErrorTypeValidation, "utf_sizes must be boolean");
@@ -176,6 +214,7 @@ Boolean nvim_buf_attach(uint64_t channel_id,
error:
// TODO(bfredl): ASAN build should check that the ref table is empty?
executor_free_luaref(cb.on_lines);
+ executor_free_luaref(cb.on_bytes);
executor_free_luaref(cb.on_changedtick);
executor_free_luaref(cb.on_detach);
return false;
@@ -183,13 +222,14 @@ error:
/// Deactivates buffer-update events on the channel.
///
-/// For Lua callbacks see |api-lua-detach|.
+/// @see |nvim_buf_attach()|
+/// @see |api-lua-detach| for detaching Lua callbacks
///
/// @param channel_id
/// @param buffer Buffer handle, or 0 for current buffer
/// @param[out] err Error details, if any
-/// @return False when updates couldn't be disabled because the buffer
-/// isn't loaded; otherwise True.
+/// @return False if detach failed (because the buffer isn't loaded);
+/// otherwise True.
Boolean nvim_buf_detach(uint64_t channel_id,
Buffer buffer,
Error *err)
@@ -205,6 +245,90 @@ Boolean nvim_buf_detach(uint64_t channel_id,
return true;
}
+static void buf_clear_luahl(buf_T *buf, bool force)
+{
+ if (buf->b_luahl || force) {
+ executor_free_luaref(buf->b_luahl_start);
+ executor_free_luaref(buf->b_luahl_window);
+ executor_free_luaref(buf->b_luahl_line);
+ executor_free_luaref(buf->b_luahl_end);
+ }
+ buf->b_luahl_start = LUA_NOREF;
+ buf->b_luahl_window = LUA_NOREF;
+ buf->b_luahl_line = LUA_NOREF;
+ buf->b_luahl_end = LUA_NOREF;
+}
+
+/// Unstabilized interface for defining syntax hl in lua.
+///
+/// This is not yet safe for general use, lua callbacks will need to
+/// be restricted, like textlock and probably other stuff.
+///
+/// The API on_line/nvim__put_attr is quite raw and not intended to be the
+/// final shape. Ideally this should operate on chunks larger than a single
+/// line to reduce interpreter overhead, and generate annotation objects
+/// (bufhl/virttext) on the fly but using the same representation.
+void nvim__buf_set_luahl(uint64_t channel_id, Buffer buffer,
+ DictionaryOf(LuaRef) opts, Error *err)
+ FUNC_API_LUA_ONLY
+{
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+
+ if (!buf) {
+ return;
+ }
+
+ redraw_buf_later(buf, NOT_VALID);
+ buf_clear_luahl(buf, false);
+
+ for (size_t i = 0; i < opts.size; i++) {
+ String k = opts.items[i].key;
+ Object *v = &opts.items[i].value;
+ if (strequal("on_start", k.data)) {
+ if (v->type != kObjectTypeLuaRef) {
+ api_set_error(err, kErrorTypeValidation, "callback is not a function");
+ goto error;
+ }
+ buf->b_luahl_start = v->data.luaref;
+ v->data.luaref = LUA_NOREF;
+ } else if (strequal("on_window", k.data)) {
+ if (v->type != kObjectTypeLuaRef) {
+ api_set_error(err, kErrorTypeValidation, "callback is not a function");
+ goto error;
+ }
+ buf->b_luahl_window = v->data.luaref;
+ v->data.luaref = LUA_NOREF;
+ } else if (strequal("on_line", k.data)) {
+ if (v->type != kObjectTypeLuaRef) {
+ api_set_error(err, kErrorTypeValidation, "callback is not a function");
+ goto error;
+ }
+ buf->b_luahl_line = v->data.luaref;
+ v->data.luaref = LUA_NOREF;
+ } else {
+ api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data);
+ goto error;
+ }
+ }
+ buf->b_luahl = true;
+ return;
+error:
+ buf_clear_luahl(buf, true);
+ buf->b_luahl = false;
+}
+
+void nvim__buf_redraw_range(Buffer buffer, Integer first, Integer last,
+ Error *err)
+ FUNC_API_LUA_ONLY
+{
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+ if (!buf) {
+ return;
+ }
+
+ redraw_buf_range_later(buf, (linenr_T)first+1, (linenr_T)last);
+}
+
/// Sets a buffer line
///
/// @deprecated use nvim_buf_set_lines instead.
@@ -529,7 +653,7 @@ void nvim_buf_set_lines(uint64_t channel_id,
(linenr_T)(end - 1),
MAXLNUM,
(long)extra,
- false);
+ kExtmarkUndo);
changed_lines((linenr_T)start, 0, (linenr_T)end, (long)extra, true);
fix_cursor((linenr_T)start, (linenr_T)end, (linenr_T)extra);
@@ -984,6 +1108,247 @@ ArrayOf(Integer, 2) nvim_buf_get_mark(Buffer buffer, String name, Error *err)
return rv;
}
+/// Returns position for a given extmark id
+///
+/// @param buffer Buffer handle, or 0 for current buffer
+/// @param ns_id Namespace id from |nvim_create_namespace()|
+/// @param id Extmark id
+/// @param[out] err Error details, if any
+/// @return (row, col) tuple or empty list () if extmark id was absent
+ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id,
+ Integer id, Error *err)
+ FUNC_API_SINCE(7)
+{
+ Array rv = ARRAY_DICT_INIT;
+
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+
+ if (!buf) {
+ return rv;
+ }
+
+ if (!ns_initialized((uint64_t)ns_id)) {
+ api_set_error(err, kErrorTypeValidation, "Invalid ns_id");
+ return rv;
+ }
+
+ ExtmarkInfo extmark = extmark_from_id(buf, (uint64_t)ns_id, (uint64_t)id);
+ if (extmark.row < 0) {
+ return rv;
+ }
+ ADD(rv, INTEGER_OBJ((Integer)extmark.row));
+ ADD(rv, INTEGER_OBJ((Integer)extmark.col));
+ return rv;
+}
+
+/// Gets extmarks 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>
+/// nvim_buf_get_extmarks(0, my_ns, 0, -1, {})
+/// nvim_buf_get_extmarks(0, my_ns, [0,0], [-1,-1], {})
+/// </pre>
+///
+/// If `end` is less than `start`, traversal works backwards. (Useful
+/// with `limit`, to get the first marks prior to a given position.)
+///
+/// Example:
+///
+/// <pre>
+/// 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, 0, {})
+/// -- Create new extmark at line 3, column 1.
+/// local m2 = a.nvim_buf_set_extmark(0, ns, 0, 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>
+///
+/// @param buffer Buffer handle, or 0 for current buffer
+/// @param ns_id Namespace id from |nvim_create_namespace()|
+/// @param start Start of range, given as (row, col) or valid extmark id
+/// (whose position defines the bound)
+/// @param end End of range, given as (row, col) or valid extmark id
+/// (whose position defines the bound)
+/// @param opts Optional parameters. Keys:
+/// - limit: Maximum number of marks to return
+/// @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)
+ FUNC_API_SINCE(7)
+{
+ Array rv = ARRAY_DICT_INIT;
+
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+ if (!buf) {
+ return rv;
+ }
+
+ if (!ns_initialized((uint64_t)ns_id)) {
+ api_set_error(err, kErrorTypeValidation, "Invalid ns_id");
+ return rv;
+ }
+ Integer limit = -1;
+
+ 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 {
+ api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data);
+ return rv;
+ }
+ }
+
+ if (limit == 0) {
+ return rv;
+ } else if (limit < 0) {
+ limit = INT64_MAX;
+ }
+
+
+ bool reverse = false;
+
+ int l_row;
+ colnr_T l_col;
+ if (!extmark_get_index_from_obj(buf, ns_id, start, &l_row, &l_col, err)) {
+ return rv;
+ }
+
+ int u_row;
+ colnr_T u_col;
+ if (!extmark_get_index_from_obj(buf, ns_id, end, &u_row, &u_col, err)) {
+ return rv;
+ }
+
+ if (l_row > u_row || (l_row == u_row && l_col > u_col)) {
+ reverse = true;
+ }
+
+
+ ExtmarkArray marks = extmark_get(buf, (uint64_t)ns_id, l_row, l_col, u_row,
+ u_col, (int64_t)limit, reverse);
+
+ for (size_t i = 0; i < kv_size(marks); i++) {
+ Array mark = ARRAY_DICT_INIT;
+ ExtmarkInfo extmark = kv_A(marks, i);
+ ADD(mark, INTEGER_OBJ((Integer)extmark.mark_id));
+ ADD(mark, INTEGER_OBJ(extmark.row));
+ ADD(mark, INTEGER_OBJ(extmark.col));
+ ADD(rv, ARRAY_OBJ(mark));
+ }
+
+ kv_destroy(marks);
+ return rv;
+}
+
+/// Creates or updates an extmark.
+///
+/// To create a new extmark, pass id=0. The extmark id will be returned.
+// To move an existing mark, pass its id.
+///
+/// It is also allowed to create a new mark by passing in a previously unused
+/// id, but the caller must then keep track of existing and unused ids itself.
+/// (Useful over RPC, to avoid waiting for the return value.)
+///
+/// @param buffer Buffer handle, or 0 for current buffer
+/// @param ns_id Namespace id from |nvim_create_namespace()|
+/// @param id Extmark id, or 0 to create new
+/// @param line Line number where to place the mark
+/// @param col Column where to place the mark
+/// @param opts Optional parameters. Currently not used.
+/// @param[out] err Error details, if any
+/// @return Id of the created/updated extmark
+Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer id,
+ Integer line, Integer col,
+ Dictionary opts, Error *err)
+ FUNC_API_SINCE(7)
+{
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+ if (!buf) {
+ return 0;
+ }
+
+ if (!ns_initialized((uint64_t)ns_id)) {
+ api_set_error(err, kErrorTypeValidation, "Invalid ns_id");
+ return 0;
+ }
+
+ if (opts.size > 0) {
+ api_set_error(err, kErrorTypeValidation, "opts dict isn't empty");
+ return 0;
+ }
+
+ size_t len = 0;
+ if (line < 0 || line > buf->b_ml.ml_line_count) {
+ api_set_error(err, kErrorTypeValidation, "line value outside range");
+ return 0;
+ } else if (line < buf->b_ml.ml_line_count) {
+ len = STRLEN(ml_get_buf(buf, (linenr_T)line+1, false));
+ }
+
+ if (col == -1) {
+ col = (Integer)len;
+ } else if (col < -1 || col > (Integer)len) {
+ api_set_error(err, kErrorTypeValidation, "col value outside range");
+ return 0;
+ }
+
+ uint64_t id_num;
+ if (id >= 0) {
+ id_num = (uint64_t)id;
+ } else {
+ api_set_error(err, kErrorTypeValidation, "Invalid mark id");
+ return 0;
+ }
+
+ id_num = extmark_set(buf, (uint64_t)ns_id, id_num,
+ (int)line, (colnr_T)col, kExtmarkUndo);
+
+ return (Integer)id_num;
+}
+
+/// Removes an extmark.
+///
+/// @param buffer Buffer handle, or 0 for current buffer
+/// @param ns_id Namespace id from |nvim_create_namespace()|
+/// @param id Extmark id
+/// @param[out] err Error details, if any
+/// @return true if the extmark was found, else false
+Boolean nvim_buf_del_extmark(Buffer buffer,
+ Integer ns_id,
+ Integer id,
+ Error *err)
+ FUNC_API_SINCE(7)
+{
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+
+ if (!buf) {
+ return false;
+ }
+ if (!ns_initialized((uint64_t)ns_id)) {
+ api_set_error(err, kErrorTypeValidation, "Invalid ns_id");
+ return false;
+ }
+
+ return extmark_del(buf, (uint64_t)ns_id, (uint64_t)id);
+}
+
/// Adds a highlight to buffer.
///
/// Useful for plugins that dynamically generate highlights to a buffer
@@ -1015,7 +1380,7 @@ ArrayOf(Integer, 2) nvim_buf_get_mark(Buffer buffer, String name, Error *err)
/// @param[out] err Error details, if any
/// @return The ns_id that was used
Integer nvim_buf_add_highlight(Buffer buffer,
- Integer ns_id,
+ Integer src_id,
String hl_group,
Integer line,
Integer col_start,
@@ -1040,17 +1405,35 @@ Integer nvim_buf_add_highlight(Buffer buffer,
col_end = MAXCOL;
}
+ uint64_t ns_id = src2ns(&src_id);
+
+ if (!(line < buf->b_ml.ml_line_count)) {
+ // safety check, we can't add marks outside the range
+ return src_id;
+ }
+
int hlg_id = 0;
if (hl_group.size > 0) {
hlg_id = syn_check_group((char_u *)hl_group.data, (int)hl_group.size);
+ } else {
+ return src_id;
}
- ns_id = bufhl_add_hl(buf, (int)ns_id, hlg_id, (linenr_T)line+1,
- (colnr_T)col_start+1, (colnr_T)col_end);
- return ns_id;
+ int end_line = (int)line;
+ if (col_end == MAXCOL) {
+ col_end = 0;
+ end_line++;
+ }
+
+ extmark_add_decoration(buf, ns_id, hlg_id,
+ (int)line, (colnr_T)col_start,
+ end_line, (colnr_T)col_end,
+ VIRTTEXT_EMPTY);
+ return src_id;
}
-/// Clears namespaced objects, highlights and virtual text, from a line range
+/// Clears namespaced 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.
@@ -1080,8 +1463,9 @@ void nvim_buf_clear_namespace(Buffer buffer,
if (line_end < 0 || line_end > MAXLNUM) {
line_end = MAXLNUM;
}
-
- bufhl_clear_line_range(buf, (int)ns_id, (int)line_start+1, (int)line_end);
+ extmark_clear(buf, (ns_id < 0 ? 0 : (uint64_t)ns_id),
+ (int)line_start, 0,
+ (int)line_end-1, MAXCOL);
}
/// Clears highlights and virtual text from namespace and range of lines
@@ -1104,6 +1488,43 @@ void nvim_buf_clear_highlight(Buffer buffer,
nvim_buf_clear_namespace(buffer, ns_id, line_start, line_end, err);
}
+static VirtText parse_virt_text(Array chunks, Error *err)
+{
+ VirtText virt_text = KV_INITIAL_VALUE;
+ 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");
+ goto free_exit;
+ }
+ Array chunk = chunks.items[i].data.array;
+ if (chunk.size == 0 || chunk.size > 2
+ || chunk.items[0].type != kObjectTypeString
+ || (chunk.size == 2 && chunk.items[1].type != kObjectTypeString)) {
+ api_set_error(err, kErrorTypeValidation,
+ "Chunk is not an array with one or two strings");
+ goto free_exit;
+ }
+
+ String str = chunk.items[0].data.string;
+ char *text = transstr(str.size > 0 ? str.data : ""); // allocates
+
+ int hl_id = 0;
+ if (chunk.size == 2) {
+ String hl = chunk.items[1].data.string;
+ if (hl.size > 0) {
+ hl_id = syn_check_group((char_u *)hl.data, (int)hl.size);
+ }
+ }
+ kv_push(virt_text, ((VirtTextChunk){ .text = text, .hl_id = hl_id }));
+ }
+
+ return virt_text;
+
+free_exit:
+ clear_virttext(&virt_text);
+ return virt_text;
+}
+
/// Set the virtual text (annotation) for a buffer line.
///
@@ -1133,7 +1554,7 @@ void nvim_buf_clear_highlight(Buffer buffer,
/// @param[out] err Error details, if any
/// @return The ns_id that was used
Integer nvim_buf_set_virtual_text(Buffer buffer,
- Integer ns_id,
+ Integer src_id,
Integer line,
Array chunks,
Dictionary opts,
@@ -1155,41 +1576,129 @@ Integer nvim_buf_set_virtual_text(Buffer buffer,
return 0;
}
- VirtText virt_text = KV_INITIAL_VALUE;
- 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");
- goto free_exit;
- }
- Array chunk = chunks.items[i].data.array;
- if (chunk.size == 0 || chunk.size > 2
- || chunk.items[0].type != kObjectTypeString
- || (chunk.size == 2 && chunk.items[1].type != kObjectTypeString)) {
- api_set_error(err, kErrorTypeValidation,
- "Chunk is not an array with one or two strings");
- goto free_exit;
- }
+ uint64_t ns_id = src2ns(&src_id);
- String str = chunk.items[0].data.string;
- char *text = transstr(str.size > 0 ? str.data : ""); // allocates
+ VirtText virt_text = parse_virt_text(chunks, err);
+ if (ERROR_SET(err)) {
+ return 0;
+ }
- int hl_id = 0;
- if (chunk.size == 2) {
- String hl = chunk.items[1].data.string;
- if (hl.size > 0) {
- hl_id = syn_check_group((char_u *)hl.data, (int)hl.size);
- }
+
+ VirtText *existing = extmark_find_virttext(buf, (int)line, ns_id);
+
+ if (existing) {
+ clear_virttext(existing);
+ *existing = virt_text;
+ return src_id;
+ }
+
+ extmark_add_decoration(buf, ns_id, 0,
+ (int)line, 0, -1, -1,
+ virt_text);
+ return src_id;
+}
+
+/// Get the virtual text (annotation) for a buffer line.
+///
+/// The virtual text is returned as list of lists, whereas the inner lists have
+/// either one or two elements. The first element is the actual text, the
+/// optional second element is the highlight group.
+///
+/// The format is exactly the same as given to nvim_buf_set_virtual_text().
+///
+/// If there is no virtual text associated with the given line, an empty list
+/// is returned.
+///
+/// @param buffer Buffer handle, or 0 for current buffer
+/// @param line Line to get the virtual text from (zero-indexed)
+/// @param[out] err Error details, if any
+/// @return List of virtual text chunks
+Array nvim_buf_get_virtual_text(Buffer buffer, Integer line, Error *err)
+ FUNC_API_SINCE(7)
+{
+ Array chunks = ARRAY_DICT_INIT;
+
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+ if (!buf) {
+ return chunks;
+ }
+
+ if (line < 0 || line >= MAXLNUM) {
+ api_set_error(err, kErrorTypeValidation, "Line number outside range");
+ return chunks;
+ }
+
+ VirtText *virt_text = extmark_find_virttext(buf, (int)line, 0);
+
+ if (!virt_text) {
+ return chunks;
+ }
+
+ for (size_t i = 0; i < virt_text->size; i++) {
+ Array chunk = ARRAY_DICT_INIT;
+ VirtTextChunk *vtc = &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))));
}
- kv_push(virt_text, ((VirtTextChunk){ .text = text, .hl_id = hl_id }));
+ ADD(chunks, ARRAY_OBJ(chunk));
+ }
+
+ return chunks;
+}
+
+Integer nvim__buf_add_decoration(Buffer buffer, Integer ns_id, String hl_group,
+ Integer start_row, Integer start_col,
+ Integer end_row, Integer end_col,
+ Array virt_text,
+ Error *err)
+{
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+ if (!buf) {
+ return 0;
+ }
+
+ if (!ns_initialized((uint64_t)ns_id)) {
+ api_set_error(err, kErrorTypeValidation, "Invalid ns_id");
+ return 0;
}
- ns_id = bufhl_add_virt_text(buf, (int)ns_id, (linenr_T)line+1,
- virt_text);
- return ns_id;
-free_exit:
- kv_destroy(virt_text);
- return 0;
+ if (start_row < 0 || start_row >= MAXLNUM || end_row > MAXCOL) {
+ api_set_error(err, kErrorTypeValidation, "Line number outside range");
+ return 0;
+ }
+
+ if (start_col < 0 || start_col > MAXCOL || end_col > MAXCOL) {
+ api_set_error(err, kErrorTypeValidation, "Column value outside range");
+ return 0;
+ }
+ if (end_row < 0 || end_col < 0) {
+ end_row = -1;
+ end_col = -1;
+ }
+
+ if (start_row >= buf->b_ml.ml_line_count
+ || end_row >= buf->b_ml.ml_line_count) {
+ // safety check, we can't add marks outside the range
+ return 0;
+ }
+
+ int hlg_id = 0;
+ if (hl_group.size > 0) {
+ hlg_id = syn_check_group((char_u *)hl_group.data, (int)hl_group.size);
+ }
+
+ VirtText vt = parse_virt_text(virt_text, err);
+ if (ERROR_SET(err)) {
+ return 0;
+ }
+
+ uint64_t mark_id = extmark_add_decoration(buf, (uint64_t)ns_id, hlg_id,
+ (int)start_row, (colnr_T)start_col,
+ (int)end_row, (colnr_T)end_col, vt);
+ return (Integer)mark_id;
}
Dictionary nvim__buf_stats(Buffer buffer, Error *err)
@@ -1213,6 +1722,16 @@ Dictionary nvim__buf_stats(Buffer buffer, Error *err)
// this exists to debug issues
PUT(rv, "dirty_bytes", INTEGER_OBJ((Integer)buf->deleted_bytes));
+ u_header_T *uhp = NULL;
+ if (buf->b_u_curhead != NULL) {
+ uhp = buf->b_u_curhead;
+ } else if (buf->b_u_newhead) {
+ uhp = buf->b_u_newhead;
+ }
+ if (uhp) {
+ PUT(rv, "uhp_extmark_size", INTEGER_OBJ((Integer)kv_size(uhp->uh_extmark)));
+ }
+
return rv;
}
diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index 2056cb07e3..f194b6b474 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -10,6 +10,7 @@
#include "nvim/api/private/helpers.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/handle.h"
+#include "nvim/api/vim.h"
#include "nvim/msgpack_rpc/helpers.h"
#include "nvim/lua/executor.h"
#include "nvim/ascii.h"
@@ -23,6 +24,7 @@
#include "nvim/eval/typval.h"
#include "nvim/map_defs.h"
#include "nvim/map.h"
+#include "nvim/extmark.h"
#include "nvim/option.h"
#include "nvim/option_defs.h"
#include "nvim/version.h"
@@ -629,7 +631,7 @@ buf_T *find_buffer_by_handle(Buffer buffer, Error *err)
buf_T *rv = handle_get_buffer(buffer);
if (!rv) {
- api_set_error(err, kErrorTypeValidation, "Invalid buffer id");
+ api_set_error(err, kErrorTypeValidation, "Invalid buffer id: %d", buffer);
}
return rv;
@@ -644,7 +646,7 @@ win_T *find_window_by_handle(Window window, Error *err)
win_T *rv = handle_get_window(window);
if (!rv) {
- api_set_error(err, kErrorTypeValidation, "Invalid window id");
+ api_set_error(err, kErrorTypeValidation, "Invalid window id: %d", window);
}
return rv;
@@ -659,7 +661,7 @@ tabpage_T *find_tab_by_handle(Tabpage tabpage, Error *err)
tabpage_T *rv = handle_get_tabpage(tabpage);
if (!rv) {
- api_set_error(err, kErrorTypeValidation, "Invalid tabpage id");
+ api_set_error(err, kErrorTypeValidation, "Invalid tabpage id: %d", tabpage);
}
return rv;
@@ -1073,8 +1075,8 @@ bool object_to_vim(Object obj, typval_T *tv, Error *err)
break;
case kObjectTypeBoolean:
- tv->v_type = VAR_SPECIAL;
- tv->vval.v_special = obj.data.boolean? kSpecialVarTrue: kSpecialVarFalse;
+ tv->v_type = VAR_BOOL;
+ tv->vval.v_bool = obj.data.boolean? kBoolVarTrue: kBoolVarFalse;
break;
case kObjectTypeBuffer:
@@ -1363,6 +1365,9 @@ Dictionary copy_dictionary(Dictionary dict)
Object copy_object(Object obj)
{
switch (obj.type) {
+ case kObjectTypeBuffer:
+ case kObjectTypeTabpage:
+ case kObjectTypeWindow:
case kObjectTypeNil:
case kObjectTypeBoolean:
case kObjectTypeInteger:
@@ -1505,3 +1510,72 @@ ArrayOf(Dictionary) keymap_array(String mode, buf_T *buf)
return mappings;
}
+
+// Is the Namespace in use?
+bool ns_initialized(uint64_t ns)
+{
+ if (ns < 1) {
+ return false;
+ }
+ return ns < (uint64_t)next_namespace_id;
+}
+
+/// Gets the line and column of an extmark.
+///
+/// Extmarks may be queried by position, name or even special names
+/// in the future such as "cursor".
+///
+/// @param[out] lnum extmark line
+/// @param[out] colnr extmark column
+///
+/// @return true if the extmark was found, else false
+bool extmark_get_index_from_obj(buf_T *buf, Integer ns_id, Object obj, int
+ *row, colnr_T *col, Error *err)
+{
+ // Check if it is mark id
+ if (obj.type == kObjectTypeInteger) {
+ Integer id = obj.data.integer;
+ if (id == 0) {
+ *row = 0;
+ *col = 0;
+ return true;
+ } else if (id == -1) {
+ *row = MAXLNUM;
+ *col = MAXCOL;
+ return true;
+ } else if (id < 0) {
+ api_set_error(err, kErrorTypeValidation, "Mark id must be positive");
+ return false;
+ }
+
+ ExtmarkInfo extmark = extmark_from_id(buf, (uint64_t)ns_id, (uint64_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");
+ return false;
+ }
+
+ // 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");
+ 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);
+ *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;
+ }
+}
diff --git a/src/nvim/api/private/helpers.h b/src/nvim/api/private/helpers.h
index 0ea7667428..df3a263dcf 100644
--- a/src/nvim/api/private/helpers.h
+++ b/src/nvim/api/private/helpers.h
@@ -60,6 +60,12 @@
#define ADD(array, item) \
kv_push(array, item)
+#define FIXED_TEMP_ARRAY(name, fixsize) \
+ Array name = ARRAY_DICT_INIT; \
+ Object name##__items[fixsize]; \
+ name.size = fixsize; \
+ name.items = name##__items; \
+
#define STATIC_CSTR_AS_STRING(s) ((String) {.data = s, .size = sizeof(s) - 1})
/// Create a new String instance, putting data in allocated memory
@@ -102,6 +108,20 @@ typedef struct {
int did_emsg;
} TryState;
+// `msg_list` controls the collection of abort-causing non-exception errors,
+// 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) \
+ do { \
+ struct msglist **saved_msg_list = msg_list; \
+ struct msglist *private_msg_list; \
+ msg_list = &private_msg_list; \
+ private_msg_list = NULL; \
+ code \
+ msg_list = saved_msg_list; /* Restore the exception context. */ \
+ } while (0)
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/private/helpers.h.generated.h"
#endif
diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c
index 75ee05761b..717713b948 100644
--- a/src/nvim/api/ui.c
+++ b/src/nvim/api/ui.c
@@ -109,7 +109,12 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height,
UI *ui = xcalloc(1, sizeof(UI));
ui->width = (int)width;
ui->height = (int)height;
- ui->pum_height = 0;
+ ui->pum_nlines = 0;
+ ui->pum_pos = false;
+ ui->pum_width = 0.0;
+ ui->pum_height = 0.0;
+ ui->pum_row = -1.0;
+ ui->pum_col = -1.0;
ui->rgb = true;
ui->override = false;
ui->grid_resize = remote_ui_grid_resize;
@@ -340,7 +345,56 @@ void nvim_ui_pum_set_height(uint64_t channel_id, Integer height, Error *err)
"It must support the ext_popupmenu option");
return;
}
- ui->pum_height = (int)height;
+
+ ui->pum_nlines = (int)height;
+}
+
+/// Tells Nvim the geometry of the popumenu, to align floating windows with an
+/// external popup menu.
+///
+/// Note that this method is not to be confused with |nvim_ui_pum_set_height()|,
+/// which sets the number of visible items in the popup menu, while this
+/// function sets the bounding box of the popup menu, including visual
+/// decorations such as boarders and sliders. Floats need not use the same font
+/// size, nor be anchored to exact grid corners, so one can set floating-point
+/// numbers to the popup menu geometry.
+///
+/// @param channel_id
+/// @param width Popupmenu width.
+/// @param height Popupmenu height.
+/// @param row Popupmenu row.
+/// @param col Popupmenu height.
+/// @param[out] err Error details, if any.
+void nvim_ui_pum_set_bounds(uint64_t channel_id, Float width, Float height,
+ Float row, Float col, Error *err)
+ FUNC_API_SINCE(7) FUNC_API_REMOTE_ONLY
+{
+ if (!pmap_has(uint64_t)(connected_uis, channel_id)) {
+ api_set_error(err, kErrorTypeException,
+ "UI not attached to channel: %" PRId64, channel_id);
+ return;
+ }
+
+ UI *ui = pmap_get(uint64_t)(connected_uis, channel_id);
+ if (!ui->ui_ext[kUIPopupmenu]) {
+ api_set_error(err, kErrorTypeValidation,
+ "UI must support the ext_popupmenu option");
+ return;
+ }
+
+ if (width <= 0) {
+ api_set_error(err, kErrorTypeValidation, "Expected width > 0");
+ return;
+ } else if (height <= 0) {
+ api_set_error(err, kErrorTypeValidation, "Expected height > 0");
+ return;
+ }
+
+ ui->pum_row = (double)row;
+ ui->pum_col = (double)col;
+ ui->pum_width = (double)width;
+ ui->pum_height = (double)height;
+ ui->pum_pos = true;
}
/// Pushes data into UI.UIData, to be consumed later by remote_ui_flush().
diff --git a/src/nvim/api/ui_events.in.h b/src/nvim/api/ui_events.in.h
index 6677e248cf..ab31db39e9 100644
--- a/src/nvim/api/ui_events.in.h
+++ b/src/nvim/api/ui_events.in.h
@@ -115,6 +115,10 @@ void win_close(Integer grid)
void msg_set_pos(Integer grid, Integer row, Boolean scrolled, String sep_char)
FUNC_API_SINCE(6) FUNC_API_BRIDGE_IMPL FUNC_API_COMPOSITOR_IMPL;
+void win_viewport(Integer grid, Window win, Integer topline,
+ Integer botline, Integer curline, Integer curcol)
+ FUNC_API_SINCE(7) FUNC_API_REMOTE_ONLY;
+
void popupmenu_show(Array items, Integer selected,
Integer row, Integer col, Integer grid)
FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY;
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index 602733fd31..d6f95c7a5f 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -25,6 +25,7 @@
#include "nvim/highlight.h"
#include "nvim/window.h"
#include "nvim/types.h"
+#include "nvim/ex_cmds2.h"
#include "nvim/ex_docmd.h"
#include "nvim/screen.h"
#include "nvim/memline.h"
@@ -35,10 +36,12 @@
#include "nvim/edit.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
+#include "nvim/eval/userfunc.h"
#include "nvim/fileio.h"
#include "nvim/ops.h"
#include "nvim/option.h"
#include "nvim/state.h"
+#include "nvim/extmark.h"
#include "nvim/syntax.h"
#include "nvim/getchar.h"
#include "nvim/os/input.h"
@@ -53,20 +56,6 @@
# include "api/vim.c.generated.h"
#endif
-// `msg_list` controls the collection of abort-causing non-exception errors,
-// 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) \
- do { \
- struct msglist **saved_msg_list = msg_list; \
- struct msglist *private_msg_list; \
- msg_list = &private_msg_list; \
- private_msg_list = NULL; \
- code \
- msg_list = saved_msg_list; /* Restore the exception context. */ \
- } while (0)
-
void api_vim_init(void)
FUNC_API_NOEXPORT
{
@@ -85,10 +74,70 @@ void api_vim_free_all_mem(void)
map_free(String, handle_T)(namespace_ids);
}
+/// 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, does not update v:errmsg.
+///
+/// @see |execute()|
+/// @see |nvim_command()|
+///
+/// @param src Vimscript code
+/// @param output 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(String src, Boolean output, Error *err)
+ FUNC_API_SINCE(7)
+{
+ const int save_msg_silent = msg_silent;
+ garray_T *const save_capture_ga = capture_ga;
+ garray_T capture_local;
+ if (output) {
+ ga_init(&capture_local, 1, 80);
+ capture_ga = &capture_local;
+ }
+
+ try_start();
+ msg_silent++;
+ do_source_str(src.data, "nvim_exec()");
+ capture_ga = save_capture_ga;
+ msg_silent = save_msg_silent;
+ try_end(err);
+
+ if (ERROR_SET(err)) {
+ goto theend;
+ }
+
+ if (output && capture_local.ga_len > 1) {
+ String s = (String){
+ .data = capture_local.ga_data,
+ .size = (size_t)capture_local.ga_len,
+ };
+ // redir usually (except :echon) prepends a newline.
+ if (s.data[0] == '\n') {
+ memmove(s.data, s.data + 1, s.size - 1);
+ s.data[s.size - 1] = '\0';
+ s.size = s.size - 1;
+ }
+ return s; // Caller will free the memory.
+ }
+theend:
+ if (output) {
+ ga_clear(&capture_local);
+ }
+ return (String)STRING_INIT;
+}
+
/// Executes an ex-command.
///
/// On execution error: fails with VimL error, does not update v:errmsg.
///
+/// @see |nvim_exec()|
+///
/// @param command Ex-command string
/// @param[out] err Error details (Vim error), if any
void nvim_command(String command, Error *err)
@@ -141,10 +190,29 @@ Dictionary nvim_get_hl_by_id(Integer hl_id, Boolean rgb, Error *err)
return hl_get_attr_by_id(attrcode, rgb, err);
}
+/// Gets a highlight group by name
+///
+/// similar to |hlID()|, but allocates a new ID if not present.
+Integer nvim_get_hl_id_by_name(String name)
+ FUNC_API_SINCE(7)
+{
+ return syn_check_group((const char_u *)name.data, (int)name.size);
+}
+
/// 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.
+//
+// If you need to input sequences like <C-o> use nvim_replace_termcodes
+// to replace the termcodes and then pass the resulting string to
+// nvim_feedkeys. You'll also want to enable escape_csi.
+///
+/// Example:
+/// <pre>
+/// :let key = nvim_replace_termcodes("<C-o>", v:true, v:false, v:true)
+/// :call nvim_feedkeys(key, 'n', v:true)
+/// </pre>
///
/// @param keys to be typed
/// @param mode behavior flags, see |feedkeys()|
@@ -184,19 +252,19 @@ void nvim_feedkeys(String keys, String mode, Boolean escape_csi)
keys_esc = keys.data;
}
ins_typebuf((char_u *)keys_esc, (remap ? REMAP_YES : REMAP_NONE),
- insert ? 0 : typebuf.tb_len, !typed, false);
+ insert ? 0 : typebuf.tb_len, !typed, false);
+ if (vgetc_busy) {
+ typebuf_was_filled = true;
+ }
if (escape_csi) {
xfree(keys_esc);
}
- if (vgetc_busy) {
- typebuf_was_filled = true;
- }
if (execute) {
int save_msg_scroll = msg_scroll;
- /* Avoid a 1 second delay when the keys start Insert mode. */
+ // Avoid a 1 second delay when the keys start Insert mode.
msg_scroll = false;
if (!dangerous) {
ex_normal_busy++;
@@ -278,7 +346,7 @@ void nvim_input_mouse(String button, String action, String modifier,
if (strequal(action.data, "down")) {
code = KE_MOUSEUP;
} else if (strequal(action.data, "up")) {
- code = KE_MOUSEDOWN;
+ // code = KE_MOUSEDOWN
} else if (strequal(action.data, "left")) {
code = KE_MOUSERIGHT;
} else if (strequal(action.data, "right")) {
@@ -345,53 +413,16 @@ String nvim_replace_termcodes(String str, Boolean from_part, Boolean do_lt,
return cstr_as_string(ptr);
}
-/// Executes an ex-command and returns its (non-error) output.
-/// Shell |:!| output is not captured.
-///
-/// On execution error: fails with VimL error, does not update v:errmsg.
-///
-/// @param command Ex-command string
-/// @param[out] err Error details (Vim error), if any
+/// @deprecated
+/// @see nvim_exec
String nvim_command_output(String command, Error *err)
FUNC_API_SINCE(1)
+ FUNC_API_DEPRECATED_SINCE(7)
{
- const int save_msg_silent = msg_silent;
- garray_T *const save_capture_ga = capture_ga;
- garray_T capture_local;
- ga_init(&capture_local, 1, 80);
-
- try_start();
- msg_silent++;
- capture_ga = &capture_local;
- do_cmdline_cmd(command.data);
- capture_ga = save_capture_ga;
- msg_silent = save_msg_silent;
- try_end(err);
-
- if (ERROR_SET(err)) {
- goto theend;
- }
-
- if (capture_local.ga_len > 1) {
- String s = (String){
- .data = capture_local.ga_data,
- .size = (size_t)capture_local.ga_len,
- };
- // redir usually (except :echon) prepends a newline.
- if (s.data[0] == '\n') {
- memmove(s.data, s.data + 1, s.size - 1);
- s.data[s.size - 1] = '\0';
- s.size = s.size - 1;
- }
- return s; // Caller will free the memory.
- }
-
-theend:
- ga_clear(&capture_local);
- return (String)STRING_INIT;
+ return nvim_exec(command, true, err);
}
-/// Evaluates a VimL expression (:help expression).
+/// Evaluates a VimL |expression|.
/// Dictionaries and Lists are recursively expanded.
///
/// On execution error: fails with VimL error, does not update v:errmsg.
@@ -423,7 +454,8 @@ Object nvim_eval(String expr, Error *err)
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");
+ api_set_error(err, kErrorTypeException,
+ "Failed to evaluate expression: '%.*s'", 256, expr.data);
} else {
rv = vim_to_object(&rettv);
}
@@ -436,6 +468,16 @@ Object nvim_eval(String expr, Error *err)
return rv;
}
+/// @deprecated Use nvim_exec_lua() instead.
+/// @see nvim_exec_lua
+Object nvim_execute_lua(String code, Array args, Error *err)
+ FUNC_API_SINCE(3)
+ FUNC_API_DEPRECATED_SINCE(7)
+ FUNC_API_REMOTE_ONLY
+{
+ return executor_exec_lua_api(code, args, err);
+}
+
/// Execute Lua code. Parameters (if any) are available as `...` inside the
/// chunk. The chunk can return a value.
///
@@ -448,8 +490,9 @@ Object nvim_eval(String expr, Error *err)
/// or executing the Lua code.
///
/// @return Return value of Lua code if present or NIL.
-Object nvim_execute_lua(String code, Array args, Error *err)
- FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY
+Object nvim_exec_lua(String code, Array args, Error *err)
+ FUNC_API_SINCE(7)
+ FUNC_API_REMOTE_ONLY
{
return executor_exec_lua_api(code, args, err);
}
@@ -671,6 +714,40 @@ ArrayOf(String) nvim_list_runtime_paths(void)
return rv;
}
+/// Find files in runtime directories
+///
+/// 'name' can contain wildcards. For example
+/// nvim_get_runtime_file("colors/*.vim", true) will return all color
+/// scheme files.
+///
+/// It is not an error to not find any files. An empty array is returned then.
+///
+/// @param name pattern of files to search for
+/// @param all whether to return all matches or only the first
+/// @return list of absolute paths to the found files
+ArrayOf(String) nvim_get_runtime_file(String name, Boolean all)
+ FUNC_API_SINCE(7)
+{
+ Array rv = ARRAY_DICT_INIT;
+ if (!name.data) {
+ return rv;
+ }
+ int flags = DIP_START | (all ? DIP_ALL : 0);
+ do_in_runtimepath((char_u *)name.data, flags, find_runtime_cb, &rv);
+ return rv;
+}
+
+static void find_runtime_cb(char_u *fname, void *cookie)
+{
+ Array *rv = (Array *)cookie;
+ ADD(*rv, STRING_OBJ(cstr_to_string((char *)fname)));
+}
+
+String nvim__get_lib_dir(void)
+{
+ return cstr_as_string(get_lib_dir());
+}
+
/// Changes the global working directory.
///
/// @param dir Directory path
@@ -970,7 +1047,7 @@ void nvim_set_current_win(Window window, Error *err)
///
/// @param listed Sets 'buflisted'
/// @param scratch Creates a "throwaway" |scratch-buffer| for temporary work
-/// (always 'nomodified')
+/// (always 'nomodified'). Also sets 'nomodeline' on the buffer.
/// @param[out] err Error details, if any
/// @return Buffer handle, or 0 on error
///
@@ -1000,9 +1077,10 @@ Buffer nvim_create_buf(Boolean listed, Boolean scratch, Error *err)
if (scratch) {
aco_save_T aco;
aucmd_prepbuf(&aco, buf);
- set_option_value("bh", 0L, "hide", OPT_LOCAL);
- set_option_value("bt", 0L, "nofile", OPT_LOCAL);
- set_option_value("swf", 0L, NULL, OPT_LOCAL);
+ 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);
}
return buf->b_fnum;
@@ -1054,10 +1132,9 @@ fail:
/// @param enter Enter the window (make it the current window)
/// @param config Map defining the window configuration. Keys:
/// - `relative`: Sets the window layout to "floating", placed at (row,col)
-/// coordinates relative to one of:
+/// coordinates relative to:
/// - "editor" The global editor grid
-/// - "win" Window given by the `win` field, or current window by
-/// default.
+/// - "win" Window given by the `win` field, or current window.
/// - "cursor" Cursor position in current window.
/// - `win`: |window-ID| for relative="win".
/// - `anchor`: Decides which corner of the float to place at (row,col):
@@ -1088,9 +1165,10 @@ fail:
/// float where the text should not be edited. Disables
/// 'number', 'relativenumber', 'cursorline', 'cursorcolumn',
/// 'foldcolumn', 'spell' and 'list' options. 'signcolumn'
-/// is changed to `auto`. The end-of-buffer region is hidden
-/// by setting `eob` flag of 'fillchars' to a space char,
-/// and clearing the |EndOfBuffer| region in 'winhighlight'.
+/// is changed to `auto` and 'colorcolumn' is cleared. The
+/// end-of-buffer region is hidden by setting `eob` flag of
+/// 'fillchars' to a space char, and clearing the
+/// |EndOfBuffer| region in 'winhighlight'.
/// @param[out] err Error details, if any
///
/// @return Window handle, or 0 on error
@@ -1109,6 +1187,10 @@ Window nvim_open_win(Buffer buffer, Boolean enter, Dictionary config,
if (enter) {
win_enter(wp, false);
}
+ if (!win_valid(wp)) {
+ api_set_error(err, kErrorTypeException, "Window was closed immediately");
+ return 0;
+ }
if (buffer > 0) {
nvim_win_set_buf(wp->handle, buffer, err);
}
@@ -1260,13 +1342,13 @@ Boolean nvim_paste(String data, Boolean crlf, Integer phase, Error *err)
Array lines = string_to_array(data, crlf);
ADD(args, ARRAY_OBJ(lines));
ADD(args, INTEGER_OBJ(phase));
- rv = nvim_execute_lua(STATIC_CSTR_AS_STRING("return vim.paste(...)"), args,
- err);
+ rv = nvim_exec_lua(STATIC_CSTR_AS_STRING("return vim.paste(...)"), args,
+ err);
if (ERROR_SET(err)) {
draining = true;
goto theend;
}
- if (!(State & CMDLINE) && !(State & INSERT) && (phase == -1 || phase == 1)) {
+ if (!(State & (CMDLINE | INSERT)) && (phase == -1 || phase == 1)) {
ResetRedobuff();
AppendCharToRedobuff('a'); // Dot-repeat.
}
@@ -1284,7 +1366,7 @@ Boolean nvim_paste(String data, Boolean crlf, Integer phase, Error *err)
}
}
}
- if (!(State & CMDLINE) && !(State & INSERT) && (phase == -1 || phase == 3)) {
+ if (!(State & (CMDLINE | INSERT)) && (phase == -1 || phase == 3)) {
AppendCharToRedobuff(ESC); // Dot-repeat.
}
theend:
@@ -1304,7 +1386,7 @@ theend:
/// @param lines |readfile()|-style list of lines. |channel-lines|
/// @param type Edit behavior: any |getregtype()| result, or:
/// - "b" |blockwise-visual| mode (may include width, e.g. "b3")
-/// - "c" |characterwise| mode
+/// - "c" |charwise| mode
/// - "l" |linewise| mode
/// - "" guess by contents, see |setreg()|
/// @param after Insert after cursor (like |p|), or before (like |P|).
@@ -2395,7 +2477,7 @@ Array nvim_get_proc_children(Integer pid, Error *err)
Array a = ARRAY_DICT_INIT;
ADD(a, INTEGER_OBJ(pid));
String s = cstr_to_string("return vim._os_proc_children(select(1, ...))");
- Object o = nvim_execute_lua(s, a, err);
+ Object o = nvim_exec_lua(s, a, err);
api_free_string(s);
api_free_array(a);
if (o.type == kObjectTypeArray) {
@@ -2441,7 +2523,7 @@ Object nvim_get_proc(Integer pid, Error *err)
Array a = ARRAY_DICT_INIT;
ADD(a, INTEGER_OBJ(pid));
String s = cstr_to_string("return vim._os_proc_info(select(1, ...))");
- Object o = nvim_execute_lua(s, a, err);
+ Object o = nvim_exec_lua(s, a, err);
api_free_string(s);
api_free_array(a);
if (o.type == kObjectTypeArray && o.data.array.size == 0) {
@@ -2521,3 +2603,27 @@ Array nvim__inspect_cell(Integer grid, Integer row, Integer col, Error *err)
}
return ret;
}
+
+/// Set attrs in nvim__buf_set_lua_hl callbacks
+///
+/// TODO(bfredl): This is rather pedestrian. The final
+/// interface should probably be derived from a reformed
+/// bufhl/virttext interface with full support for multi-line
+/// ranges etc
+void nvim__put_attr(Integer id, Integer start_row, Integer start_col,
+ Integer end_row, Integer end_col)
+ FUNC_API_LUA_ONLY
+{
+ if (!lua_attr_active) {
+ return;
+ }
+ if (id == 0 || syn_get_final_id((int)id) == 0) {
+ return;
+ }
+ int attr = syn_id2attr((int)id);
+ if (attr == 0) {
+ return;
+ }
+ decorations_add_luahl_attr(attr, (int)start_row, (colnr_T)start_col,
+ (int)end_row, (colnr_T)end_col);
+}
diff --git a/src/nvim/ascii.h b/src/nvim/ascii.h
index ff6840d690..31423e79af 100644
--- a/src/nvim/ascii.h
+++ b/src/nvim/ascii.h
@@ -23,26 +23,28 @@
#define NL '\012'
#define NL_STR "\012"
#define FF '\014'
-#define CAR '\015' /* CR is used by Mac OS X */
+#define CAR '\015' // CR is used by Mac OS X
#define ESC '\033'
#define ESC_STR "\033"
#define DEL 0x7f
#define DEL_STR "\177"
#define CSI 0x9b // Control Sequence Introducer
#define CSI_STR "\233"
-#define DCS 0x90 /* Device Control String */
-#define STERM 0x9c /* String Terminator */
+#define DCS 0x90 // Device Control String
+#define DCS_STR "\033P"
+#define STERM 0x9c // String Terminator
+#define STERM_STR "\033\\"
#define POUND 0xA3
-#define Ctrl_chr(x) (TOUPPER_ASC(x) ^ 0x40) /* '?' -> DEL, '@' -> ^@, etc. */
+#define Ctrl_chr(x) (TOUPPER_ASC(x) ^ 0x40) // '?' -> DEL, '@' -> ^@, etc.
#define Meta(x) ((x) | 0x80)
#define CTRL_F_STR "\006"
#define CTRL_H_STR "\010"
#define CTRL_V_STR "\026"
-#define Ctrl_AT 0 /* @ */
+#define Ctrl_AT 0 // @
#define Ctrl_A 1
#define Ctrl_B 2
#define Ctrl_C 3
@@ -69,16 +71,14 @@
#define Ctrl_X 24
#define Ctrl_Y 25
#define Ctrl_Z 26
-/* CTRL- [ Left Square Bracket == ESC*/
-#define Ctrl_BSL 28 /* \ BackSLash */
-#define Ctrl_RSB 29 /* ] Right Square Bracket */
-#define Ctrl_HAT 30 /* ^ */
+// CTRL- [ Left Square Bracket == ESC
+#define Ctrl_BSL 28 // \ BackSLash
+#define Ctrl_RSB 29 // ] Right Square Bracket
+#define Ctrl_HAT 30 // ^
#define Ctrl__ 31
-/*
- * Character that separates dir names in a path.
- */
+// Character that separates dir names in a path.
#ifdef BACKSLASH_IN_FILENAME
# define PATHSEP psepc
# define PATHSEPSTR pseps
@@ -168,4 +168,4 @@ static inline bool ascii_isspace(int c)
return (c >= 9 && c <= 13) || c == ' ';
}
-#endif /* NVIM_ASCII_H */
+#endif // NVIM_ASCII_H
diff --git a/src/nvim/aucmd.c b/src/nvim/aucmd.c
index a9a7d834a4..32c77fa288 100644
--- a/src/nvim/aucmd.c
+++ b/src/nvim/aucmd.c
@@ -8,6 +8,8 @@
#include "nvim/ui.h"
#include "nvim/aucmd.h"
#include "nvim/eval.h"
+#include "nvim/ex_getln.h"
+#include "nvim/buffer.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "aucmd.c.generated.h"
@@ -50,12 +52,47 @@ void aucmd_schedule_focusgained(bool gained)
static void do_autocmd_focusgained(bool gained)
{
static bool recursive = false;
+ static Timestamp last_time = (time_t)0;
+ bool need_redraw = false;
if (recursive) {
return; // disallow recursion
}
recursive = true;
- apply_autocmds((gained ? EVENT_FOCUSGAINED : EVENT_FOCUSLOST),
- NULL, NULL, false, curbuf);
+ need_redraw |= 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:
+ // 1. Some filesystems have modification time granularity in seconds. Fat32
+ // 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);
+ 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 & CMDLINE) {
+ redrawcmdline();
+ } else if ((State & NORMAL) || (State & INSERT)) {
+ if (must_redraw != 0) {
+ update_screen(0);
+ }
+
+ setcursor();
+ }
+
+ ui_flush();
+ }
+
+ if (need_maketitle) {
+ maketitle();
+ }
+
recursive = false;
}
diff --git a/src/nvim/auevents.lua b/src/nvim/auevents.lua
index c223679596..4391d997a7 100644
--- a/src/nvim/auevents.lua
+++ b/src/nvim/auevents.lua
@@ -21,16 +21,17 @@ return {
'BufWritePre', -- before writing a buffer
'ChanInfo', -- info was received about channel
'ChanOpen', -- channel was opened
- 'CmdLineChanged', -- command line was modified
- 'CmdLineEnter', -- after entering cmdline mode
- 'CmdLineLeave', -- before leaving cmdline mode
'CmdUndefined', -- command undefined
'CmdWinEnter', -- after entering the cmdline window
'CmdWinLeave', -- before leaving the cmdline window
+ 'CmdlineChanged', -- command line was modified
+ 'CmdlineEnter', -- after entering cmdline mode
+ 'CmdlineLeave', -- before leaving cmdline mode
'ColorScheme', -- after loading a colorscheme
'ColorSchemePre', -- before loading a colorscheme
'CompleteChanged', -- after popup menu changed
'CompleteDone', -- after finishing insert complete
+ 'CompleteDonePre', -- idem, before clearing info
'CursorHold', -- cursor in same position for a while
'CursorHoldI', -- idem, in Insert mode
'CursorMoved', -- cursor was moved
@@ -76,8 +77,8 @@ return {
'ShellFilterPost', -- after ":1,2!cmd", ":w !cmd", ":r !cmd".
'Signal', -- after nvim process received a signal
'SourceCmd', -- sourcing a Vim script using command
- 'SourcePre', -- before sourcing a Vim script
'SourcePost', -- after sourcing a Vim script
+ 'SourcePre', -- before sourcing a Vim script
'SpellFileMissing', -- spell file missing
'StdinReadPost', -- after reading from stdin
'StdinReadPre', -- before reading from stdin
@@ -107,6 +108,7 @@ return {
'VimResized', -- after Vim window was resized
'VimResume', -- after Nvim is resumed
'VimSuspend', -- before Nvim is suspended
+ 'WinClosed', -- after closing a window
'WinEnter', -- after entering a window
'WinLeave', -- before leaving a window
'WinNew', -- when entering a new window
@@ -129,5 +131,6 @@ return {
TermOpen=true,
UIEnter=true,
UILeave=true,
+ WinClosed=true,
},
}
diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c
index b50c764ac3..b3bbdce9d9 100644
--- a/src/nvim/buffer.c
+++ b/src/nvim/buffer.c
@@ -53,6 +53,7 @@
#include "nvim/indent_c.h"
#include "nvim/main.h"
#include "nvim/mark.h"
+#include "nvim/extmark.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
@@ -80,12 +81,6 @@
#include "nvim/os/input.h"
#include "nvim/buffer_updates.h"
-typedef enum {
- kBLSUnchanged = 0,
- kBLSChanged = 1,
- kBLSDeleted = 2,
-} BufhlLineStatus;
-
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "buffer.c.generated.h"
#endif
@@ -191,14 +186,17 @@ int open_buffer(
}
}
- /*
- * if there is no memfile at all, exit
- * This is OK, since there are no changes to lose.
- */
+ // If there is no memfile at all, exit.
+ // This is OK, since there are no changes to lose.
if (curbuf == NULL) {
EMSG(_("E82: Cannot allocate any buffer, exiting..."));
+
+ // Don't try to do any saving, with "curbuf" NULL almost nothing
+ // will work.
+ v_dying = 2;
getout(2);
}
+
EMSG(_("E83: Cannot allocate buffer, using other one..."));
enter_buffer(curbuf);
if (old_tw != curbuf->b_p_tw) {
@@ -410,11 +408,11 @@ bool buf_valid(buf_T *buf)
/// caller should get a new buffer very soon!
/// The 'bufhidden' option can force freeing and deleting.
/// @param abort_if_last
-/// If TRUE, do not close the buffer if autocommands cause
+/// If true, do not close the buffer if autocommands cause
/// there to be only one window with this buffer. e.g. when
/// ":quit" is supposed to close the window but autocommands
/// close all other windows.
-void close_buffer(win_T *win, buf_T *buf, int action, int abort_if_last)
+void close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last)
{
bool unload_buf = (action != 0);
bool del_buf = (action == DOBUF_DEL || action == DOBUF_WIPE);
@@ -768,6 +766,9 @@ static void free_buffer(buf_T *buf)
unref_var_dict(buf->b_vars);
aubuflocal_remove(buf);
tv_dict_unref(buf->additional_data);
+ xfree(buf->b_prompt_text);
+ callback_free(&buf->b_prompt_callback);
+ callback_free(&buf->b_prompt_interrupt);
clear_fmark(&buf->b_last_cursor);
clear_fmark(&buf->b_last_insert);
clear_fmark(&buf->b_last_change);
@@ -816,7 +817,7 @@ static void free_buffer_stuff(buf_T *buf, int free_flags)
}
uc_clear(&buf->b_ucmds); // clear local user commands
buf_delete_signs(buf, (char_u *)"*"); // delete any signs
- bufhl_clear_all(buf); // delete any highligts
+ extmark_free_all(buf); // delete any extmarks
map_clear_int(buf, MAP_ALL_MODES, true, false); // clear local mappings
map_clear_int(buf, MAP_ALL_MODES, true, true); // clear local abbrevs
XFREE_CLEAR(buf->b_start_fenc);
@@ -929,7 +930,7 @@ void handle_swap_exists(bufref_T *old_curbuf)
// User selected Recover at ATTENTION prompt.
msg_scroll = true;
- ml_recover();
+ ml_recover(false);
MSG_PUTS("\n"); // don't overwrite the last message
cmdline_row = msg_row;
do_modelines(0);
@@ -1236,7 +1237,7 @@ do_buffer(
return FAIL;
}
} else {
- EMSG2(_("E89: %s will be killed(add ! to override)"),
+ EMSG2(_("E89: %s will be killed (add ! to override)"),
(char *)buf->b_fname);
return FAIL;
}
@@ -1583,10 +1584,12 @@ void enter_buffer(buf_T *buf)
open_buffer(false, NULL, 0);
} else {
- if (!msg_silent) {
+ if (!msg_silent && !shortmess(SHM_FILEINFO)) {
need_fileinfo = true; // display file info after redraw
}
- (void)buf_check_timestamp(curbuf, false); // check if file changed
+ // check if file changed
+ (void)buf_check_timestamp(curbuf, false);
+
curwin->w_topline = 1;
curwin->w_topfill = 0;
apply_autocmds(EVENT_BUFENTER, NULL, NULL, false, curbuf);
@@ -1618,6 +1621,7 @@ void enter_buffer(buf_T *buf)
if (!curbuf->b_help && curwin->w_p_spell && *curwin->w_s->b_p_spl != NUL) {
(void)did_set_spelllang(curwin);
}
+ curbuf->b_last_used = time(NULL);
redraw_later(NOT_VALID);
}
@@ -1750,6 +1754,7 @@ buf_T * buflist_new(char_u *ffname, char_u *sfname, linenr_T lnum, int flags)
apply_autocmds(EVENT_BUFWIPEOUT, NULL, NULL, false, curbuf);
}
if (aborting()) { // autocmds may abort script processing
+ xfree(ffname);
return NULL;
}
if (buf == curbuf) {
@@ -1879,6 +1884,10 @@ buf_T * buflist_new(char_u *ffname, char_u *sfname, linenr_T lnum, int flags)
}
}
+ buf->b_prompt_callback.type = kCallbackNone;
+ buf->b_prompt_interrupt.type = kCallbackNone;
+ buf->b_prompt_text = NULL;
+
return buf;
}
@@ -1947,6 +1956,7 @@ void free_buf_options(buf_T *buf, int free_p_ff)
clear_string_option(&buf->b_p_path);
clear_string_option(&buf->b_p_tags);
clear_string_option(&buf->b_p_tc);
+ clear_string_option(&buf->b_p_tfu);
clear_string_option(&buf->b_p_dict);
clear_string_option(&buf->b_p_tsr);
clear_string_option(&buf->b_p_qe);
@@ -2244,6 +2254,23 @@ int buflist_findpat(
return match;
}
+typedef struct {
+ buf_T *buf;
+ char_u *match;
+} bufmatch_T;
+
+/// Compare functions for qsort() below, that compares b_last_used.
+static int
+buf_time_compare(const void *s1, const void *s2)
+{
+ buf_T *buf1 = *(buf_T **)s1;
+ buf_T *buf2 = *(buf_T **)s2;
+
+ if (buf1->b_last_used == buf2->b_last_used) {
+ return 0;
+ }
+ return buf1->b_last_used > buf2->b_last_used ? -1 : 1;
+}
/*
* Find all buffer names that match.
@@ -2257,6 +2284,7 @@ int ExpandBufnames(char_u *pat, int *num_file, char_u ***file, int options)
char_u *p;
int attempt;
char_u *patc;
+ bufmatch_T *matches = NULL;
*num_file = 0; // return values in case of FAIL
*file = NULL;
@@ -2307,7 +2335,13 @@ int ExpandBufnames(char_u *pat, int *num_file, char_u ***file, int options)
} else {
p = vim_strsave(p);
}
- (*file)[count++] = p;
+ if (matches != NULL) {
+ matches[count].buf = buf;
+ matches[count].match = p;
+ count++;
+ } else {
+ (*file)[count++] = p;
+ }
}
}
}
@@ -2316,6 +2350,10 @@ int ExpandBufnames(char_u *pat, int *num_file, char_u ***file, int options)
}
if (round == 1) {
*file = xmalloc((size_t)count * sizeof(**file));
+
+ if (options & WILD_BUFLASTUSED) {
+ matches = xmalloc((size_t)count * sizeof(*matches));
+ }
}
}
vim_regfree(regmatch.regprog);
@@ -2328,6 +2366,25 @@ int ExpandBufnames(char_u *pat, int *num_file, char_u ***file, int options)
xfree(patc);
}
+ if (matches != NULL) {
+ if (count > 1) {
+ qsort(matches, (size_t)count, sizeof(bufmatch_T), buf_time_compare);
+ }
+
+ // if the current buffer is first in the list, place it at the end
+ if (matches[0].buf == curbuf) {
+ for (int i = 1; i < count; i++) {
+ (*file)[i-1] = matches[i].match;
+ }
+ (*file)[count-1] = matches[0].match;
+ } else {
+ for (int i = 0; i < count; i++) {
+ (*file)[i] = matches[i].match;
+ }
+ }
+ xfree(matches);
+ }
+
*num_file = count;
return count == 0 ? FAIL : OK;
}
@@ -2588,11 +2645,35 @@ linenr_T buflist_findlnum(buf_T *buf)
// List all known file names (for :files and :buffers command).
void buflist_list(exarg_T *eap)
{
- buf_T *buf;
+ buf_T *buf = firstbuf;
int len;
int i;
- for (buf = firstbuf; buf != NULL && !got_int; buf = buf->b_next) {
+ garray_T buflist;
+ buf_T **buflist_data = NULL, **p;
+
+ if (vim_strchr(eap->arg, 't')) {
+ ga_init(&buflist, sizeof(buf_T *), 50);
+ for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
+ ga_grow(&buflist, 1);
+ ((buf_T **)buflist.ga_data)[buflist.ga_len++] = buf;
+ }
+
+ qsort(buflist.ga_data, (size_t)buflist.ga_len,
+ sizeof(buf_T *), buf_time_compare);
+
+ p = buflist_data = (buf_T **)buflist.ga_data;
+ buf = *p;
+ }
+
+ for (;
+ buf != NULL && !got_int;
+ buf = buflist_data
+ ? (++p < buflist_data + buflist.ga_len ? *p : NULL)
+ : buf->b_next) {
+ const bool is_terminal = buf->terminal;
+ const bool job_running = buf->terminal && terminal_running(buf->terminal);
+
// skip unspecified buffers
if ((!buf->b_p_bl && !eap->forceit && !strchr((char *)eap->arg, 'u'))
|| (strchr((char *)eap->arg, 'u') && buf->b_p_bl)
@@ -2602,6 +2683,8 @@ void buflist_list(exarg_T *eap)
&& (buf->b_ml.ml_mfp == NULL || buf->b_nwindows == 0))
|| (strchr((char *)eap->arg, 'h')
&& (buf->b_ml.ml_mfp == NULL || buf->b_nwindows != 0))
+ || (strchr((char *)eap->arg, 'R') && (!is_terminal || !job_running))
+ || (strchr((char *)eap->arg, 'F') && (!is_terminal || job_running))
|| (strchr((char *)eap->arg, '-') && buf->b_p_ma)
|| (strchr((char *)eap->arg, '=') && !buf->b_p_ro)
|| (strchr((char *)eap->arg, 'x') && !(buf->b_flags & BF_READERR))
@@ -2648,13 +2731,22 @@ void buflist_list(exarg_T *eap)
do {
IObuff[len++] = ' ';
} while (--i > 0 && len < IOSIZE - 18);
- vim_snprintf((char *)IObuff + len, (size_t)(IOSIZE - len),
- _("line %" PRId64),
- buf == curbuf ? (int64_t)curwin->w_cursor.lnum
- : (int64_t)buflist_findlnum(buf));
+ if (vim_strchr(eap->arg, 't') && buf->b_last_used) {
+ add_time(IObuff + len, (size_t)(IOSIZE - len), buf->b_last_used);
+ } else {
+ vim_snprintf((char *)IObuff + len, (size_t)(IOSIZE - len),
+ _("line %" PRId64),
+ buf == curbuf ? (int64_t)curwin->w_cursor.lnum
+ : (int64_t)buflist_findlnum(buf));
+ }
+
msg_outtrans(IObuff);
line_breakcheck();
}
+
+ if (buflist_data) {
+ ga_clear(&buflist);
+ }
}
/*
@@ -2689,7 +2781,7 @@ setfname(
buf_T *buf,
char_u *ffname,
char_u *sfname,
- int message // give message when buffer already exists
+ bool message // give message when buffer already exists
)
{
buf_T *obuf = NULL;
@@ -2992,28 +3084,29 @@ fileinfo(
}
vim_snprintf_add((char *)buffer, IOSIZE, "\"%s%s%s%s%s%s",
- curbufIsChanged() ? (shortmess(SHM_MOD)
- ? " [+]" : _(" [Modified]")) : " ",
- (curbuf->b_flags & BF_NOTEDITED)
- && !bt_dontwrite(curbuf)
- ? _("[Not edited]") : "",
- (curbuf->b_flags & BF_NEW)
- && !bt_dontwrite(curbuf)
- ? _("[New file]") : "",
- (curbuf->b_flags & BF_READERR) ? _("[Read errors]") : "",
- curbuf->b_p_ro ? (shortmess(SHM_RO) ? _("[RO]")
- : _("[readonly]")) : "",
- (curbufIsChanged() || (curbuf->b_flags & BF_WRITE_MASK)
- || curbuf->b_p_ro) ?
- " " : "");
- /* 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)
+ curbufIsChanged()
+ ? (shortmess(SHM_MOD) ? " [+]" : _(" [Modified]")) : " ",
+ (curbuf->b_flags & BF_NOTEDITED) && !bt_dontwrite(curbuf)
+ ? _("[Not edited]") : "",
+ (curbuf->b_flags & BF_NEW) && !bt_dontwrite(curbuf)
+ ? new_file_message() : "",
+ (curbuf->b_flags & BF_READERR)
+ ? _("[Read errors]") : "",
+ curbuf->b_p_ro
+ ? (shortmess(SHM_RO) ? _("[RO]") : _("[readonly]")) : "",
+ (curbufIsChanged()
+ || (curbuf->b_flags & BF_WRITE_MASK)
+ || curbuf->b_p_ro)
+ ? " " : "");
+ // 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));
- else
+ } else {
n = (int)(((long)curwin->w_cursor.lnum * 100L) /
(long)curbuf->b_ml.ml_line_count);
+ }
if (curbuf->b_ml.ml_flags & ML_EMPTY) {
vim_snprintf_add((char *)buffer, IOSIZE, "%s", _(no_lines_msg));
} else if (p_ru) {
@@ -3070,18 +3163,15 @@ void col_print(char_u *buf, size_t buflen, int col, int vcol)
}
}
-/*
- * put file name in title bar of window and in icon title
- */
-
static char_u *lasttitle = NULL;
static char_u *lasticon = NULL;
+
+// Put the title name in the title bar and icon of the window.
void maketitle(void)
{
- char_u *t_str = NULL;
- char_u *i_name;
- char_u *i_str = NULL;
+ char_u *title_str = NULL;
+ char_u *icon_str = NULL;
int maxlen = 0;
int len;
int mustset;
@@ -3095,7 +3185,7 @@ void maketitle(void)
need_maketitle = false;
if (!p_title && !p_icon && lasttitle == NULL && lasticon == NULL) {
- return;
+ return; // nothing to do
}
if (p_title) {
@@ -3116,14 +3206,14 @@ void maketitle(void)
build_stl_str_hl(curwin, (char_u *)buf, sizeof(buf),
p_titlestring, use_sandbox,
0, maxlen, NULL, NULL);
- t_str = (char_u *)buf;
+ title_str = (char_u *)buf;
if (called_emsg) {
set_string_option_direct((char_u *)"titlestring", -1, (char_u *)"",
OPT_FREE, SID_ERROR);
}
called_emsg |= save_called_emsg;
} else {
- t_str = p_titlestring;
+ title_str = p_titlestring;
}
} else {
// Format: "fname + (path) (1 of 2) - VIM".
@@ -3207,16 +3297,16 @@ void maketitle(void)
trunc_string((char_u *)buf, (char_u *)buf, maxlen, sizeof(buf));
}
}
- t_str = (char_u *)buf;
+ title_str = (char_u *)buf;
#undef SPACE_FOR_FNAME
#undef SPACE_FOR_DIR
#undef SPACE_FOR_ARGNR
}
}
- mustset = ti_change(t_str, &lasttitle);
+ mustset = value_change(title_str, &lasttitle);
if (p_icon) {
- i_str = (char_u *)buf;
+ icon_str = (char_u *)buf;
if (*p_iconstring != NUL) {
if (stl_syntax & STL_IN_ICON) {
int use_sandbox = false;
@@ -3224,37 +3314,40 @@ void maketitle(void)
use_sandbox = was_set_insecurely((char_u *)"iconstring", 0);
called_emsg = false;
- build_stl_str_hl(curwin, i_str, sizeof(buf),
- p_iconstring, use_sandbox,
- 0, 0, NULL, NULL);
- if (called_emsg)
+ build_stl_str_hl(curwin, icon_str, sizeof(buf),
+ p_iconstring, use_sandbox,
+ 0, 0, NULL, NULL);
+ if (called_emsg) {
set_string_option_direct((char_u *)"iconstring", -1,
- (char_u *)"", OPT_FREE, SID_ERROR);
+ (char_u *)"", OPT_FREE, SID_ERROR);
+ }
called_emsg |= save_called_emsg;
- } else
- i_str = p_iconstring;
+ } else {
+ icon_str = p_iconstring;
+ }
} else {
+ char_u *buf_p;
if (buf_spname(curbuf) != NULL) {
- i_name = buf_spname(curbuf);
+ buf_p = buf_spname(curbuf);
} else { // use file name only in icon
- i_name = path_tail(curbuf->b_ffname);
+ buf_p = path_tail(curbuf->b_ffname);
}
- *i_str = NUL;
+ *icon_str = NUL;
// Truncate name at 100 bytes.
- len = (int)STRLEN(i_name);
+ len = (int)STRLEN(buf_p);
if (len > 100) {
len -= 100;
if (has_mbyte) {
- len += (*mb_tail_off)(i_name, i_name + len) + 1;
+ len += (*mb_tail_off)(buf_p, buf_p + len) + 1;
}
- i_name += len;
+ buf_p += len;
}
- STRCPY(i_str, i_name);
- trans_characters(i_str, IOSIZE);
+ STRCPY(icon_str, buf_p);
+ trans_characters(icon_str, IOSIZE);
}
}
- mustset |= ti_change(i_str, &lasticon);
+ mustset |= value_change(icon_str, &lasticon);
if (mustset) {
resettitle();
@@ -3268,8 +3361,8 @@ void maketitle(void)
/// @param str desired title string
/// @param[in,out] last current title string
//
-/// @return true when "*last" changed.
-static bool ti_change(char_u *str, char_u **last)
+/// @return true if resettitle() is to be called.
+static bool value_change(char_u *str, char_u **last)
FUNC_ATTR_WARN_UNUSED_RESULT
{
if ((str == NULL) != (*last == NULL)
@@ -3277,10 +3370,11 @@ static bool ti_change(char_u *str, char_u **last)
xfree(*last);
if (str == NULL) {
*last = NULL;
+ resettitle();
} else {
*last = vim_strsave(str);
+ return true;
}
- return true;
}
return false;
}
@@ -3370,7 +3464,8 @@ int build_stl_str_hl(
} type;
} items[STL_MAX_ITEM];
#define TMPLEN 70
- char_u tmp[TMPLEN];
+ char_u buf_tmp[TMPLEN];
+ char_u win_tmp[TMPLEN];
char_u *usefmt = fmt;
const int save_must_redraw = must_redraw;
const int save_redr_type = curwin->w_redr_type;
@@ -3378,10 +3473,18 @@ int build_stl_str_hl(
// When the format starts with "%!" then evaluate it as an expression and
// use the result as the actual format string.
if (fmt[0] == '%' && fmt[1] == '!') {
+ typval_T tv = {
+ .v_type = VAR_NUMBER,
+ .vval.v_number = wp->handle,
+ };
+ set_var(S_LEN("g:statusline_winid"), &tv, false);
+
usefmt = eval_to_string_safe(fmt + 2, NULL, use_sandbox);
if (usefmt == NULL) {
usefmt = fmt;
}
+
+ do_unlet(S_LEN("g:statusline_winid"), true);
}
if (fillchar == 0) {
@@ -3391,14 +3494,27 @@ int build_stl_str_hl(
fillchar = '-';
}
+ // The cursor in windows other than the current one isn't always
+ // up-to-date, esp. because of autocommands and timers.
+ linenr_T lnum = wp->w_cursor.lnum;
+ if (lnum > wp->w_buffer->b_ml.ml_line_count) {
+ lnum = wp->w_buffer->b_ml.ml_line_count;
+ wp->w_cursor.lnum = lnum;
+ }
+
// Get line & check if empty (cursorpos will show "0-1").
- char_u *line_ptr = ml_get_buf(wp->w_buffer, wp->w_cursor.lnum, false);
+ const char_u *line_ptr = ml_get_buf(wp->w_buffer, lnum, false);
bool empty_line = (*line_ptr == NUL);
// Get the byte value now, in case we need it below. This is more
// efficient than making a copy of the line.
int byteval;
- if (wp->w_cursor.col > (colnr_T)STRLEN(line_ptr)) {
+ const size_t len = STRLEN(line_ptr);
+ if (wp->w_cursor.col > (colnr_T)len) {
+ // Line may have changed since checking the cursor column, or the lnum
+ // was adjusted above.
+ wp->w_cursor.col = (colnr_T)len;
+ wp->w_cursor.coladd = 0;
byteval = 0;
} else {
byteval = utf_ptr2char(line_ptr + wp->w_cursor.col);
@@ -3530,8 +3646,20 @@ int build_stl_str_hl(
}
}
if (n == curitem && group_start_userhl == group_end_userhl) {
+ // empty group
out_p = t;
group_len = 0;
+ for (n = groupitems[groupdepth] + 1; n < curitem; n++) {
+ // do not use the highlighting from the removed group
+ if (items[n].type == Highlight) {
+ items[n].type = Empty;
+ }
+ // adjust the start position of TabPage to the next
+ // item position
+ if (items[n].type == TabPage) {
+ items[n].start = out_p;
+ }
+ }
}
}
@@ -3791,22 +3919,31 @@ int build_stl_str_hl(
// { Evaluate the expression
// Store the current buffer number as a string variable
- vim_snprintf((char *)tmp, sizeof(tmp), "%d", curbuf->b_fnum);
- set_internal_string_var((char_u *)"g:actual_curbuf", tmp);
+ vim_snprintf((char *)buf_tmp, sizeof(buf_tmp), "%d", curbuf->b_fnum);
+ set_internal_string_var((char_u *)"g:actual_curbuf", buf_tmp);
+ vim_snprintf((char *)win_tmp, sizeof(win_tmp), "%d", curwin->handle);
+ set_internal_string_var((char_u *)"g:actual_curwin", win_tmp);
buf_T *const save_curbuf = curbuf;
win_T *const save_curwin = curwin;
+ const int save_VIsual_active = VIsual_active;
curwin = wp;
curbuf = wp->w_buffer;
+ // Visual mode is only valid in the current window.
+ if (curwin != save_curwin) {
+ VIsual_active = false;
+ }
// Note: The result stored in `t` is unused.
str = eval_to_string_safe(out_p, &t, use_sandbox);
curwin = save_curwin;
curbuf = save_curbuf;
+ VIsual_active = save_VIsual_active;
// Remove the variable we just stored
do_unlet(S_LEN("g:actual_curbuf"), true);
+ do_unlet(S_LEN("g:actual_curwin"), true);
// }
@@ -3865,8 +4002,8 @@ int build_stl_str_hl(
// Store the position percentage in our temporary buffer.
// Note: We cannot store the value in `num` because
// `get_rel_pos` can return a named position. Ex: "Top"
- get_rel_pos(wp, tmp, TMPLEN);
- str = tmp;
+ get_rel_pos(wp, buf_tmp, TMPLEN);
+ str = buf_tmp;
break;
case STL_ARGLISTSTAT:
@@ -3876,19 +4013,19 @@ int build_stl_str_hl(
// at the end of the null-terminated string.
// Setting the first byte to null means it will place the argument
// number string at the beginning of the buffer.
- tmp[0] = 0;
+ buf_tmp[0] = 0;
// Note: The call will only return true if it actually
- // appended data to the `tmp` buffer.
- if (append_arg_number(wp, tmp, (int)sizeof(tmp), false)) {
- str = tmp;
+ // appended data to the `buf_tmp` buffer.
+ if (append_arg_number(wp, buf_tmp, (int)sizeof(buf_tmp), false)) {
+ str = buf_tmp;
}
break;
case STL_KEYMAP:
fillable = false;
- if (get_keymap_str(wp, (char_u *)"<%s>", tmp, TMPLEN)) {
- str = tmp;
+ if (get_keymap_str(wp, (char_u *)"<%s>", buf_tmp, TMPLEN)) {
+ str = buf_tmp;
}
break;
case STL_PAGENUM:
@@ -3945,9 +4082,9 @@ int build_stl_str_hl(
// (including the brackets and null terminating character)
if (*wp->w_buffer->b_p_ft != NUL
&& STRLEN(wp->w_buffer->b_p_ft) < TMPLEN - 3) {
- vim_snprintf((char *)tmp, sizeof(tmp), "[%s]",
- wp->w_buffer->b_p_ft);
- str = tmp;
+ vim_snprintf((char *)buf_tmp, sizeof(buf_tmp), "[%s]",
+ wp->w_buffer->b_p_ft);
+ str = buf_tmp;
}
break;
@@ -3959,13 +4096,13 @@ int build_stl_str_hl(
// (including the comma and null terminating character)
if (*wp->w_buffer->b_p_ft != NUL
&& STRLEN(wp->w_buffer->b_p_ft) < TMPLEN - 2) {
- vim_snprintf((char *)tmp, sizeof(tmp), ",%s",
- wp->w_buffer->b_p_ft);
+ vim_snprintf((char *)buf_tmp, sizeof(buf_tmp), ",%s",
+ wp->w_buffer->b_p_ft);
// Uppercase the file extension
- for (char_u *t = tmp; *t != 0; t++) {
+ for (char_u *t = buf_tmp; *t != 0; t++) {
*t = (char_u)TOUPPER_LOC(*t);
}
- str = tmp;
+ str = buf_tmp;
}
break;
}
@@ -4629,7 +4766,8 @@ do_arg_all(
if (i < alist->al_ga.ga_len
&& (AARGLIST(alist)[i].ae_fnum == buf->b_fnum
|| path_full_compare(alist_name(&AARGLIST(alist)[i]),
- buf->b_ffname, true) & kEqualFiles)) {
+ buf->b_ffname,
+ true, true) & kEqualFiles)) {
int weight = 1;
if (old_curtab == curtab) {
@@ -4812,6 +4950,12 @@ do_arg_all(
xfree(opened);
}
+// Return TRUE if "buf" is a prompt buffer.
+int bt_prompt(buf_T *buf)
+{
+ return buf != NULL && buf->b_p_bt[0] == 'p';
+}
+
/*
* Open a window for a number of buffers.
*/
@@ -5179,6 +5323,13 @@ bool bt_help(const buf_T *const buf)
return buf != NULL && buf->b_help;
}
+// Return true if "buf" is a normal buffer, 'buftype' is empty.
+bool bt_normal(const buf_T *const buf)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ return buf != NULL && buf->b_p_bt[0] == NUL;
+}
+
// Return true if "buf" is the quickfix buffer.
bool bt_quickfix(const buf_T *const buf)
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
@@ -5199,14 +5350,18 @@ bool bt_nofile(const buf_T *const buf)
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
return buf != NULL && ((buf->b_p_bt[0] == 'n' && buf->b_p_bt[2] == 'f')
- || buf->b_p_bt[0] == 'a' || buf->terminal);
+ || buf->b_p_bt[0] == 'a'
+ || buf->terminal
+ || buf->b_p_bt[0] == 'p');
}
// Return true if "buf" is a "nowrite", "nofile" or "terminal" buffer.
bool bt_dontwrite(const buf_T *const buf)
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
- return buf != NULL && (buf->b_p_bt[0] == 'n' || buf->terminal);
+ return buf != NULL && (buf->b_p_bt[0] == 'n'
+ || buf->terminal
+ || buf->b_p_bt[0] == 'p');
}
bool bt_dontwrite_msg(const buf_T *const buf)
@@ -5259,6 +5414,9 @@ char_u *buf_spname(buf_T *buf)
if (buf->b_fname != NULL) {
return buf->b_fname;
}
+ if (bt_prompt(buf)) {
+ return (char_u *)_("[Prompt]");
+ }
return (char_u *)_("[Scratch]");
}
if (buf->b_fname == NULL) {
@@ -5323,349 +5481,6 @@ int buf_signcols(buf_T *buf)
return buf->b_signcols;
}
-// bufhl: plugin highlights associated with a buffer
-
-/// Get reference to line in kbtree_t
-///
-/// @param b the three
-/// @param line the linenumber to lookup
-/// @param put if true, put a new line when not found
-/// if false, return NULL when not found
-BufhlLine *bufhl_tree_ref(BufhlInfo *b, linenr_T line, bool put)
-{
- BufhlLine t = BUFHLLINE_INIT(line);
-
- // kp_put() only works if key is absent, try get first
- BufhlLine **pp = kb_get(bufhl, b, &t);
- if (pp) {
- return *pp;
- } else if (!put) {
- return NULL;
- }
-
- BufhlLine *p = xmalloc(sizeof(*p));
- *p = (BufhlLine)BUFHLLINE_INIT(line);
- kb_put(bufhl, b, p);
- return p;
-}
-
-/// Adds a highlight to buffer.
-///
-/// Unlike matchaddpos() highlights follow changes to line numbering (as lines
-/// are inserted/removed above the highlighted line), like signs and marks do.
-///
-/// When called with "src_id" set to 0, a unique source id is generated and
-/// returned. Succesive calls can pass it in as "src_id" to add new highlights
-/// to the same source group. All highlights in the same group can be cleared
-/// at once. If the highlight never will be manually deleted pass in -1 for
-/// "src_id"
-///
-/// if "hl_id" or "lnum" is invalid no highlight is added, but a new src_id
-/// is still returned.
-///
-/// @param buf The buffer to add highlights to
-/// @param src_id src_id to use or 0 to use a new src_id group,
-/// or -1 for ungrouped highlight.
-/// @param hl_id Id of the highlight group to use
-/// @param lnum The line to highlight
-/// @param col_start First column to highlight
-/// @param col_end The last column to highlight,
-/// or -1 to highlight to end of line
-/// @return The src_id that was used
-int bufhl_add_hl(buf_T *buf,
- int src_id,
- int hl_id,
- linenr_T lnum,
- colnr_T col_start,
- colnr_T col_end)
-{
- if (src_id == 0) {
- src_id = (int)nvim_create_namespace((String)STRING_INIT);
- }
- if (hl_id <= 0) {
- // no highlight group or invalid line, just return src_id
- return src_id;
- }
-
- BufhlLine *lineinfo = bufhl_tree_ref(&buf->b_bufhl_info, lnum, true);
-
- BufhlItem *hlentry = kv_pushp(lineinfo->items);
- hlentry->src_id = src_id;
- hlentry->hl_id = hl_id;
- hlentry->start = col_start;
- hlentry->stop = col_end;
-
- if (0 < lnum && lnum <= buf->b_ml.ml_line_count) {
- redraw_buf_line_later(buf, lnum);
- }
- return src_id;
-}
-
-/// Add highlighting to a buffer, bounded by two cursor positions,
-/// with an offset.
-///
-/// @param buf Buffer to add highlights to
-/// @param src_id src_id to use or 0 to use a new src_id group,
-/// or -1 for ungrouped highlight.
-/// @param hl_id Highlight group id
-/// @param pos_start Cursor position to start the hightlighting at
-/// @param pos_end Cursor position to end the highlighting at
-/// @param offset Move the whole highlighting this many columns to the right
-void bufhl_add_hl_pos_offset(buf_T *buf,
- int src_id,
- int hl_id,
- lpos_T pos_start,
- lpos_T pos_end,
- colnr_T offset)
-{
- colnr_T hl_start = 0;
- colnr_T hl_end = 0;
-
- for (linenr_T lnum = pos_start.lnum; lnum <= pos_end.lnum; lnum ++) {
- if (pos_start.lnum < lnum && lnum < pos_end.lnum) {
- hl_start = offset;
- hl_end = MAXCOL;
- } else if (lnum == pos_start.lnum && lnum < pos_end.lnum) {
- hl_start = pos_start.col + offset + 1;
- hl_end = MAXCOL;
- } else if (pos_start.lnum < lnum && lnum == pos_end.lnum) {
- hl_start = offset;
- hl_end = pos_end.col + offset;
- } else if (pos_start.lnum == lnum && pos_end.lnum == lnum) {
- hl_start = pos_start.col + offset + 1;
- hl_end = pos_end.col + offset;
- }
- (void)bufhl_add_hl(buf, src_id, hl_id, lnum, hl_start, hl_end);
- }
-}
-
-int bufhl_add_virt_text(buf_T *buf,
- int src_id,
- linenr_T lnum,
- VirtText virt_text)
-{
- if (src_id == 0) {
- src_id = (int)nvim_create_namespace((String)STRING_INIT);
- }
-
- BufhlLine *lineinfo = bufhl_tree_ref(&buf->b_bufhl_info, lnum, true);
-
- bufhl_clear_virttext(&lineinfo->virt_text);
- if (kv_size(virt_text) > 0) {
- lineinfo->virt_text_src = src_id;
- lineinfo->virt_text = virt_text;
- } else {
- lineinfo->virt_text_src = 0;
- // currently not needed, but allow a future caller with
- // 0 size and non-zero capacity
- kv_destroy(virt_text);
- }
-
- if (0 < lnum && lnum <= buf->b_ml.ml_line_count) {
- redraw_buf_line_later(buf, lnum);
- }
- return src_id;
-}
-
-static void bufhl_clear_virttext(VirtText *text)
-{
- for (size_t i = 0; i < kv_size(*text); i++) {
- xfree(kv_A(*text, i).text);
- }
- kv_destroy(*text);
- *text = (VirtText)KV_INITIAL_VALUE;
-}
-
-/// Clear bufhl highlights from a given source group and range of lines.
-///
-/// @param buf The buffer to remove highlights from
-/// @param src_id highlight source group to clear, or -1 to clear all groups.
-/// @param line_start first line to clear
-/// @param line_end last line to clear or MAXLNUM to clear to end of file.
-void bufhl_clear_line_range(buf_T *buf,
- int src_id,
- linenr_T line_start,
- linenr_T line_end)
-{
- kbitr_t(bufhl) itr;
- BufhlLine *l, t = BUFHLLINE_INIT(line_start);
- if (!kb_itr_get(bufhl, &buf->b_bufhl_info, &t, &itr)) {
- kb_itr_next(bufhl, &buf->b_bufhl_info, &itr);
- }
- for (; kb_itr_valid(&itr); kb_itr_next(bufhl, &buf->b_bufhl_info, &itr)) {
- l = kb_itr_key(&itr);
- linenr_T line = l->line;
- if (line > line_end) {
- break;
- }
- if (line_start <= line) {
- BufhlLineStatus status = bufhl_clear_line(l, src_id, line);
- if (status != kBLSUnchanged) {
- redraw_buf_line_later(buf, line);
- }
- if (status == kBLSDeleted) {
- kb_del_itr(bufhl, &buf->b_bufhl_info, &itr);
- xfree(l);
- }
- }
- }
-}
-
-/// Clear bufhl highlights from a given source group and given line
-///
-/// @param bufhl_info The highlight info for the buffer
-/// @param src_id Highlight source group to clear, or -1 to clear all groups.
-/// @param lnum Linenr where the highlight should be cleared
-static BufhlLineStatus bufhl_clear_line(BufhlLine *lineinfo, int src_id,
- linenr_T lnum)
-{
- BufhlLineStatus changed = kBLSUnchanged;
- size_t oldsize = kv_size(lineinfo->items);
- if (src_id < 0) {
- kv_size(lineinfo->items) = 0;
- } else {
- size_t newidx = 0;
- for (size_t i = 0; i < kv_size(lineinfo->items); i++) {
- if (kv_A(lineinfo->items, i).src_id != src_id) {
- if (i != newidx) {
- kv_A(lineinfo->items, newidx) = kv_A(lineinfo->items, i);
- }
- newidx++;
- }
- }
- kv_size(lineinfo->items) = newidx;
- }
- if (kv_size(lineinfo->items) != oldsize) {
- changed = kBLSChanged;
- }
-
- if (kv_size(lineinfo->virt_text) != 0
- && (src_id < 0 || src_id == lineinfo->virt_text_src)) {
- bufhl_clear_virttext(&lineinfo->virt_text);
- lineinfo->virt_text_src = 0;
- changed = kBLSChanged;
- }
-
- if (kv_size(lineinfo->items) == 0 && kv_size(lineinfo->virt_text) == 0) {
- kv_destroy(lineinfo->items);
- return kBLSDeleted;
- }
- return changed;
-}
-
-
-/// Remove all highlights and free the highlight data
-void bufhl_clear_all(buf_T *buf)
-{
- bufhl_clear_line_range(buf, -1, 1, MAXLNUM);
- kb_destroy(bufhl, (&buf->b_bufhl_info));
- kb_init(&buf->b_bufhl_info);
- kv_destroy(buf->b_bufhl_move_space);
- kv_init(buf->b_bufhl_move_space);
-}
-
-/// Adjust a placed highlight for inserted/deleted lines.
-void bufhl_mark_adjust(buf_T* buf,
- linenr_T line1,
- linenr_T line2,
- long amount,
- long amount_after,
- bool end_temp)
-{
- kbitr_t(bufhl) itr;
- BufhlLine *l, t = BUFHLLINE_INIT(line1);
- if (end_temp && amount < 0) {
- // Move all items from b_bufhl_move_space to the btree.
- for (size_t i = 0; i < kv_size(buf->b_bufhl_move_space); i++) {
- l = kv_A(buf->b_bufhl_move_space, i);
- l->line += amount;
- kb_put(bufhl, &buf->b_bufhl_info, l);
- }
- kv_size(buf->b_bufhl_move_space) = 0;
- return;
- }
-
- if (!kb_itr_get(bufhl, &buf->b_bufhl_info, &t, &itr)) {
- kb_itr_next(bufhl, &buf->b_bufhl_info, &itr);
- }
- for (; kb_itr_valid(&itr); kb_itr_next(bufhl, &buf->b_bufhl_info, &itr)) {
- l = kb_itr_key(&itr);
- if (l->line >= line1 && l->line <= line2) {
- if (end_temp && amount > 0) {
- kb_del_itr(bufhl, &buf->b_bufhl_info, &itr);
- kv_push(buf->b_bufhl_move_space, l);
- }
- if (amount == MAXLNUM) {
- if (bufhl_clear_line(l, -1, l->line) == kBLSDeleted) {
- kb_del_itr(bufhl, &buf->b_bufhl_info, &itr);
- xfree(l);
- } else {
- assert(false);
- }
- } else {
- l->line += amount;
- }
- } else if (l->line > line2) {
- if (amount_after == 0) {
- break;
- }
- l->line += amount_after;
- }
- }
-}
-
-
-/// Get highlights to display at a specific line
-///
-/// @param buf The buffer handle
-/// @param lnum The line number
-/// @param[out] info The highligts for the line
-/// @return true if there was highlights to display
-bool bufhl_start_line(buf_T *buf, linenr_T lnum, BufhlLineInfo *info)
-{
- BufhlLine *lineinfo = bufhl_tree_ref(&buf->b_bufhl_info, lnum, false);
- if (!lineinfo) {
- return false;
- }
- info->valid_to = -1;
- info->line = lineinfo;
- return true;
-}
-
-/// get highlighting at column col
-///
-/// It is is assumed this will be called with
-/// non-decreasing column nrs, so that it is
-/// possible to only recalculate highlights
-/// at endpoints.
-///
-/// @param info The info returned by bufhl_start_line
-/// @param col The column to get the attr for
-/// @return The highilight attr to display at the column
-int bufhl_get_attr(BufhlLineInfo *info, colnr_T col)
-{
- if (col <= info->valid_to) {
- return info->current;
- }
- int attr = 0;
- info->valid_to = MAXCOL;
- for (size_t i = 0; i < kv_size(info->line->items); i++) {
- BufhlItem entry = kv_A(info->line->items, i);
- if (entry.start <= col && col <= entry.stop) {
- int entry_attr = syn_id2attr(entry.hl_id);
- attr = hl_combine_attr(attr, entry_attr);
- if (entry.stop < info->valid_to) {
- info->valid_to = entry.stop;
- }
- } else if (col < entry.start && entry.start-1 < info->valid_to) {
- info->valid_to = entry.start-1;
- }
- }
- info->current = attr;
- return attr;
-}
-
-
/*
* Set 'buflisted' for curbuf to "on" and trigger autocommands if it changed.
*/
diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h
index 16c7804be0..550f8a5e40 100644
--- a/src/nvim/buffer_defs.h
+++ b/src/nvim/buffer_defs.h
@@ -42,6 +42,8 @@ typedef struct {
#include "nvim/map.h"
// for kvec
#include "nvim/lib/kvec.h"
+// for marktree
+#include "nvim/marktree.h"
#define GETFILE_SUCCESS(x) ((x) <= 0)
#define MODIFIABLE(buf) (buf->b_p_ma)
@@ -65,14 +67,14 @@ typedef struct {
* off off w_botline not valid
* on off not possible
*/
-#define VALID_WROW 0x01 /* w_wrow (window row) is valid */
-#define VALID_WCOL 0x02 /* w_wcol (window col) is valid */
-#define VALID_VIRTCOL 0x04 /* w_virtcol (file col) is valid */
-#define VALID_CHEIGHT 0x08 /* w_cline_height and w_cline_folded valid */
-#define VALID_CROW 0x10 /* w_cline_row is valid */
-#define VALID_BOTLINE 0x20 /* w_botine and w_empty_rows are valid */
-#define VALID_BOTLINE_AP 0x40 /* w_botine is approximated */
-#define VALID_TOPLINE 0x80 /* w_topline is valid (for cursor position) */
+#define VALID_WROW 0x01 // w_wrow (window row) is valid
+#define VALID_WCOL 0x02 // w_wcol (window col) is valid
+#define VALID_VIRTCOL 0x04 // w_virtcol (file col) is valid
+#define VALID_CHEIGHT 0x08 // w_cline_height and w_cline_folded valid
+#define VALID_CROW 0x10 // w_cline_row is valid
+#define VALID_BOTLINE 0x20 // w_botine and w_empty_rows are valid
+#define VALID_BOTLINE_AP 0x40 // w_botine is approximated
+#define VALID_TOPLINE 0x80 // w_topline is valid (for cursor position)
// flags for b_flags
#define BF_RECOVERED 0x01 // buffer has been recovered
@@ -90,7 +92,7 @@ typedef struct {
#define BF_DUMMY 0x80 // dummy buffer, only used internally
#define BF_PRESERVED 0x100 // ":preserve" was used
-/* Mask to check for flags that prevent normal writing */
+// Mask to check for flags that prevent normal writing
#define BF_WRITE_MASK (BF_NOTEDITED + BF_NEW + BF_READERR)
typedef struct window_S win_T;
@@ -109,8 +111,6 @@ typedef uint16_t disptick_T; // display tick type
#include "nvim/syntax_defs.h"
// for signlist_T
#include "nvim/sign_defs.h"
-// for bufhl_*_T
-#include "nvim/bufhl_defs.h"
#include "nvim/os/fs_defs.h" // for FileID
#include "nvim/terminal.h" // for Terminal
@@ -119,10 +119,11 @@ typedef uint16_t disptick_T; // display tick type
* The taggy struct is used to store the information about a :tag command.
*/
typedef struct taggy {
- char_u *tagname; /* tag name */
- fmark_T fmark; /* cursor position BEFORE ":tag" */
- int cur_match; /* match number */
- int cur_fnum; /* buffer number used for cur_match */
+ char_u *tagname; // tag name
+ fmark_T fmark; // cursor position BEFORE ":tag"
+ int cur_match; // match number
+ int cur_fnum; // buffer number used for cur_match
+ char_u *user_data; // used with tagfunc
} taggy_T;
typedef struct buffblock buffblock_T;
@@ -159,90 +160,92 @@ typedef struct
*/
typedef struct {
int wo_arab;
-# define w_p_arab w_onebuf_opt.wo_arab /* 'arabic' */
+# define w_p_arab w_onebuf_opt.wo_arab // 'arabic'
int wo_bri;
# define w_p_bri w_onebuf_opt.wo_bri // 'breakindent'
char_u *wo_briopt;
-# define w_p_briopt w_onebuf_opt.wo_briopt /* 'breakindentopt' */
+# define w_p_briopt w_onebuf_opt.wo_briopt // 'breakindentopt'
int wo_diff;
-# define w_p_diff w_onebuf_opt.wo_diff /* 'diff' */
- long wo_fdc;
-# define w_p_fdc w_onebuf_opt.wo_fdc /* 'foldcolumn' */
- int wo_fdc_save;
-# define w_p_fdc_save w_onebuf_opt.wo_fdc_save /* 'foldenable' saved for diff mode */
+# define w_p_diff w_onebuf_opt.wo_diff // 'diff'
+ char_u *wo_fdc;
+# define w_p_fdc w_onebuf_opt.wo_fdc // 'foldcolumn'
+ char_u *wo_fdc_save;
+# define w_p_fdc_save w_onebuf_opt.wo_fdc_save // 'fdc' saved for diff mode
int wo_fen;
-# define w_p_fen w_onebuf_opt.wo_fen /* 'foldenable' */
+# define w_p_fen w_onebuf_opt.wo_fen // 'foldenable'
int wo_fen_save;
-# define w_p_fen_save w_onebuf_opt.wo_fen_save /* 'foldenable' saved for diff mode */
+ // 'foldenable' saved for diff mode
+# define w_p_fen_save w_onebuf_opt.wo_fen_save
char_u *wo_fdi;
-# define w_p_fdi w_onebuf_opt.wo_fdi /* 'foldignore' */
+# define w_p_fdi w_onebuf_opt.wo_fdi // 'foldignore'
long wo_fdl;
-# define w_p_fdl w_onebuf_opt.wo_fdl /* 'foldlevel' */
+# define w_p_fdl w_onebuf_opt.wo_fdl // 'foldlevel'
int wo_fdl_save;
-# define w_p_fdl_save w_onebuf_opt.wo_fdl_save /* 'foldlevel' state saved for diff mode */
+ // 'foldlevel' state saved for diff mode
+# define w_p_fdl_save w_onebuf_opt.wo_fdl_save
char_u *wo_fdm;
-# define w_p_fdm w_onebuf_opt.wo_fdm /* 'foldmethod' */
+# define w_p_fdm w_onebuf_opt.wo_fdm // 'foldmethod'
char_u *wo_fdm_save;
-# define w_p_fdm_save w_onebuf_opt.wo_fdm_save /* 'fdm' saved for diff mode */
+# define w_p_fdm_save w_onebuf_opt.wo_fdm_save // 'fdm' saved for diff mode
long wo_fml;
-# define w_p_fml w_onebuf_opt.wo_fml /* 'foldminlines' */
+# define w_p_fml w_onebuf_opt.wo_fml // 'foldminlines'
long wo_fdn;
-# define w_p_fdn w_onebuf_opt.wo_fdn /* 'foldnestmax' */
+# define w_p_fdn w_onebuf_opt.wo_fdn // 'foldnestmax'
char_u *wo_fde;
-# define w_p_fde w_onebuf_opt.wo_fde /* 'foldexpr' */
+# define w_p_fde w_onebuf_opt.wo_fde // 'foldexpr'
char_u *wo_fdt;
-# define w_p_fdt w_onebuf_opt.wo_fdt /* 'foldtext' */
+# define w_p_fdt w_onebuf_opt.wo_fdt // 'foldtext'
char_u *wo_fmr;
-# define w_p_fmr w_onebuf_opt.wo_fmr /* 'foldmarker' */
+# define w_p_fmr w_onebuf_opt.wo_fmr // 'foldmarker'
int wo_lbr;
-# define w_p_lbr w_onebuf_opt.wo_lbr /* 'linebreak' */
+# define w_p_lbr w_onebuf_opt.wo_lbr // 'linebreak'
int wo_list;
-#define w_p_list w_onebuf_opt.wo_list /* 'list' */
+#define w_p_list w_onebuf_opt.wo_list // 'list'
int wo_nu;
-#define w_p_nu w_onebuf_opt.wo_nu /* 'number' */
+#define w_p_nu w_onebuf_opt.wo_nu // 'number'
int wo_rnu;
-#define w_p_rnu w_onebuf_opt.wo_rnu /* 'relativenumber' */
+#define w_p_rnu w_onebuf_opt.wo_rnu // 'relativenumber'
long wo_nuw;
-# define w_p_nuw w_onebuf_opt.wo_nuw /* 'numberwidth' */
+# define w_p_nuw w_onebuf_opt.wo_nuw // 'numberwidth'
int wo_wfh;
-# define w_p_wfh w_onebuf_opt.wo_wfh /* 'winfixheight' */
+# define w_p_wfh w_onebuf_opt.wo_wfh // 'winfixheight'
int wo_wfw;
-# define w_p_wfw w_onebuf_opt.wo_wfw /* 'winfixwidth' */
+# define w_p_wfw w_onebuf_opt.wo_wfw // 'winfixwidth'
int wo_pvw;
-# define w_p_pvw w_onebuf_opt.wo_pvw /* 'previewwindow' */
+# define w_p_pvw w_onebuf_opt.wo_pvw // 'previewwindow'
int wo_rl;
-# define w_p_rl w_onebuf_opt.wo_rl /* 'rightleft' */
+# define w_p_rl w_onebuf_opt.wo_rl // 'rightleft'
char_u *wo_rlc;
-# define w_p_rlc w_onebuf_opt.wo_rlc /* 'rightleftcmd' */
+# define w_p_rlc w_onebuf_opt.wo_rlc // 'rightleftcmd'
long wo_scr;
-#define w_p_scr w_onebuf_opt.wo_scr /* 'scroll' */
+#define w_p_scr w_onebuf_opt.wo_scr // 'scroll'
int wo_spell;
-# define w_p_spell w_onebuf_opt.wo_spell /* 'spell' */
+# define w_p_spell w_onebuf_opt.wo_spell // 'spell'
int wo_cuc;
-# define w_p_cuc w_onebuf_opt.wo_cuc /* 'cursorcolumn' */
+# define w_p_cuc w_onebuf_opt.wo_cuc // 'cursorcolumn'
int wo_cul;
-# define w_p_cul w_onebuf_opt.wo_cul /* 'cursorline' */
+# define w_p_cul w_onebuf_opt.wo_cul // 'cursorline'
char_u *wo_cc;
-# define w_p_cc w_onebuf_opt.wo_cc /* 'colorcolumn' */
+# define w_p_cc w_onebuf_opt.wo_cc // 'colorcolumn'
char_u *wo_stl;
-#define w_p_stl w_onebuf_opt.wo_stl /* 'statusline' */
+#define w_p_stl w_onebuf_opt.wo_stl // 'statusline'
int wo_scb;
-# define w_p_scb w_onebuf_opt.wo_scb /* 'scrollbind' */
- int wo_diff_saved; /* options were saved for starting diff mode */
+# define w_p_scb w_onebuf_opt.wo_scb // 'scrollbind'
+ int wo_diff_saved; // options were saved for starting diff mode
# define w_p_diff_saved w_onebuf_opt.wo_diff_saved
- int wo_scb_save; /* 'scrollbind' saved for diff mode*/
+ int wo_scb_save; // 'scrollbind' saved for diff mode
# define w_p_scb_save w_onebuf_opt.wo_scb_save
int wo_wrap;
-#define w_p_wrap w_onebuf_opt.wo_wrap /* 'wrap' */
- int wo_wrap_save; /* 'wrap' state saved for diff mode*/
+#define w_p_wrap w_onebuf_opt.wo_wrap // 'wrap'
+ int wo_wrap_save; // 'wrap' state saved for diff mode
# define w_p_wrap_save w_onebuf_opt.wo_wrap_save
- char_u *wo_cocu; /* 'concealcursor' */
+ char_u *wo_cocu; // 'concealcursor'
# define w_p_cocu w_onebuf_opt.wo_cocu
- long wo_cole; /* 'conceallevel' */
+ long wo_cole; // 'conceallevel'
# define w_p_cole w_onebuf_opt.wo_cole
int wo_crb;
-# define w_p_crb w_onebuf_opt.wo_crb /* 'cursorbind' */
- int wo_crb_save; /* 'cursorbind' state saved for diff mode*/
+# define w_p_crb w_onebuf_opt.wo_crb // 'cursorbind'
+ int wo_crb_save; // 'cursorbind' state saved for diff mode
# define w_p_crb_save w_onebuf_opt.wo_crb_save
char_u *wo_scl;
# define w_p_scl w_onebuf_opt.wo_scl // 'signcolumn'
@@ -270,14 +273,14 @@ typedef struct {
* most-recently-used order.
*/
struct wininfo_S {
- wininfo_T *wi_next; /* next entry or NULL for last entry */
- wininfo_T *wi_prev; /* previous entry or NULL for first entry */
- win_T *wi_win; /* pointer to window that did set wi_fpos */
- pos_T wi_fpos; /* last cursor position in the file */
- bool wi_optset; /* true when wi_opt has useful values */
- winopt_T wi_opt; /* local window options */
- bool wi_fold_manual; /* copy of w_fold_manual */
- garray_T wi_folds; /* clone of w_folds */
+ wininfo_T *wi_next; // next entry or NULL for last entry
+ wininfo_T *wi_prev; // previous entry or NULL for first entry
+ win_T *wi_win; // pointer to window that did set wi_fpos
+ pos_T wi_fpos; // last cursor position in the file
+ bool wi_optset; // true when wi_opt has useful values
+ winopt_T wi_opt; // local window options
+ bool wi_fold_manual; // copy of w_fold_manual
+ garray_T wi_folds; // clone of w_folds
};
/*
@@ -287,8 +290,8 @@ struct wininfo_S {
* TODO: move struct arglist to another header
*/
typedef struct arglist {
- garray_T al_ga; /* growarray with the array of file names */
- int al_refcount; /* number of windows using this arglist */
+ garray_T al_ga; // growarray with the array of file names
+ int al_refcount; // number of windows using this arglist
int id; ///< id of this arglist
} alist_T;
@@ -300,8 +303,8 @@ typedef struct arglist {
* TODO: move aentry_T to another header
*/
typedef struct argentry {
- char_u *ae_fname; /* file name as specified */
- int ae_fnum; /* buffer number with expanded file name */
+ char_u *ae_fname; // file name as specified
+ int ae_fnum; // buffer number with expanded file name
} aentry_T;
# define ALIST(win) (win)->w_alist
@@ -317,21 +320,21 @@ typedef struct argentry {
* Used for the typeahead buffer: typebuf.
*/
typedef struct {
- char_u *tb_buf; /* buffer for typed characters */
- char_u *tb_noremap; /* mapping flags for characters in tb_buf[] */
- int tb_buflen; /* size of tb_buf[] */
- int tb_off; /* current position in tb_buf[] */
- int tb_len; /* number of valid bytes in tb_buf[] */
- int tb_maplen; /* nr of mapped bytes in tb_buf[] */
- int tb_silent; /* nr of silently mapped bytes in tb_buf[] */
- int tb_no_abbr_cnt; /* nr of bytes without abbrev. in tb_buf[] */
- int tb_change_cnt; /* nr of time tb_buf was changed; never zero */
+ char_u *tb_buf; // buffer for typed characters
+ char_u *tb_noremap; // mapping flags for characters in tb_buf[]
+ int tb_buflen; // size of tb_buf[]
+ int tb_off; // current position in tb_buf[]
+ int tb_len; // number of valid bytes in tb_buf[]
+ int tb_maplen; // nr of mapped bytes in tb_buf[]
+ int tb_silent; // nr of silently mapped bytes in tb_buf[]
+ int tb_no_abbr_cnt; // nr of bytes without abbrev. in tb_buf[]
+ int tb_change_cnt; // nr of time tb_buf was changed; never zero
} typebuf_T;
-/* Struct to hold the saved typeahead for save_typeahead(). */
+// Struct to hold the saved typeahead for save_typeahead().
typedef struct {
typebuf_T save_typebuf;
- int typebuf_valid; /* TRUE when save_typebuf valid */
+ bool typebuf_valid; // true when save_typebuf valid
int old_char;
int old_mod_mask;
buffheader_T save_readbuf1;
@@ -362,15 +365,19 @@ struct mapblock {
*/
struct stl_hlrec {
char_u *start;
- int userhl; /* 0: no HL, 1-9: User HL, < 0 for syn ID */
+ int userhl; // 0: no HL, 1-9: User HL, < 0 for syn ID
};
-/* values for b_syn_spell: what to do with toplevel text */
-#define SYNSPL_DEFAULT 0 /* spell check if @Spell not defined */
-#define SYNSPL_TOP 1 /* spell check toplevel text */
-#define SYNSPL_NOTOP 2 /* don't spell check toplevel text */
+// values for b_syn_spell: what to do with toplevel text
+#define SYNSPL_DEFAULT 0 // spell check if @Spell not defined
+#define SYNSPL_TOP 1 // spell check toplevel text
+#define SYNSPL_NOTOP 2 // don't spell check toplevel text
-/* avoid #ifdefs for when b_spell is not available */
+// values for b_syn_foldlevel: how to compute foldlevel on a line
+#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;
@@ -379,10 +386,10 @@ typedef struct qf_info_S qf_info_T;
* Used for :syntime: timing of executing a syntax pattern.
*/
typedef struct {
- proftime_T total; /* total time used */
- proftime_T slowest; /* time of slowest call */
- long count; /* nr of times used */
- long match; /* nr of times matched */
+ proftime_T total; // total time used
+ proftime_T slowest; // time of slowest call
+ long count; // nr of times used
+ long match; // nr of times matched
} syn_time_T;
/*
@@ -395,6 +402,7 @@ typedef struct {
int b_syn_error; // TRUE when error occurred in HL
bool b_syn_slow; // true when 'redrawtime' reached
int b_syn_ic; // ignore case for :syn cmds
+ int b_syn_foldlevel; // how to compute foldlevel on a line
int b_syn_spell; // SYNSPL_ values
garray_T b_syn_patterns; // table for syntax patterns
garray_T b_syn_clusters; // table for syntax clusters
@@ -410,25 +418,23 @@ typedef struct {
char_u *b_syn_linecont_pat; // line continuation pattern
regprog_T *b_syn_linecont_prog; // line continuation program
syn_time_T b_syn_linecont_time;
- int b_syn_linecont_ic; /* ignore-case flag for above */
- int b_syn_topgrp; /* for ":syntax include" */
- int b_syn_conceal; /* auto-conceal for :syn cmds */
- int b_syn_folditems; /* number of patterns with the HL_FOLD
- flag set */
- /*
- * b_sst_array[] contains the state stack for a number of lines, for the
- * start of that line (col == 0). This avoids having to recompute the
- * syntax state too often.
- * b_sst_array[] is allocated to hold the state for all displayed lines,
- * and states for 1 out of about 20 other lines.
- * b_sst_array pointer to an array of synstate_T
- * b_sst_len number of entries in b_sst_array[]
- * b_sst_first pointer to first used entry in b_sst_array[] or NULL
- * b_sst_firstfree pointer to first free entry in b_sst_array[] or NULL
- * b_sst_freecount number of free entries in b_sst_array[]
- * b_sst_check_lnum entries after this lnum need to be checked for
- * validity (MAXLNUM means no check needed)
- */
+ int b_syn_linecont_ic; // ignore-case flag for above
+ int b_syn_topgrp; // for ":syntax include"
+ int b_syn_conceal; // auto-conceal for :syn cmds
+ int b_syn_folditems; // number of patterns with the HL_FOLD
+ // flag set
+ // b_sst_array[] contains the state stack for a number of lines, for the
+ // start of that line (col == 0). This avoids having to recompute the
+ // syntax state too often.
+ // b_sst_array[] is allocated to hold the state for all displayed lines,
+ // and states for 1 out of about 20 other lines.
+ // b_sst_array pointer to an array of synstate_T
+ // b_sst_len number of entries in b_sst_array[]
+ // b_sst_first pointer to first used entry in b_sst_array[] or NULL
+ // b_sst_firstfree pointer to first free entry in b_sst_array[] or NULL
+ // b_sst_freecount number of free entries in b_sst_array[]
+ // b_sst_check_lnum entries after this lnum need to be checked for
+ // validity (MAXLNUM means no check needed)
synstate_T *b_sst_array;
int b_sst_len;
synstate_T *b_sst_first;
@@ -457,11 +463,15 @@ typedef TV_DICTITEM_STRUCT(sizeof("changedtick")) ChangedtickDictItem;
typedef struct {
LuaRef on_lines;
+ LuaRef on_bytes;
LuaRef on_changedtick;
LuaRef on_detach;
bool utf_sizes;
} BufUpdateCallbacks;
-#define BUF_UPDATE_CALLBACKS_INIT { LUA_NOREF, LUA_NOREF, LUA_NOREF, false }
+#define BUF_UPDATE_CALLBACKS_INIT { LUA_NOREF, LUA_NOREF, LUA_NOREF, \
+ LUA_NOREF, false }
+
+EXTERN int curbuf_splice_pending INIT(= 0);
#define BUF_HAS_QF_ENTRY 1
#define BUF_HAS_LL_ENTRY 2
@@ -483,10 +493,10 @@ struct file_buffer {
memline_T b_ml; // associated memline (also contains line count
- buf_T *b_next; /* links in list of buffers */
+ buf_T *b_next; // links in list of buffers
buf_T *b_prev;
- int b_nwindows; /* nr of windows open on this buffer */
+ int b_nwindows; // nr of windows open on this buffer
int b_flags; // various BF_ flags
int b_locked; // Buffer is being closed or referenced, don't
@@ -527,24 +537,25 @@ struct file_buffer {
*/
bool b_mod_set; /* true when there are changes since the last
time the display was updated */
- linenr_T b_mod_top; /* topmost lnum that was changed */
- linenr_T b_mod_bot; /* lnum below last changed line, AFTER the
- change */
- long b_mod_xlines; /* number of extra buffer lines inserted;
- negative when lines were deleted */
-
- wininfo_T *b_wininfo; /* list of last used info for each window */
-
- long b_mtime; /* last change time of original file */
- long b_mtime_read; /* last change time when reading */
- uint64_t b_orig_size; /* size of original file in bytes */
- int b_orig_mode; /* mode of original file */
-
- fmark_T b_namedm[NMARKS]; /* current named marks (mark.c) */
-
- /* These variables are set when VIsual_active becomes FALSE */
+ linenr_T b_mod_top; // topmost lnum that was changed
+ linenr_T b_mod_bot; // lnum below last changed line, AFTER the
+ // change
+ long b_mod_xlines; // number of extra buffer lines inserted;
+ // negative when lines were deleted
+ wininfo_T *b_wininfo; // list of last used info for each window
+
+ long b_mtime; // last change time of original file
+ long b_mtime_read; // last change time when reading
+ uint64_t b_orig_size; // size of original file in bytes
+ int b_orig_mode; // mode of original file
+ time_t b_last_used; // time when the buffer was last used; used
+ // for viminfo
+
+ fmark_T b_namedm[NMARKS]; // current named marks (mark.c)
+
+ // These variables are set when VIsual_active becomes FALSE
visualinfo_T b_visual;
- int b_visual_mode_eval; /* b_visual.vi_mode for visualmode() */
+ int b_visual_mode_eval; // b_visual.vi_mode for visualmode()
fmark_T b_last_cursor; // cursor position when last unloading this
// buffer
@@ -555,8 +566,8 @@ struct file_buffer {
* the changelist contains old change positions
*/
fmark_T b_changelist[JUMPLISTSIZE];
- int b_changelistlen; /* number of active entries */
- bool b_new_change; /* set by u_savecommon() */
+ int b_changelistlen; // number of active entries
+ bool b_new_change; // set by u_savecommon()
/*
* Character table, only used in charset.c for 'iskeyword'
@@ -567,9 +578,9 @@ struct file_buffer {
// Table used for mappings local to a buffer.
mapblock_T *(b_maphash[MAX_MAPHASH]);
- /* First abbreviation local to a buffer. */
+ // First abbreviation local to a buffer.
mapblock_T *b_first_abbr;
- /* User commands local to the buffer. */
+ // User commands local to the buffer.
garray_T b_ucmds;
/*
* start and end of an operator, also used for '[ and ']
@@ -578,31 +589,31 @@ struct file_buffer {
pos_T b_op_start_orig; // used for Insstart_orig
pos_T b_op_end;
- bool b_marks_read; /* Have we read ShaDa marks yet? */
+ bool b_marks_read; // Have we read ShaDa marks yet?
/*
* The following only used in undo.c.
*/
- u_header_T *b_u_oldhead; /* pointer to oldest header */
- u_header_T *b_u_newhead; /* pointer to newest header; may not be valid
- if b_u_curhead is not NULL */
- u_header_T *b_u_curhead; /* pointer to current header */
- int b_u_numhead; /* current number of headers */
- bool b_u_synced; /* entry lists are synced */
- long b_u_seq_last; /* last used undo sequence number */
- long b_u_save_nr_last; /* counter for last file write */
- long b_u_seq_cur; /* hu_seq of header below which we are now */
- time_t b_u_time_cur; /* uh_time of header below which we are now */
- long b_u_save_nr_cur; /* file write nr after which we are now */
+ u_header_T *b_u_oldhead; // pointer to oldest header
+ u_header_T *b_u_newhead; // pointer to newest header; may not be valid
+ // if b_u_curhead is not NULL
+ u_header_T *b_u_curhead; // pointer to current header
+ int b_u_numhead; // current number of headers
+ bool b_u_synced; // entry lists are synced
+ long b_u_seq_last; // last used undo sequence number
+ long b_u_save_nr_last; // counter for last file write
+ long b_u_seq_cur; // hu_seq of header below which we are now
+ time_t b_u_time_cur; // uh_time of header below which we are now
+ long b_u_save_nr_cur; // file write nr after which we are now
/*
* variables for "U" command in undo.c
*/
- char_u *b_u_line_ptr; /* saved line for "U" command */
- linenr_T b_u_line_lnum; /* line number of line in u_line */
- colnr_T b_u_line_colnr; /* optional column number */
+ char_u *b_u_line_ptr; // saved line for "U" command
+ linenr_T b_u_line_lnum; // line number of line in u_line
+ colnr_T b_u_line_colnr; // optional column number
- bool b_scanned; /* ^N/^P have scanned this buffer */
+ bool b_scanned; // ^N/^P have scanned this buffer
// flags for use of ":lmap" and IM control
long b_p_iminsert; // input mode for insert
@@ -612,10 +623,10 @@ struct file_buffer {
#define B_IMODE_LMAP 1 // Input via langmap
# define B_IMODE_LAST 1
- short b_kmap_state; /* using "lmap" mappings */
-# define KEYMAP_INIT 1 /* 'keymap' was set, call keymap_init() */
-# define KEYMAP_LOADED 2 /* 'keymap' mappings have been loaded */
- garray_T b_kmap_ga; /* the keymap table */
+ int16_t b_kmap_state; // using "lmap" mappings
+# define KEYMAP_INIT 1 // 'keymap' was set, call keymap_init()
+# define KEYMAP_LOADED 2 // 'keymap' mappings have been loaded
+ garray_T b_kmap_ga; // the keymap table
/*
* Options local to a buffer.
@@ -647,6 +658,7 @@ struct file_buffer {
char_u *b_p_cpt; ///< 'complete'
char_u *b_p_cfu; ///< 'completefunc'
char_u *b_p_ofu; ///< 'omnifunc'
+ char_u *b_p_tfu; ///< 'tagfunc'
int b_p_eol; ///< 'endofline'
int b_p_fixeol; ///< 'fixendofline'
int b_p_et; ///< 'expandtab'
@@ -714,9 +726,9 @@ struct file_buffer {
int b_p_udf; ///< 'undofile'
char_u *b_p_lw; ///< 'lispwords' local value
- /* end of buffer options */
+ // end of buffer options
- /* values set from b_p_cino */
+ // values set from b_p_cino
int b_ind_level;
int b_ind_open_imag;
int b_ind_no_brace;
@@ -757,11 +769,11 @@ struct file_buffer {
linenr_T b_no_eol_lnum; /* non-zero lnum when last line of next binary
* write should not have an end-of-line */
- int b_start_eol; /* last line had eol when it was read */
- int b_start_ffc; /* first char of 'ff' when edit started */
- char_u *b_start_fenc; /* 'fileencoding' when edit started or NULL */
- int b_bad_char; /* "++bad=" argument when edit started or 0 */
- int b_start_bomb; /* 'bomb' when it was read */
+ int b_start_eol; // last line had eol when it was read
+ int b_start_ffc; // first char of 'ff' when edit started
+ char_u *b_start_fenc; // 'fileencoding' when edit started or NULL
+ int b_bad_char; // "++bad=" argument when edit started or 0
+ int b_start_bomb; // 'bomb' when it was read
ScopeDictDictItem b_bufvar; ///< Variable for "b:" Dictionary.
dict_T *b_vars; ///< b: scope dictionary.
@@ -785,6 +797,12 @@ struct file_buffer {
// are not used! Use the B_SPELL macro to
// access b_spell without #ifdef.
+ char_u *b_prompt_text; // set by prompt_setprompt()
+ Callback b_prompt_callback; // set by prompt_setcallback()
+ Callback b_prompt_interrupt; // set by prompt_setinterrupt()
+ int b_prompt_insert; // value for restart_edit when entering
+ // a prompt buffer window.
+
synblock_T b_s; // Info related to syntax highlighting. w_s
// normally points to this, but some windows
// may use a different synblock_T.
@@ -799,9 +817,9 @@ struct file_buffer {
int b_mapped_ctrl_c; // modes where CTRL-C is mapped
- BufhlInfo b_bufhl_info; // buffer stored highlights
-
- kvec_t(BufhlLine *) b_bufhl_move_space; // temporary space for highlights
+ MarkTree b_marktree[1];
+ Map(uint64_t, ExtmarkItem) *b_extmark_index;
+ Map(uint64_t, ExtmarkNs) *b_extmark_ns; // extmark namespaces
// array of channel_id:s which have asked to receive updates for this
// buffer.
@@ -823,6 +841,12 @@ struct file_buffer {
// The number for times the current line has been flushed in the memline.
int flush_count;
+ bool b_luahl;
+ LuaRef b_luahl_start;
+ LuaRef b_luahl_window;
+ LuaRef b_luahl_line;
+ LuaRef b_luahl_end;
+
int b_diff_failed; // internal diff failed for this buffer
};
@@ -848,8 +872,8 @@ struct file_buffer {
typedef struct diffblock_S diff_T;
struct diffblock_S {
diff_T *df_next;
- linenr_T df_lnum[DB_COUNT]; /* line number in buffer */
- linenr_T df_count[DB_COUNT]; /* nr of inserted/changed lines */
+ linenr_T df_lnum[DB_COUNT]; // line number in buffer
+ linenr_T df_count[DB_COUNT]; // nr of inserted/changed lines
};
#define SNAP_HELP_IDX 0
@@ -897,11 +921,11 @@ struct tabpage_S {
* wl_lnum and wl_lastlnum are invalid too.
*/
typedef struct w_line {
- linenr_T wl_lnum; /* buffer line number for logical line */
- uint16_t wl_size; /* height in screen lines */
- char wl_valid; /* TRUE values are valid for text in buffer */
- char wl_folded; /* TRUE when this is a range of folded lines */
- linenr_T wl_lastlnum; /* last buffer line number for logical line */
+ linenr_T wl_lnum; // buffer line number for logical line
+ uint16_t wl_size; // height in screen lines
+ char wl_valid; // TRUE values are valid for text in buffer
+ char wl_folded; // TRUE when this is a range of folded lines
+ linenr_T wl_lastlnum; // last buffer line number for logical line
} wline_T;
/*
@@ -909,24 +933,24 @@ typedef struct w_line {
* or row (FR_ROW) layout or is a leaf, which has a window.
*/
struct frame_S {
- char fr_layout; /* FR_LEAF, FR_COL or FR_ROW */
+ char fr_layout; // FR_LEAF, FR_COL or FR_ROW
int fr_width;
- int fr_newwidth; /* new width used in win_equal_rec() */
+ int fr_newwidth; // new width used in win_equal_rec()
int fr_height;
- int fr_newheight; /* new height used in win_equal_rec() */
- frame_T *fr_parent; /* containing frame or NULL */
- frame_T *fr_next; /* frame right or below in same parent, NULL
- for first */
- frame_T *fr_prev; /* frame left or above in same parent, NULL
- for last */
- /* fr_child and fr_win are mutually exclusive */
- frame_T *fr_child; /* first contained frame */
- win_T *fr_win; /* window that fills this frame */
+ int fr_newheight; // new height used in win_equal_rec()
+ frame_T *fr_parent; // containing frame or NULL
+ frame_T *fr_next; // frame right or below in same parent, NULL
+ // for last
+ frame_T *fr_prev; // frame left or above in same parent, NULL
+ // for first
+ // fr_child and fr_win are mutually exclusive
+ frame_T *fr_child; // first contained frame
+ win_T *fr_win; // window that fills this frame
};
-#define FR_LEAF 0 /* frame is a leaf */
-#define FR_ROW 1 /* frame with a row of windows */
-#define FR_COL 2 /* frame with a column of windows */
+#define FR_LEAF 0 // frame is a leaf
+#define FR_ROW 1 // frame with a row of windows
+#define FR_COL 2 // frame with a column of windows
/*
* Struct used for highlighting 'hlsearch' matches, matches defined by
@@ -1043,6 +1067,48 @@ typedef struct
pos_T w_cursor_corr; // corrected cursor position
} pos_save_T;
+/// Indices into vimmenu_T->strings[] and vimmenu_T->noremap[] for each mode
+/// \addtogroup MENU_INDEX
+/// @{
+enum {
+ MENU_INDEX_INVALID = -1,
+ MENU_INDEX_NORMAL = 0,
+ MENU_INDEX_VISUAL = 1,
+ MENU_INDEX_SELECT = 2,
+ MENU_INDEX_OP_PENDING = 3,
+ MENU_INDEX_INSERT = 4,
+ MENU_INDEX_CMDLINE = 5,
+ MENU_INDEX_TIP = 6,
+ MENU_MODES = 7,
+};
+
+typedef struct VimMenu vimmenu_T;
+
+struct VimMenu {
+ int modes; ///< Which modes is this menu visible for
+ int enabled; ///< for which modes the menu is enabled
+ char_u *name; ///< Name of menu, possibly translated
+ char_u *dname; ///< Displayed Name ("name" without '&')
+ char_u *en_name; ///< "name" untranslated, NULL when
+ ///< was not translated
+ char_u *en_dname; ///< NULL when "dname" untranslated
+ int mnemonic; ///< mnemonic key (after '&')
+ char_u *actext; ///< accelerator text (after TAB)
+ long priority; ///< Menu order priority
+ char_u *strings[MENU_MODES]; ///< Mapped string for each mode
+ int noremap[MENU_MODES]; ///< A \ref REMAP_VALUES flag for each mode
+ bool silent[MENU_MODES]; ///< A silent flag for each mode
+ vimmenu_T *children; ///< Children of sub-menu
+ vimmenu_T *parent; ///< Parent of menu
+ vimmenu_T *next; ///< Next item in menu
+};
+
+typedef struct {
+ int wb_startcol;
+ int wb_endcol;
+ vimmenu_T *wb_menu;
+} winbar_item_T;
+
/// Structure which contains all information that belongs to a window.
///
/// All row numbers are relative to the start of the window, except w_winrow.
@@ -1111,6 +1177,9 @@ struct window_S {
int stlnc;
int vert;
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;
@@ -1124,16 +1193,16 @@ struct window_S {
top of the window */
char w_topline_was_set; /* flag set to TRUE when topline is set,
e.g. by winrestview() */
- int w_topfill; /* number of filler lines above w_topline */
- int w_old_topfill; /* w_topfill at last redraw */
- bool w_botfill; /* true when filler lines are actually
- below w_topline (at end of file) */
- bool w_old_botfill; /* w_botfill at last redraw */
- colnr_T w_leftcol; /* window column number of the left most
- character in the window; used when
- 'wrap' is off */
- colnr_T w_skipcol; /* starting column when a single line
- doesn't fit in the window */
+ int w_topfill; // number of filler lines above w_topline
+ int w_old_topfill; // w_topfill at last redraw
+ bool w_botfill; // true when filler lines are actually
+ // below w_topline (at end of file)
+ bool w_old_botfill; // w_botfill at last redraw
+ colnr_T w_leftcol; // window column number of the left most
+ // character in the window; used when
+ // 'wrap' is off
+ colnr_T w_skipcol; // starting column when a single line
+ // doesn't fit in the window
//
// Layout of the window in the screen.
@@ -1141,7 +1210,7 @@ struct window_S {
//
int w_winrow; // first row of window in screen
int w_height; // number of rows in window, excluding
- // status/command line(s)
+ // status/command/winbar line(s)
int w_status_height; // number of status lines (0 or 1)
int w_wincol; // Leftmost column of window in screen.
int w_width; // Width of window, excluding separation.
@@ -1167,16 +1236,18 @@ struct window_S {
int w_valid;
pos_T w_valid_cursor; /* last known position of w_cursor, used
to adjust w_valid */
- colnr_T w_valid_leftcol; /* last known w_leftcol */
+ colnr_T w_valid_leftcol; // last known w_leftcol
+
+ bool w_viewport_invalid;
/*
* w_cline_height is the number of physical lines taken by the buffer line
* that the cursor is on. We use this to avoid extra calls to plines().
*/
- int w_cline_height; /* current size of cursor line */
- bool w_cline_folded; /* cursor line is folded */
+ int w_cline_height; // current size of cursor line
+ bool w_cline_folded; // cursor line is folded
- int w_cline_row; /* starting row of the cursor line */
+ int w_cline_row; // starting row of the cursor line
colnr_T w_virtcol; // column number of the cursor in the
// buffer line, as opposed to the column
@@ -1190,7 +1261,7 @@ struct window_S {
* This is related to positions in the window, not in the display or
* buffer, thus w_wrow is relative to w_winrow.
*/
- int w_wrow, w_wcol; /* cursor position in window */
+ int w_wrow, w_wcol; // cursor position in window
linenr_T w_botline; // number of the line below the bottom of
// the window
@@ -1208,65 +1279,70 @@ struct window_S {
* what is currently displayed. wl_valid is reset to indicated this.
* This is used for efficient redrawing.
*/
- int w_lines_valid; /* number of valid entries */
+ int w_lines_valid; // number of valid entries
wline_T *w_lines;
- garray_T w_folds; /* array of nested folds */
- bool w_fold_manual; /* when true: some folds are opened/closed
- manually */
- bool w_foldinvalid; /* when true: folding needs to be
- recomputed */
- int w_nrwidth; /* width of 'number' and 'relativenumber'
- column being used */
+ garray_T w_folds; // array of nested folds
+ bool w_fold_manual; // when true: some folds are opened/closed
+ // manually
+ bool w_foldinvalid; // when true: folding needs to be
+ // recomputed
+ int w_nrwidth; // width of 'number' and 'relativenumber'
+ // column being used
/*
* === end of cached values ===
*/
- int w_redr_type; /* type of redraw to be performed on win */
- int w_upd_rows; /* number of window lines to update when
- w_redr_type is REDRAW_TOP */
- linenr_T w_redraw_top; /* when != 0: first line needing redraw */
- linenr_T w_redraw_bot; /* when != 0: last line needing redraw */
- int w_redr_status; /* if TRUE status line must be redrawn */
+ int w_redr_type; // type of redraw to be performed on win
+ int w_upd_rows; // number of window lines to update when
+ // w_redr_type is REDRAW_TOP
+ linenr_T w_redraw_top; // when != 0: first line needing redraw
+ linenr_T w_redraw_bot; // when != 0: last line needing redraw
+ int w_redr_status; // if TRUE status line must be redrawn
- /* remember what is shown in the ruler for this window (if 'ruler' set) */
- pos_T w_ru_cursor; /* cursor position shown in ruler */
- colnr_T w_ru_virtcol; /* virtcol shown in ruler */
- linenr_T w_ru_topline; /* topline shown in ruler */
- linenr_T w_ru_line_count; /* line count used for ruler */
- int w_ru_topfill; /* topfill shown in ruler */
- char w_ru_empty; /* TRUE if ruler shows 0-1 (empty line) */
+ // remember what is shown in the ruler for this window (if 'ruler' set)
+ pos_T w_ru_cursor; // cursor position shown in ruler
+ colnr_T w_ru_virtcol; // virtcol shown in ruler
+ linenr_T w_ru_topline; // topline shown in ruler
+ linenr_T w_ru_line_count; // line count used for ruler
+ int w_ru_topfill; // topfill shown in ruler
+ char w_ru_empty; // TRUE if ruler shows 0-1 (empty line)
- int w_alt_fnum; /* alternate file (for # and CTRL-^) */
+ int w_alt_fnum; // alternate file (for # and CTRL-^)
- alist_T *w_alist; /* pointer to arglist for this window */
- int w_arg_idx; /* current index in argument list (can be
- out of range!) */
- int w_arg_idx_invalid; /* editing another file than w_arg_idx */
+ alist_T *w_alist; // pointer to arglist for this window
+ int w_arg_idx; // current index in argument list (can be
+ // out of range!)
+ int w_arg_idx_invalid; // editing another file than w_arg_idx
char_u *w_localdir; /* absolute path of local directory or
NULL */
- /*
- * Options local to a window.
- * They are local because they influence the layout of the window or
- * depend on the window layout.
- * There are two values: w_onebuf_opt is local to the buffer currently in
- * this window, w_allbuf_opt is for all buffers in this window.
- */
+ vimmenu_T *w_winbar; // The root of the WinBar menu hierarchy.
+ winbar_item_T *w_winbar_items; // list of items in the WinBar
+ int w_winbar_height; // 1 if there is a window toolbar
+
+ // Options local to a window.
+ // They are local because they influence the layout of the window or
+ // depend on the window layout.
+ // There are two values: w_onebuf_opt is local to the buffer currently in
+ // this window, w_allbuf_opt is for all buffers in this window.
winopt_T w_onebuf_opt;
winopt_T w_allbuf_opt;
- /* A few options have local flags for P_INSECURE. */
- uint32_t w_p_stl_flags; /* flags for 'statusline' */
- uint32_t w_p_fde_flags; /* flags for 'foldexpr' */
- uint32_t w_p_fdt_flags; /* flags for 'foldtext' */
- int *w_p_cc_cols; /* array of columns to highlight or NULL */
- int w_p_brimin; /* minimum width for breakindent */
- int w_p_brishift; /* additional shift for breakindent */
- bool w_p_brisbr; /* sbr in 'briopt' */
+ // A few options have local flags for P_INSECURE.
+ uint32_t w_p_stl_flags; // flags for 'statusline'
+ uint32_t w_p_fde_flags; // flags for 'foldexpr'
+ uint32_t w_p_fdt_flags; // flags for 'foldtext'
+ int *w_p_cc_cols; // array of columns to highlight or NULL
+ long w_p_siso; // 'sidescrolloff' local value
+ long w_p_so; // 'scrolloff' local value
- /* transform a pointer to a "onebuf" option into a "allbuf" option */
+ int w_briopt_min; // minimum width for breakindent
+ int w_briopt_shift; // additional shift for breakindent
+ bool w_briopt_sbr; // sbr in 'briopt'
+
+ // transform a pointer to a "onebuf" option into a "allbuf" option
#define GLOBAL_WO(p) ((char *)p + sizeof(winopt_T))
long w_scbind_pos;
@@ -1279,20 +1355,20 @@ struct window_S {
* a new line after setting the w_pcmark. If not, then we revert to
* using the previous w_pcmark.
*/
- pos_T w_pcmark; /* previous context mark */
- pos_T w_prev_pcmark; /* previous w_pcmark */
+ pos_T w_pcmark; // previous context mark
+ pos_T w_prev_pcmark; // previous w_pcmark
/*
* the jumplist contains old cursor positions
*/
xfmark_T w_jumplist[JUMPLISTSIZE];
- int w_jumplistlen; /* number of active entries */
- int w_jumplistidx; /* current position */
+ int w_jumplistlen; // number of active entries
+ int w_jumplistidx; // current position
- int w_changelistidx; /* current position in b_changelist */
+ int w_changelistidx; // current position in b_changelist
- matchitem_T *w_match_head; /* head of match list */
- int w_next_match_id; /* next match ID */
+ matchitem_T *w_match_head; // head of match list
+ int w_next_match_id; // next match ID
/*
* the tagstack grows from 0 upwards:
@@ -1300,9 +1376,9 @@ struct window_S {
* entry 1: newer
* entry 2: newest
*/
- taggy_T w_tagstack[TAGSTACKSIZE]; /* the tag stack */
- int w_tagstackidx; /* idx just below active entry */
- int w_tagstacklen; /* number of tags on stack */
+ taggy_T w_tagstack[TAGSTACKSIZE]; // the tag stack
+ int w_tagstackidx; // idx just below active entry
+ int w_tagstacklen; // number of tags on stack
ScreenGrid w_grid; // the grid specific to the window
bool w_pos_changed; // true if window position changed
@@ -1320,13 +1396,11 @@ struct window_S {
linenr_T w_nrwidth_line_count; /* line count when ml_nrwidth_width
* was computed. */
- int w_nrwidth_width; /* nr of chars to print line count. */
+ int w_nrwidth_width; // nr of chars to print line count.
- qf_info_T *w_llist; /* Location list for this window */
- /*
- * Location list reference used in the location list window.
- * In a non-location list window, w_llist_ref is NULL.
- */
+ qf_info_T *w_llist; // Location list for this window
+ // Location list reference used in the location list window.
+ // In a non-location list window, w_llist_ref is NULL.
qf_info_T *w_llist_ref;
};
diff --git a/src/nvim/buffer_updates.c b/src/nvim/buffer_updates.c
index 3604578b50..e6393bf02c 100644
--- a/src/nvim/buffer_updates.c
+++ b/src/nvim/buffer_updates.c
@@ -157,7 +157,7 @@ void buf_updates_unregister_all(buf_T *buf)
args.items[0] = BUFFER_OBJ(buf->handle);
textlock++;
- executor_exec_lua_cb(cb.on_detach, "detach", args, false);
+ executor_exec_lua_cb(cb.on_detach, "detach", args, false, NULL);
textlock--;
}
free_update_callbacks(cb);
@@ -265,7 +265,7 @@ void buf_updates_send_changes(buf_T *buf,
args.items[7] = INTEGER_OBJ((Integer)deleted_codeunits);
}
textlock++;
- Object res = executor_exec_lua_cb(cb.on_lines, "lines", args, true);
+ Object res = executor_exec_lua_cb(cb.on_lines, "lines", args, true, NULL);
textlock--;
if (res.type == kObjectTypeBoolean && res.data.boolean == true) {
@@ -281,6 +281,51 @@ void buf_updates_send_changes(buf_T *buf,
kv_size(buf->update_callbacks) = j;
}
+void buf_updates_send_splice(buf_T *buf,
+ linenr_T start_line, colnr_T start_col,
+ linenr_T oldextent_line, colnr_T oldextent_col,
+ linenr_T newextent_line, colnr_T newextent_col)
+{
+ if (!buf_updates_active(buf)) {
+ return;
+ }
+
+ // notify each of the active callbakcs
+ size_t j = 0;
+ for (size_t i = 0; i < kv_size(buf->update_callbacks); i++) {
+ BufUpdateCallbacks cb = kv_A(buf->update_callbacks, i);
+ bool keep = true;
+ if (cb.on_bytes != LUA_NOREF) {
+ FIXED_TEMP_ARRAY(args, 8);
+
+ // the first argument is always the buffer handle
+ args.items[0] = BUFFER_OBJ(buf->handle);
+
+ // next argument is b:changedtick
+ args.items[1] = INTEGER_OBJ(buf_get_changedtick(buf));
+
+ args.items[2] = INTEGER_OBJ(start_line);
+ args.items[3] = INTEGER_OBJ(start_col);
+ args.items[4] = INTEGER_OBJ(oldextent_line);
+ args.items[5] = INTEGER_OBJ(oldextent_col);
+ args.items[6] = INTEGER_OBJ(newextent_line);
+ args.items[7] = INTEGER_OBJ(newextent_col);
+
+ textlock++;
+ Object res = executor_exec_lua_cb(cb.on_bytes, "bytes", args, true, NULL);
+ textlock--;
+
+ if (res.type == kObjectTypeBoolean && res.data.boolean == true) {
+ free_update_callbacks(cb);
+ keep = false;
+ }
+ }
+ if (keep) {
+ kv_A(buf->update_callbacks, j++) = kv_A(buf->update_callbacks, i);
+ }
+ }
+ kv_size(buf->update_callbacks) = j;
+}
void buf_updates_changedtick(buf_T *buf)
{
// notify each of the active channels
@@ -293,10 +338,7 @@ void buf_updates_changedtick(buf_T *buf)
BufUpdateCallbacks cb = kv_A(buf->update_callbacks, i);
bool keep = true;
if (cb.on_changedtick != LUA_NOREF) {
- Array args = ARRAY_DICT_INIT;
- Object items[2];
- args.size = 2;
- args.items = items;
+ FIXED_TEMP_ARRAY(args, 2);
// the first argument is always the buffer handle
args.items[0] = BUFFER_OBJ(buf->handle);
@@ -306,7 +348,7 @@ void buf_updates_changedtick(buf_T *buf)
textlock++;
Object res = executor_exec_lua_cb(cb.on_changedtick, "changedtick",
- args, true);
+ args, true, NULL);
textlock--;
if (res.type == kObjectTypeBoolean && res.data.boolean == true) {
diff --git a/src/nvim/bufhl_defs.h b/src/nvim/bufhl_defs.h
deleted file mode 100644
index d0fb40ab88..0000000000
--- a/src/nvim/bufhl_defs.h
+++ /dev/null
@@ -1,41 +0,0 @@
-#ifndef NVIM_BUFHL_DEFS_H
-#define NVIM_BUFHL_DEFS_H
-
-#include "nvim/pos.h"
-#include "nvim/lib/kvec.h"
-#include "nvim/lib/kbtree.h"
-
-// bufhl: buffer specific highlighting
-
-typedef struct {
- int src_id;
- int hl_id; // highlight group
- colnr_T start; // first column to highlight
- colnr_T stop; // last column to highlight
-} BufhlItem;
-
-typedef struct {
- char *text;
- int hl_id;
-} VirtTextChunk;
-
-typedef kvec_t(VirtTextChunk) VirtText;
-
-typedef struct {
- linenr_T line;
- kvec_t(BufhlItem) items;
- int virt_text_src;
- VirtText virt_text;
-} BufhlLine;
-#define BUFHLLINE_INIT(l) { l, KV_INITIAL_VALUE, 0, KV_INITIAL_VALUE }
-
-typedef struct {
- BufhlLine *line;
- int current;
- colnr_T valid_to;
-} BufhlLineInfo;
-
-#define BUFHL_CMP(a, b) ((int)(((a)->line - (b)->line)))
-KBTREE_INIT(bufhl, BufhlLine *, BUFHL_CMP, 10) // -V512
-typedef kbtree_t(bufhl) BufhlInfo;
-#endif // NVIM_BUFHL_DEFS_H
diff --git a/src/nvim/change.c b/src/nvim/change.c
index ba80e71ae6..51afb40b40 100644
--- a/src/nvim/change.c
+++ b/src/nvim/change.c
@@ -17,6 +17,7 @@
#include "nvim/indent.h"
#include "nvim/indent_c.h"
#include "nvim/mark.h"
+#include "nvim/extmark.h"
#include "nvim/memline.h"
#include "nvim/misc1.h"
#include "nvim/move.h"
@@ -358,6 +359,19 @@ void changed_bytes(linenr_T lnum, colnr_T col)
}
}
+/// insert/delete bytes at column
+///
+/// Like changed_bytes() but also adjust extmark for "new" bytes.
+/// When "new" is negative text was deleted.
+static void inserted_bytes(linenr_T lnum, colnr_T col, int old, int new)
+{
+ if (curbuf_splice_pending == 0) {
+ extmark_splice(curbuf, (int)lnum-1, col, 0, old, 0, new, kExtmarkUndo);
+ }
+
+ changed_bytes(lnum, col);
+}
+
/// Appended "count" lines below line "lnum" in the current buffer.
/// Must be called AFTER the change and after mark_adjust().
/// Takes care of marking the buffer to be redrawn and sets the changed flag.
@@ -372,7 +386,10 @@ void appended_lines_mark(linenr_T lnum, long count)
// Skip mark_adjust when adding a line after the last one, there can't
// be marks there. But it's still needed in diff mode.
if (lnum + count < curbuf->b_ml.ml_line_count || curwin->w_p_diff) {
- mark_adjust(lnum + 1, (linenr_T)MAXLNUM, count, 0L, false);
+ mark_adjust(lnum + 1, (linenr_T)MAXLNUM, count, 0L, kExtmarkUndo);
+ } else {
+ extmark_adjust(curbuf, lnum + 1, (linenr_T)MAXLNUM, count, 0L,
+ kExtmarkUndo);
}
changed_lines(lnum + 1, 0, lnum + 1, count, true);
}
@@ -390,7 +407,8 @@ void deleted_lines(linenr_T lnum, long count)
/// be triggered to display the cursor.
void deleted_lines_mark(linenr_T lnum, long count)
{
- mark_adjust(lnum, (linenr_T)(lnum + count - 1), (long)MAXLNUM, -count, false);
+ mark_adjust(lnum, (linenr_T)(lnum + count - 1), (long)MAXLNUM, -count,
+ kExtmarkUndo);
changed_lines(lnum, 0, lnum + count, -count, true);
}
@@ -628,7 +646,7 @@ void ins_char_bytes(char_u *buf, size_t charlen)
ml_replace(lnum, newp, false);
// mark the buffer as changed and prepare for displaying
- changed_bytes(lnum, (colnr_T)col);
+ inserted_bytes(lnum, (colnr_T)col, (int)oldlen, (int)newlen);
// If we're in Insert or Replace mode and 'showmatch' is set, then briefly
// show the match for right parens and braces.
@@ -674,7 +692,7 @@ void ins_str(char_u *s)
assert(bytes >= 0);
memmove(newp + col + newlen, oldp + col, (size_t)bytes);
ml_replace(lnum, newp, false);
- changed_bytes(lnum, col);
+ inserted_bytes(lnum, col, 0, newlen);
curwin->w_cursor.col += newlen;
}
@@ -737,7 +755,7 @@ int del_bytes(colnr_T count, bool fixpos_arg, bool use_delcombine)
}
// If "count" is negative the caller must be doing something wrong.
if (count < 1) {
- IEMSGN("E950: Invalid count for del_bytes(): %ld", count);
+ IEMSGN("E292: Invalid count for del_bytes(): %ld", count);
return FAIL;
}
@@ -795,7 +813,7 @@ int del_bytes(colnr_T count, bool fixpos_arg, bool use_delcombine)
}
// mark the buffer as changed and prepare for displaying
- changed_bytes(lnum, curwin->w_cursor.col);
+ inserted_bytes(lnum, col, count, 0);
return OK;
}
@@ -951,6 +969,9 @@ int open_line(
bool did_append; // appended a new line
int saved_pi = curbuf->b_p_pi; // copy of preserveindent setting
+ linenr_T lnum = curwin->w_cursor.lnum;
+ colnr_T mincol = curwin->w_cursor.col + 1;
+
// make a copy of the current line so we can mess with it
char_u *saved_line = vim_strsave(get_cursor_line_ptr());
@@ -1560,6 +1581,7 @@ int open_line(
end_comment_pending = NUL; // turns out there was no leader
}
+ curbuf_splice_pending++;
old_cursor = curwin->w_cursor;
if (dir == BACKWARD) {
curwin->w_cursor.lnum--;
@@ -1574,7 +1596,8 @@ int open_line(
// be marks there. But still needed in diff mode.
if (curwin->w_cursor.lnum + 1 < curbuf->b_ml.ml_line_count
|| curwin->w_p_diff) {
- mark_adjust(curwin->w_cursor.lnum + 1, (linenr_T)MAXLNUM, 1L, 0L, false);
+ mark_adjust(curwin->w_cursor.lnum + 1, (linenr_T)MAXLNUM, 1L, 0L,
+ kExtmarkUndo);
}
did_append = true;
} else {
@@ -1614,7 +1637,7 @@ int open_line(
// it. It gets restored at the function end.
curbuf->b_p_pi = true;
} else {
- (void)set_indent(newindent, SIN_INSERT);
+ (void)set_indent(newindent, SIN_INSERT|SIN_NOMARK);
}
less_cols -= curwin->w_cursor.col;
@@ -1665,6 +1688,11 @@ int open_line(
curwin->w_cursor.col + less_cols_off,
1L, (long)-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
+ int cols_added = mincol-1+less_cols_off-less_cols;
+ extmark_splice(curbuf, (int)lnum-1, mincol-1, 0, less_cols_off,
+ 1, cols_added, kExtmarkUndo);
} else {
changed_bytes(curwin->w_cursor.lnum, curwin->w_cursor.col);
}
@@ -1676,7 +1704,10 @@ int open_line(
}
if (did_append) {
changed_lines(curwin->w_cursor.lnum, 0, curwin->w_cursor.lnum, 1L, true);
+ extmark_splice(curbuf, (int)curwin->w_cursor.lnum-1,
+ 0, 0, 0, 1, 0, kExtmarkUndo);
}
+ curbuf_splice_pending--;
curwin->w_cursor.col = newcol;
curwin->w_cursor.coladd = 0;
diff --git a/src/nvim/channel.c b/src/nvim/channel.c
index f9102fa0e2..37cbfb968b 100644
--- a/src/nvim/channel.c
+++ b/src/nvim/channel.c
@@ -11,11 +11,14 @@
#include "nvim/msgpack_rpc/channel.h"
#include "nvim/msgpack_rpc/server.h"
#include "nvim/os/shell.h"
+#ifdef WIN32
+# include "nvim/os/pty_conpty_win.h"
+# include "nvim/os/os_win_console.h"
+#endif
#include "nvim/path.h"
#include "nvim/ascii.h"
static bool did_stdio = false;
-PMap(uint64_t) *channels = NULL;
/// next free id for a job or rpc channel
/// 1 is reserved for stdio channel
@@ -272,11 +275,36 @@ static void close_cb(Stream *stream, void *data)
channel_decref(data);
}
+
+/// Starts a job and returns the associated channel
+///
+/// @param[in] argv Arguments vector specifying the command to run,
+/// NULL-terminated
+/// @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
+/// @param[in] pty True if the job should run attached to a pty
+/// @param[in] rpc True to communicate with the job using msgpack-rpc,
+/// `on_stdout` is ignored
+/// @param[in] detach True if the job should not be killed when nvim exits,
+/// ignored if `pty` is true
+/// @param[in] cwd Initial working directory for the job. Nvim's working
+/// directory if `cwd` is NULL
+/// @param[in] pty_width Width of the pty, ignored if `pty` is false
+/// @param[in] pty_height Height of the pty, ignored if `pty` is false
+/// @param[in] term_name `$TERM` for the pty
+/// @param[in] env Nvim's configured environment is used if this is NULL,
+/// otherwise defines all environment variables
+/// @param[out] status_out 0 for invalid arguments, > 0 for the channel id,
+/// < 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 detach, const char *cwd,
+ bool pty, bool rpc, bool overlapped, bool detach,
+ const char *cwd,
uint16_t pty_width, uint16_t pty_height,
- char *term_name, varnumber_T *status_out)
+ char *term_name, char **env, varnumber_T *status_out)
{
assert(cwd == NULL || os_isdir_executable(cwd));
@@ -314,6 +342,8 @@ Channel *channel_job_start(char **argv, CallbackReader on_stdout,
proc->events = chan->events;
proc->detach = detach;
proc->cwd = cwd;
+ proc->env = env;
+ proc->overlapped = overlapped;
char *cmd = xstrdup(proc->argv[0]);
bool has_out, has_err;
@@ -328,6 +358,7 @@ Channel *channel_job_start(char **argv, CallbackReader on_stdout,
if (status) {
EMSG3(_(e_jobspawn), os_strerror(status), cmd);
xfree(cmd);
+ os_free_fullenv(proc->env);
if (proc->type == kProcessTypePty) {
xfree(chan->stream.pty.term_name);
}
@@ -336,6 +367,8 @@ Channel *channel_job_start(char **argv, CallbackReader on_stdout,
return NULL;
}
xfree(cmd);
+ os_free_fullenv(proc->env);
+
wstream_init(&proc->in, 0);
if (has_out) {
@@ -441,8 +474,20 @@ uint64_t channel_from_stdio(bool rpc, CallbackReader on_output,
Channel *channel = channel_alloc(kChannelStreamStdio);
- rstream_init_fd(&main_loop, &channel->stream.stdio.in, 0, 0);
- wstream_init_fd(&main_loop, &channel->stream.stdio.out, 1, 0);
+ int stdin_dup_fd = STDIN_FILENO;
+ int stdout_dup_fd = STDOUT_FILENO;
+#ifdef WIN32
+ // Strangely, ConPTY doesn't work if stdin and stdout are pipes. So replace
+ // stdin and stdout with CONIN$ and CONOUT$, respectively.
+ if (embedded_mode && os_has_conpty_working()) {
+ stdin_dup_fd = os_dup(STDIN_FILENO);
+ os_replace_stdin_to_conin();
+ stdout_dup_fd = os_dup(STDOUT_FILENO);
+ os_replace_stdout_and_stderr_to_conout();
+ }
+#endif
+ rstream_init_fd(&main_loop, &channel->stream.stdio.in, stdin_dup_fd, 0);
+ wstream_init_fd(&main_loop, &channel->stream.stdio.out, stdout_dup_fd, 0);
if (rpc) {
rpc_start(channel);
diff --git a/src/nvim/channel.h b/src/nvim/channel.h
index c733e276be..9d26852ce5 100644
--- a/src/nvim/channel.h
+++ b/src/nvim/channel.h
@@ -85,7 +85,7 @@ struct Channel {
bool callback_scheduled;
};
-EXTERN PMap(uint64_t) *channels;
+EXTERN PMap(uint64_t) *channels INIT(= NULL);
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "channel.h.generated.h"
diff --git a/src/nvim/charset.c b/src/nvim/charset.c
index 78e7861d9d..f9d5adbc12 100644
--- a/src/nvim/charset.c
+++ b/src/nvim/charset.c
@@ -8,7 +8,6 @@
#include <assert.h>
#include <string.h>
#include <wctype.h>
-#include <wchar.h> // for towupper() and towlower()
#include <inttypes.h>
#include "nvim/vim.h"
@@ -1087,8 +1086,6 @@ int win_lbr_chartabsize(win_T *wp, char_u *line, char_u *s, colnr_T col, int *he
}
if (col == 0 || (col + size + sbrlen > (colnr_T)wp->w_width_inner)) {
- added = 0;
-
if (*p_sbr != NUL) {
if (size + sbrlen + numberwidth > (colnr_T)wp->w_width_inner) {
// Calculate effective window width.
@@ -1573,6 +1570,7 @@ char_u* skiptohex(char_u *q)
///
/// @return Pointer to the next whitespace or NUL character.
char_u *skiptowhite(const char_u *p)
+ FUNC_ATTR_NONNULL_ALL
{
while (*p != ' ' && *p != '\t' && *p != NUL) {
p++;
diff --git a/src/nvim/context.c b/src/nvim/context.c
index 2f872ff2bf..1ae0510762 100644
--- a/src/nvim/context.c
+++ b/src/nvim/context.c
@@ -254,7 +254,7 @@ 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_command_output(cstr_as_string(cmd), &err);
+ String func_body = nvim_exec(cstr_as_string(cmd), true, &err);
xfree(cmd);
if (!ERROR_SET(&err)) {
ADD(ctx->funcs, STRING_OBJ(func_body));
diff --git a/src/nvim/cursor.c b/src/nvim/cursor.c
index f2b3cfe690..036ae32589 100644
--- a/src/nvim/cursor.c
+++ b/src/nvim/cursor.c
@@ -93,11 +93,12 @@ int coladvance(colnr_T wcol)
static int coladvance2(
pos_T *pos,
- bool addspaces, /* change the text to achieve our goal? */
- bool finetune, /* change char offset for the exact column */
- colnr_T wcol /* column to move to */
+ bool addspaces, // change the text to achieve our goal?
+ bool finetune, // change char offset for the exact column
+ colnr_T wcol_arg // column to move to (can be negative)
)
{
+ colnr_T wcol = wcol_arg;
int idx;
char_u *ptr;
char_u *line;
@@ -165,6 +166,7 @@ static int coladvance2(
if (virtual_active()
&& addspaces
+ && wcol >= 0
&& ((col != wcol && col != wcol + 1) || csize > 1)) {
/* 'virtualedit' is set: The difference between wcol and col is
* filled with spaces. */
@@ -244,8 +246,9 @@ static int coladvance2(
mark_mb_adjustpos(curbuf, pos);
}
- if (col < wcol)
+ if (wcol < 0 || col < wcol) {
return FAIL;
+ }
return OK;
}
diff --git a/src/nvim/cursor_shape.h b/src/nvim/cursor_shape.h
index 2c466603f0..a23fa6836d 100644
--- a/src/nvim/cursor_shape.h
+++ b/src/nvim/cursor_shape.h
@@ -33,11 +33,11 @@ SHAPE_HOR = 1, ///< horizontal bar cursor
SHAPE_VER = 2 ///< vertical bar cursor
} CursorShape;
-#define MSHAPE_NUMBERED 1000 /* offset for shapes identified by number */
-#define MSHAPE_HIDE 1 /* hide mouse pointer */
+#define MSHAPE_NUMBERED 1000 // offset for shapes identified by number
+#define MSHAPE_HIDE 1 // hide mouse pointer
-#define SHAPE_MOUSE 1 /* used for mouse pointer shape */
-#define SHAPE_CURSOR 2 /* used for text cursor shape */
+#define SHAPE_MOUSE 1 // used for mouse pointer shape
+#define SHAPE_CURSOR 2 // used for text cursor shape
typedef struct cursor_entry {
char *full_name; ///< mode description
diff --git a/src/nvim/diff.c b/src/nvim/diff.c
index db3ef7ac47..3de5fc49bd 100644
--- a/src/nvim/diff.c
+++ b/src/nvim/diff.c
@@ -44,7 +44,7 @@
#include "nvim/os/shell.h"
static int diff_busy = false; // using diff structs, don't change them
-static int diff_need_update = false; // ex_diffupdate needs to be called
+static bool diff_need_update = false; // ex_diffupdate needs to be called
// Flags obtained from the 'diffopt' option
#define DIFF_FILLER 0x001 // display filler lines
@@ -57,8 +57,9 @@ static int diff_need_update = false; // ex_diffupdate needs to be called
#define DIFF_VERTICAL 0x080 // vertical splits
#define DIFF_HIDDEN_OFF 0x100 // diffoff when hidden
#define DIFF_INTERNAL 0x200 // use internal xdiff algorithm
+#define DIFF_CLOSE_OFF 0x400 // diffoff when closing window
#define ALL_WHITE_DIFF (DIFF_IWHITE | DIFF_IWHITEALL | DIFF_IWHITEEOL)
-static int diff_flags = DIFF_INTERNAL | DIFF_FILLER;
+static int diff_flags = DIFF_INTERNAL | DIFF_FILLER | DIFF_CLOSE_OFF;
static long diff_algorithm = 0;
@@ -490,7 +491,8 @@ static void diff_mark_adjust_tp(tabpage_T *tp, int idx, linenr_T line1,
}
if (tp == curtab) {
- diff_redraw(true);
+ // Don't redraw right away, this updates the diffs, which can be slow.
+ need_diff_redraw = true;
// Need to recompute the scroll binding, may remove or add filler
// lines (e.g., when adding lines above w_topline). But it's slow when
@@ -634,8 +636,9 @@ static int diff_check_sanity(tabpage_T *tp, diff_T *dp)
/// Mark all diff buffers in the current tab page for redraw.
///
/// @param dofold Also recompute the folds
-static void diff_redraw(int dofold)
+void diff_redraw(bool dofold)
{
+ need_diff_redraw = false;
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
if (!wp->w_p_diff) {
continue;
@@ -645,8 +648,8 @@ static void diff_redraw(int dofold)
foldUpdateAll(wp);
}
- /* A change may have made filler lines invalid, need to take care
- * of that for other windows. */
+ // A change may have made filler lines invalid, need to take care
+ // of that for other windows.
int n = diff_check(wp, wp->w_topline);
if (((wp != curwin) && (wp->w_topfill > 0)) || (n > 0)) {
@@ -723,8 +726,8 @@ static int diff_write_buffer(buf_T *buf, diffin_T *din)
char_u cbuf[MB_MAXBYTES + 1];
c = PTR2CHAR(s);
- c = enc_utf8 ? utf_fold(c) : TOLOWER_LOC(c);
- orig_len = MB_PTR2LEN(s);
+ c = utf_fold(c);
+ orig_len = utfc_ptr2len(s);
if (utf_char2bytes(c, cbuf) != orig_len) {
// TODO(Bram): handle byte length difference
memmove(ptr + len, s, orig_len);
@@ -1382,11 +1385,18 @@ void diff_win_options(win_T *wp, int addbuf)
curbuf = curwin->w_buffer;
if (!wp->w_p_diff) {
- wp->w_p_fdc_save = wp->w_p_fdc;
wp->w_p_fen_save = wp->w_p_fen;
wp->w_p_fdl_save = wp->w_p_fdl;
+
+ if (wp->w_p_diff_saved) {
+ free_string_option(wp->w_p_fdc_save);
+ }
+ wp->w_p_fdc_save = vim_strsave(wp->w_p_fdc);
}
- wp->w_p_fdc = diff_foldcolumn;
+ xfree(wp->w_p_fdc);
+ wp->w_p_fdc = (char_u *)xstrdup("2");
+ assert(diff_foldcolumn >= 0 && diff_foldcolumn <= 9);
+ snprintf((char *)wp->w_p_fdc, STRLEN(wp->w_p_fdc) + 1, "%d", diff_foldcolumn);
wp->w_p_fen = true;
wp->w_p_fdl = 0;
foldUpdateAll(wp);
@@ -1440,9 +1450,9 @@ void ex_diffoff(exarg_T *eap)
wp->w_p_fdm = vim_strsave(*wp->w_p_fdm_save
? wp->w_p_fdm_save
: (char_u *)"manual");
- if (wp->w_p_fdc == diff_foldcolumn) {
- wp->w_p_fdc = wp->w_p_fdc_save;
- }
+ free_string_option(wp->w_p_fdc);
+ wp->w_p_fdc = vim_strsave(wp->w_p_fdc_save);
+
if (wp->w_p_fdl == 0) {
wp->w_p_fdl = wp->w_p_fdl_save;
}
@@ -1472,6 +1482,13 @@ void ex_diffoff(exarg_T *eap)
diff_buf_clear();
}
+ if (!diffwin) {
+ diff_need_update = false;
+ curtab->tp_diff_invalid = false;
+ curtab->tp_diff_update = false;
+ diff_clear(curtab);
+ }
+
// Remove "hor" from from 'scrollopt' if there are no diff windows left.
if (!diffwin && (vim_strchr(p_sbo, 'h') != NULL)) {
do_cmdline_cmd("set sbo-=hor");
@@ -1712,6 +1729,7 @@ static void diff_copy_entry(diff_T *dprev, diff_T *dp, int idx_orig,
///
/// @param tp
void diff_clear(tabpage_T *tp)
+ FUNC_ATTR_NONNULL_ALL
{
diff_T *p;
diff_T *next_p;
@@ -2141,6 +2159,9 @@ int diffopt_changed(void)
} else if (STRNCMP(p, "hiddenoff", 9) == 0) {
p += 9;
diff_flags_new |= DIFF_HIDDEN_OFF;
+ } else if (STRNCMP(p, "closeoff", 8) == 0) {
+ p += 8;
+ diff_flags_new |= DIFF_CLOSE_OFF;
} else if (STRNCMP(p, "indent-heuristic", 16) == 0) {
p += 16;
diff_indent_heuristic = XDF_INDENT_HEURISTIC;
@@ -2216,6 +2237,13 @@ bool diffopt_hiddenoff(void)
return (diff_flags & DIFF_HIDDEN_OFF) != 0;
}
+// Return true if 'diffopt' contains "closeoff".
+bool diffopt_closeoff(void)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ return (diff_flags & DIFF_CLOSE_OFF) != 0;
+}
+
/// Find the difference within a changed line.
///
/// @param wp window whose current buffer to check
@@ -2411,6 +2439,10 @@ void nv_diffgetput(bool put, size_t count)
exarg_T ea;
char buf[30];
+ if (bt_prompt(curbuf)) {
+ vim_beep(BO_OPER);
+ return;
+ }
if (count == 0) {
ea.arg = (char_u *)"";
} else {
@@ -2690,7 +2722,8 @@ void ex_diffgetput(exarg_T *eap)
// Adjust marks. This will change the following entries!
if (added != 0) {
- mark_adjust(lnum, lnum + count - 1, (long)MAXLNUM, (long)added, false);
+ mark_adjust(lnum, lnum + count - 1, (long)MAXLNUM, (long)added,
+ kExtmarkUndo);
if (curwin->w_cursor.lnum >= lnum) {
// Adjust the cursor position if it's in/after the changed
// lines.
diff --git a/src/nvim/diff.h b/src/nvim/diff.h
index 3624ce29bb..99a60381bd 100644
--- a/src/nvim/diff.h
+++ b/src/nvim/diff.h
@@ -4,6 +4,13 @@
#include "nvim/pos.h"
#include "nvim/ex_cmds_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 bool need_diff_redraw INIT(= false); // need to call diff_redraw()
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "diff.h.generated.h"
#endif
diff --git a/src/nvim/digraph.c b/src/nvim/digraph.c
index f6ec350488..65d95ff158 100644
--- a/src/nvim/digraph.c
+++ b/src/nvim/digraph.c
@@ -128,101 +128,154 @@ static digr_T digraphdefault[] =
{ 'P', 'M', 0x9e },
{ 'A', 'C', 0x9f },
{ 'N', 'S', 0xa0 },
+#define DG_START_LATIN 0xa1
{ '!', 'I', 0xa1 },
+ { '~', '!', 0xa1 }, // ¡ Vim 5.x compatible
{ 'C', 't', 0xa2 },
+ { 'c', '|', 0xa2 }, // ¢ Vim 5.x compatible
{ 'P', 'd', 0xa3 },
+ { '$', '$', 0xa3 }, // £ Vim 5.x compatible
{ 'C', 'u', 0xa4 },
+ { 'o', 'x', 0xa4 }, // ¤ Vim 5.x compatible
{ 'Y', 'e', 0xa5 },
+ { 'Y', '-', 0xa5 }, // ¥ Vim 5.x compatible
{ 'B', 'B', 0xa6 },
+ { '|', '|', 0xa6 }, // ¦ Vim 5.x compatible
{ 'S', 'E', 0xa7 },
{ '\'', ':', 0xa8 },
{ 'C', 'o', 0xa9 },
+ { 'c', 'O', 0xa9 }, // © Vim 5.x compatible
{ '-', 'a', 0xaa },
{ '<', '<', 0xab },
{ 'N', 'O', 0xac },
+ { '-', ',', 0xac }, // ¬ Vim 5.x compatible
{ '-', '-', 0xad },
{ 'R', 'g', 0xae },
{ '\'', 'm', 0xaf },
+ { '-', '=', 0xaf }, // ¯ Vim 5.x compatible
{ 'D', 'G', 0xb0 },
+ { '~', 'o', 0xb0 }, // ° Vim 5.x compatible
{ '+', '-', 0xb1 },
{ '2', 'S', 0xb2 },
+ { '2', '2', 0xb2 }, // ² Vim 5.x compatible
{ '3', 'S', 0xb3 },
+ { '3', '3', 0xb3 }, // ³ Vim 5.x compatible
{ '\'', '\'', 0xb4 },
{ 'M', 'y', 0xb5 },
{ 'P', 'I', 0xb6 },
+ { 'p', 'p', 0xb6 }, // ¶ Vim 5.x compatible
{ '.', 'M', 0xb7 },
+ { '~', '.', 0xb7 }, // · Vim 5.x compatible
{ '\'', ',', 0xb8 },
{ '1', 'S', 0xb9 },
+ { '1', '1', 0xb9 }, // ¹ Vim 5.x compatible
{ '-', 'o', 0xba },
{ '>', '>', 0xbb },
{ '1', '4', 0xbc },
{ '1', '2', 0xbd },
{ '3', '4', 0xbe },
{ '?', 'I', 0xbf },
+ { '~', '?', 0xbf }, // ¿ Vim 5.x compatible
{ 'A', '!', 0xc0 },
+ { 'A', '`', 0xc0 }, // À Vim 5.x compatible
{ 'A', '\'', 0xc1 },
{ 'A', '>', 0xc2 },
+ { 'A', '^', 0xc2 }, // Â Vim 5.x compatible
{ 'A', '?', 0xc3 },
+ { 'A', '~', 0xc3 }, // Ã Vim 5.x compatible
{ 'A', ':', 0xc4 },
+ { 'A', '"', 0xc4 }, // Ä Vim 5.x compatible
{ 'A', 'A', 0xc5 },
+ { 'A', '@', 0xc5 }, // Ã… Vim 5.x compatible
{ 'A', 'E', 0xc6 },
{ 'C', ',', 0xc7 },
{ 'E', '!', 0xc8 },
+ { 'E', '`', 0xc8 }, // È Vim 5.x compatible
{ 'E', '\'', 0xc9 },
{ 'E', '>', 0xca },
+ { 'E', '^', 0xca }, // Ê Vim 5.x compatible
{ 'E', ':', 0xcb },
+ { 'E', '"', 0xcb }, // Ë Vim 5.x compatible
{ 'I', '!', 0xcc },
+ { 'I', '`', 0xcc }, // Ì Vim 5.x compatible
{ 'I', '\'', 0xcd },
{ 'I', '>', 0xce },
+ { 'I', '^', 0xce }, // ÃŽ Vim 5.x compatible
{ 'I', ':', 0xcf },
+ { 'I', '"', 0xcf }, // Ã Vim 5.x compatible
{ 'D', '-', 0xd0 },
{ 'N', '?', 0xd1 },
+ { 'N', '~', 0xd1 }, // Ñ Vim 5.x compatible
{ 'O', '!', 0xd2 },
+ { 'O', '`', 0xd2 }, // Ã’ Vim 5.x compatible
{ 'O', '\'', 0xd3 },
{ 'O', '>', 0xd4 },
+ { 'O', '^', 0xd4 }, // Ô Vim 5.x compatible
{ 'O', '?', 0xd5 },
+ { 'O', '~', 0xd5 }, // Õ Vim 5.x compatible
{ 'O', ':', 0xd6 },
{ '*', 'X', 0xd7 },
+ { '/', '\\', 0xd7 }, // × Vim 5.x compatible
{ 'O', '/', 0xd8 },
{ 'U', '!', 0xd9 },
+ { 'U', '`', 0xd9 }, // Ù Vim 5.x compatible
{ 'U', '\'', 0xda },
{ 'U', '>', 0xdb },
+ { 'U', '^', 0xdb }, // Û Vim 5.x compatible
{ 'U', ':', 0xdc },
{ 'Y', '\'', 0xdd },
{ 'T', 'H', 0xde },
+ { 'I', 'p', 0xde }, // Þ Vim 5.x compatible
{ 's', 's', 0xdf },
{ 'a', '!', 0xe0 },
+ { 'a', '`', 0xe0 }, // à Vim 5.x compatible
{ 'a', '\'', 0xe1 },
{ 'a', '>', 0xe2 },
+ { 'a', '^', 0xe2 }, // â Vim 5.x compatible
{ 'a', '?', 0xe3 },
+ { 'a', '~', 0xe3 }, // ã Vim 5.x compatible
{ 'a', ':', 0xe4 },
+ { 'a', '"', 0xe4 }, // ä Vim 5.x compatible
{ 'a', 'a', 0xe5 },
+ { 'a', '@', 0xe5 }, // å Vim 5.x compatible
{ 'a', 'e', 0xe6 },
{ 'c', ',', 0xe7 },
{ 'e', '!', 0xe8 },
+ { 'e', '`', 0xe8 }, // è Vim 5.x compatible
{ 'e', '\'', 0xe9 },
{ 'e', '>', 0xea },
+ { 'e', '^', 0xea }, // ê Vim 5.x compatible
{ 'e', ':', 0xeb },
+ { 'e', '"', 0xeb }, // ë Vim 5.x compatible
{ 'i', '!', 0xec },
+ { 'i', '`', 0xec }, // ì Vim 5.x compatible
{ 'i', '\'', 0xed },
{ 'i', '>', 0xee },
+ { 'i', '^', 0xee }, // î Vim 5.x compatible
{ 'i', ':', 0xef },
{ 'd', '-', 0xf0 },
{ 'n', '?', 0xf1 },
+ { 'n', '~', 0xf1 }, // ñ Vim 5.x compatible
{ 'o', '!', 0xf2 },
+ { 'o', '`', 0xf2 }, // ò Vim 5.x compatible
{ 'o', '\'', 0xf3 },
{ 'o', '>', 0xf4 },
+ { 'o', '^', 0xf4 }, // ô Vim 5.x compatible
{ 'o', '?', 0xf5 },
+ { 'o', '~', 0xf5 }, // õ Vim 5.x compatible
{ 'o', ':', 0xf6 },
{ '-', ':', 0xf7 },
{ 'o', '/', 0xf8 },
{ 'u', '!', 0xf9 },
+ { 'u', '`', 0xf9 }, // ù Vim 5.x compatible
{ 'u', '\'', 0xfa },
{ 'u', '>', 0xfb },
+ { 'u', '^', 0xfb }, // û Vim 5.x compatible
{ 'u', ':', 0xfc },
{ 'y', '\'', 0xfd },
{ 't', 'h', 0xfe },
{ 'y', ':', 0xff },
+ { 'y', '"', 0xff }, // x XX Vim 5.x compatible
{ 'A', '-', 0x0100 },
{ 'a', '-', 0x0101 },
@@ -397,6 +450,7 @@ static digr_T digraphdefault[] =
{ '\'', '0', 0x02da },
{ '\'', ';', 0x02db },
{ '\'', '"', 0x02dd },
+#define DG_START_GREEK 0x0386
{ 'A', '%', 0x0386 },
{ 'E', '%', 0x0388 },
{ 'Y', '%', 0x0389 },
@@ -478,6 +532,7 @@ static digr_T digraphdefault[] =
{ 'p', '3', 0x03e1 },
{ '\'', '%', 0x03f4 },
{ 'j', '3', 0x03f5 },
+#define DG_START_CYRILLIC 0x0401
{ 'I', 'O', 0x0401 },
{ 'D', '%', 0x0402 },
{ 'G', '%', 0x0403 },
@@ -582,6 +637,7 @@ static digr_T digraphdefault[] =
{ 'c', '3', 0x0481 },
{ 'G', '3', 0x0490 },
{ 'g', '3', 0x0491 },
+#define DG_START_HEBREW 0x05d0
{ 'A', '+', 0x05d0 },
{ 'B', '+', 0x05d1 },
{ 'G', '+', 0x05d2 },
@@ -609,6 +665,7 @@ static digr_T digraphdefault[] =
{ 'R', '+', 0x05e8 },
{ 'S', 'h', 0x05e9 },
{ 'T', '+', 0x05ea },
+#define DG_START_ARABIC 0x060c
{ ',', '+', 0x060c },
{ ';', '+', 0x061b },
{ '?', '+', 0x061f },
@@ -671,6 +728,7 @@ static digr_T digraphdefault[] =
{ '7', 'a', 0x06f7 },
{ '8', 'a', 0x06f8 },
{ '9', 'a', 0x06f9 },
+#define DG_START_LATIN_EXTENDED 0x1e02
{ 'B', '.', 0x1e02 },
{ 'b', '.', 0x1e03 },
{ 'B', '_', 0x1e06 },
@@ -722,7 +780,9 @@ static digr_T digraphdefault[] =
{ 'V', '?', 0x1e7c },
{ 'v', '?', 0x1e7d },
{ 'W', '!', 0x1e80 },
+ { 'W', '`', 0x1e80 }, // extra alternative, easier to remember
{ 'w', '!', 0x1e81 },
+ { 'w', '`', 0x1e81 }, // extra alternative, easier to remember
{ 'W', '\'', 0x1e82 },
{ 'w', '\'', 0x1e83 },
{ 'W', ':', 0x1e84 },
@@ -756,11 +816,14 @@ static digr_T digraphdefault[] =
{ 'U', '2', 0x1ee6 },
{ 'u', '2', 0x1ee7 },
{ 'Y', '!', 0x1ef2 },
+ { 'Y', '`', 0x1ef2 }, // extra alternative, easier to remember
{ 'y', '!', 0x1ef3 },
+ { 'y', '`', 0x1ef3 }, // extra alternative, easier to remember
{ 'Y', '2', 0x1ef6 },
{ 'y', '2', 0x1ef7 },
{ 'Y', '?', 0x1ef8 },
{ 'y', '?', 0x1ef9 },
+#define DG_START_GREEK_EXTENDED 0x1f00
{ ';', '\'', 0x1f00 },
{ ',', '\'', 0x1f01 },
{ ';', '!', 0x1f02 },
@@ -769,6 +832,7 @@ static digr_T digraphdefault[] =
{ '?', ',', 0x1f05 },
{ '!', ':', 0x1f06 },
{ '?', ':', 0x1f07 },
+#define DG_START_PUNCTUATION 0x2002
{ '1', 'N', 0x2002 },
{ '1', 'M', 0x2003 },
{ '3', 'M', 0x2004 },
@@ -807,6 +871,7 @@ static digr_T digraphdefault[] =
{ ':', 'X', 0x203b },
{ '\'', '-', 0x203e },
{ '/', 'f', 0x2044 },
+#define DG_START_SUB_SUPER 0x2070
{ '0', 'S', 0x2070 },
{ '4', 'S', 0x2074 },
{ '5', 'S', 0x2075 },
@@ -835,13 +900,15 @@ static digr_T digraphdefault[] =
{ '=', 's', 0x208c },
{ '(', 's', 0x208d },
{ ')', 's', 0x208e },
+#define DG_START_CURRENCY 0x20a4
{ 'L', 'i', 0x20a4 },
{ 'P', 't', 0x20a7 },
{ 'W', '=', 0x20a9 },
- { '=', 'e', 0x20ac }, // euro
- { 'E', 'u', 0x20ac }, // euro
- { '=', 'R', 0x20bd }, // rouble
- { '=', 'P', 0x20bd }, // rouble
+ { '=', 'e', 0x20ac }, // euro
+ { 'E', 'u', 0x20ac }, // euro
+ { '=', 'R', 0x20bd }, // rouble
+ { '=', 'P', 0x20bd }, // rouble
+#define DG_START_OTHER1 0x2103
{ 'o', 'C', 0x2103 },
{ 'c', 'o', 0x2105 },
{ 'o', 'F', 0x2109 },
@@ -864,6 +931,7 @@ static digr_T digraphdefault[] =
{ '3', '8', 0x215c },
{ '5', '8', 0x215d },
{ '7', '8', 0x215e },
+#define DG_START_ROMAN 0x2160
{ '1', 'R', 0x2160 },
{ '2', 'R', 0x2161 },
{ '3', 'R', 0x2162 },
@@ -888,6 +956,7 @@ static digr_T digraphdefault[] =
{ 'a', 'r', 0x2179 },
{ 'b', 'r', 0x217a },
{ 'c', 'r', 0x217b },
+#define DG_START_ARROWS 0x2190
{ '<', '-', 0x2190 },
{ '-', '!', 0x2191 },
{ '-', '>', 0x2192 },
@@ -897,6 +966,7 @@ static digr_T digraphdefault[] =
{ '<', '=', 0x21d0 },
{ '=', '>', 0x21d2 },
{ '=', '=', 0x21d4 },
+#define DG_START_MATH 0x2200
{ 'F', 'A', 0x2200 },
{ 'd', 'P', 0x2202 },
{ 'T', 'E', 0x2203 },
@@ -954,6 +1024,7 @@ static digr_T digraphdefault[] =
{ '.', 'P', 0x22c5 },
{ ':', '3', 0x22ee },
{ '.', '3', 0x22ef },
+#define DG_START_TECHNICAL 0x2302
{ 'E', 'h', 0x2302 },
{ '<', '7', 0x2308 },
{ '>', '7', 0x2309 },
@@ -966,6 +1037,7 @@ static digr_T digraphdefault[] =
{ 'I', 'l', 0x2321 },
{ '<', '/', 0x2329 },
{ '/', '>', 0x232a },
+#define DG_START_OTHER2 0x2423
{ 'V', 's', 0x2423 },
{ '1', 'h', 0x2440 },
{ '3', 'h', 0x2441 },
@@ -984,6 +1056,7 @@ static digr_T digraphdefault[] =
{ '7', '.', 0x248e },
{ '8', '.', 0x248f },
{ '9', '.', 0x2490 },
+#define DG_START_DRAWING 0x2500
{ 'h', 'h', 0x2500 },
{ 'H', 'H', 0x2501 },
{ 'v', 'v', 0x2502 },
@@ -1034,6 +1107,7 @@ static digr_T digraphdefault[] =
{ 'V', 'H', 0x254b },
{ 'F', 'D', 0x2571 },
{ 'B', 'D', 0x2572 },
+#define DG_START_BLOCK 0x2580
{ 'T', 'B', 0x2580 },
{ 'L', 'B', 0x2584 },
{ 'F', 'B', 0x2588 },
@@ -1042,6 +1116,7 @@ static digr_T digraphdefault[] =
{ '.', 'S', 0x2591 },
{ ':', 'S', 0x2592 },
{ '?', 'S', 0x2593 },
+#define DG_START_SHAPES 0x25a0
{ 'f', 'S', 0x25a0 },
{ 'O', 'S', 0x25a1 },
{ 'R', 'O', 0x25a2 },
@@ -1075,6 +1150,7 @@ static digr_T digraphdefault[] =
{ 'I', 'c', 0x25d9 },
{ 'F', 'd', 0x25e2 },
{ 'B', 'd', 0x25e3 },
+#define DG_START_SYMBOLS 0x2605
{ '*', '2', 0x2605 },
{ '*', '1', 0x2606 },
{ '<', 'H', 0x261c },
@@ -1094,9 +1170,11 @@ static digr_T digraphdefault[] =
{ 'M', 'b', 0x266d },
{ 'M', 'x', 0x266e },
{ 'M', 'X', 0x266f },
+#define DG_START_DINGBATS 0x2713
{ 'O', 'K', 0x2713 },
{ 'X', 'X', 0x2717 },
{ '-', 'X', 0x2720 },
+#define DG_START_CJK_SYMBOLS 0x3000
{ 'I', 'S', 0x3000 },
{ ',', '_', 0x3001 },
{ '.', '_', 0x3002 },
@@ -1120,6 +1198,7 @@ static digr_T digraphdefault[] =
{ '(', 'I', 0x3016 },
{ ')', 'I', 0x3017 },
{ '-', '?', 0x301c },
+#define DG_START_HIRAGANA 0x3041
{ 'A', '5', 0x3041 },
{ 'a', '5', 0x3042 },
{ 'I', '5', 0x3043 },
@@ -1208,6 +1287,7 @@ static digr_T digraphdefault[] =
{ '0', '5', 0x309c },
{ '*', '5', 0x309d },
{ '+', '5', 0x309e },
+#define DG_START_KATAKANA 0x30a1
{ 'a', '6', 0x30a1 },
{ 'A', '6', 0x30a2 },
{ 'i', '6', 0x30a3 },
@@ -1302,6 +1382,7 @@ static digr_T digraphdefault[] =
{ '-', '6', 0x30fc },
{ '*', '6', 0x30fd },
{ '+', '6', 0x30fe },
+#define DG_START_BOPOMOFO 0x3105
{ 'b', '4', 0x3105 },
{ 'p', '4', 0x3106 },
{ 'm', '4', 0x3107 },
@@ -1341,6 +1422,7 @@ static digr_T digraphdefault[] =
{ 'v', '4', 0x312a },
{ 'n', 'G', 0x312b },
{ 'g', 'n', 0x312c },
+#define DG_START_OTHER3 0x3220
{ '1', 'c', 0x3220 },
{ '2', 'c', 0x3221 },
{ '3', 'c', 0x3222 },
@@ -1359,66 +1441,6 @@ static digr_T digraphdefault[] =
{ 'f', 't', 0xfb05 },
{ 's', 't', 0xfb06 },
- // extra alternatives, easier to remember
- { 'W', '`', 0x1e80 },
- { 'w', '`', 0x1e81 },
- { 'Y', '`', 0x1ef2 },
- { 'y', '`', 0x1ef3 },
-
- // Vim 5.x compatible digraphs that don't conflict with the above
- { '~', '!', 161 }, // ¡
- { 'c', '|', 162 }, // ¢
- { '$', '$', 163 }, // £
- { 'o', 'x', 164 }, // ¤ - currency symbol in ISO 8859-1
- { 'Y', '-', 165 }, // ¥
- { '|', '|', 166 }, // ¦
- { 'c', 'O', 169 }, // ©
- { '-', ',', 172 }, // ¬
- { '-', '=', 175 }, // ¯
- { '~', 'o', 176 }, // °
- { '2', '2', 178 }, // ²
- { '3', '3', 179 }, // ³
- { 'p', 'p', 182 }, // ¶
- { '~', '.', 183 }, // ·
- { '1', '1', 185 }, // ¹
- { '~', '?', 191 }, // ¿
- { 'A', '`', 192 }, // À
- { 'A', '^', 194 }, // Â
- { 'A', '~', 195 }, // Ã
- { 'A', '"', 196 }, // Ä
- { 'A', '@', 197 }, // Ã…
- { 'E', '`', 200 }, // È
- { 'E', '^', 202 }, // Ê
- { 'E', '"', 203 }, // Ë
- { 'I', '`', 204 }, // Ì
- { 'I', '^', 206 }, // ÃŽ
- { 'I', '"', 207 }, // Ã
- { 'N', '~', 209 }, // Ñ
- { 'O', '`', 210 }, // Ã’
- { 'O', '^', 212 }, // Ô
- { 'O', '~', 213 }, // Õ
- { '/', '\\', 215 }, // × - multiplication symbol in ISO 8859-1
- { 'U', '`', 217 }, // Ù
- { 'U', '^', 219 }, // Û
- { 'I', 'p', 222 }, // Þ
- { 'a', '`', 224 }, // à
- { 'a', '^', 226 }, // â
- { 'a', '~', 227 }, // ã
- { 'a', '"', 228 }, // ä
- { 'a', '@', 229 }, // å
- { 'e', '`', 232 }, // è
- { 'e', '^', 234 }, // ê
- { 'e', '"', 235 }, // ë
- { 'i', '`', 236 }, // ì
- { 'i', '^', 238 }, // î
- { 'n', '~', 241 }, // ñ
- { 'o', '`', 242 }, // ò
- { 'o', '^', 244 }, // ô
- { 'o', '~', 245 }, // õ
- { 'u', '`', 249 }, // ù
- { 'u', '^', 251 }, // û
- { 'y', '"', 255 }, // x XX
-
{ NUL, NUL, NUL }
};
@@ -1436,7 +1458,7 @@ int do_digraph(int c)
backspaced = -1;
} else if (p_dg) {
if (backspaced >= 0) {
- c = getdigraph(backspaced, c, FALSE);
+ c = getdigraph(backspaced, c, false);
}
backspaced = -1;
@@ -1450,8 +1472,9 @@ int do_digraph(int c)
/// Find a digraph for "val". If found return the string to display it.
/// If not found return NULL.
-char_u *get_digraph_for_char(int val)
+char_u *get_digraph_for_char(int val_arg)
{
+ const int val = val_arg;
digr_T *dp;
static char_u r[3];
@@ -1508,7 +1531,7 @@ int get_digraph(int cmdline)
if (cc != ESC) {
// ESC cancels CTRL-K
- return getdigraph(c, cc, TRUE);
+ return getdigraph(c, cc, true);
}
}
return NUL;
@@ -1520,9 +1543,9 @@ int get_digraph(int cmdline)
/// @param char2
/// @param meta_char
///
-/// @return If no match, return "char2". If "meta_char" is TRUE and "char1"
+/// @return If no match, return "char2". If "meta_char" is true and "char1"
// is a space, return "char2" | 0x80.
-static int getexactdigraph(int char1, int char2, int meta_char)
+static int getexactdigraph(int char1, int char2, bool meta_char)
{
int retval = 0;
@@ -1572,7 +1595,7 @@ static int getexactdigraph(int char1, int char2, int meta_char)
/// @param meta_char
///
/// @return The digraph.
-int getdigraph(int char1, int char2, int meta_char)
+int getdigraph(int char1, int char2, bool meta_char)
{
int retval;
@@ -1642,9 +1665,20 @@ void putdigraph(char_u *str)
}
}
-void listdigraphs(void)
+static void digraph_header(const char *msg)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (msg_col > 0) {
+ msg_putchar('\n');
+ }
+ msg_outtrans_attr((const char_u *)msg, HL_ATTR(HLF_CM));
+ msg_putchar('\n');
+}
+
+void listdigraphs(bool use_headers)
{
digr_T *dp;
+ result_T previous = 0;
msg_putchar('\n');
@@ -1656,25 +1690,63 @@ void listdigraphs(void)
// May need to convert the result to 'encoding'.
tmp.char1 = dp->char1;
tmp.char2 = dp->char2;
- tmp.result = getexactdigraph(tmp.char1, tmp.char2, FALSE);
+ tmp.result = getexactdigraph(tmp.char1, tmp.char2, false);
if ((tmp.result != 0)
&& (tmp.result != tmp.char2)) {
- printdigraph(&tmp);
+ printdigraph(&tmp, use_headers ? &previous : NULL);
}
dp++;
fast_breakcheck();
}
dp = (digr_T *)user_digraphs.ga_data;
- for (int i = 0; i < user_digraphs.ga_len && !got_int; ++i) {
- printdigraph(dp);
+ for (int i = 0; i < user_digraphs.ga_len && !got_int; i++) {
+ if (previous >= 0 && use_headers) {
+ digraph_header(_("Custom"));
+ }
+ previous = -1;
+ printdigraph(dp, NULL);
fast_breakcheck();
dp++;
}
}
-static void printdigraph(digr_T *dp)
+struct dg_header_entry {
+ int dg_start;
+ const char *dg_header;
+} header_table[] = {
+ { DG_START_LATIN, N_("Latin supplement") },
+ { DG_START_GREEK, N_("Greek and Coptic") },
+ { DG_START_CYRILLIC, N_("Cyrillic") },
+ { DG_START_HEBREW, N_("Hebrew") },
+ { DG_START_ARABIC, N_("Arabic") },
+ { DG_START_LATIN_EXTENDED, N_("Latin extended") },
+ { DG_START_GREEK_EXTENDED, N_("Greek extended") },
+ { DG_START_PUNCTUATION, N_("Punctuation") },
+ { DG_START_SUB_SUPER, N_("Super- and subscripts") },
+ { DG_START_CURRENCY, N_("Currency") },
+ { DG_START_OTHER1, N_("Other") },
+ { DG_START_ROMAN, N_("Roman numbers") },
+ { DG_START_ARROWS, N_("Arrows") },
+ { DG_START_MATH, N_("Mathematical operators") },
+ { DG_START_TECHNICAL, N_("Technical") },
+ { DG_START_OTHER2, N_("Other") },
+ { DG_START_DRAWING, N_("Box drawing") },
+ { DG_START_BLOCK, N_("Block elements") },
+ { DG_START_SHAPES, N_("Geometric shapes") },
+ { DG_START_SYMBOLS, N_("Symbols") },
+ { DG_START_DINGBATS, N_("Dingbats") },
+ { DG_START_CJK_SYMBOLS, N_("CJK symbols and punctuation") },
+ { DG_START_HIRAGANA, N_("Hiragana") },
+ { DG_START_KATAKANA, N_("Katakana") },
+ { DG_START_BOPOMOFO, N_("Bopomofo") },
+ { DG_START_OTHER3, N_("Other") },
+ { 0xfffffff, NULL },
+};
+
+static void printdigraph(const digr_T *dp, result_T *previous)
+ FUNC_ATTR_NONNULL_ARG(1)
{
char_u buf[30];
char_u *p;
@@ -1684,6 +1756,17 @@ static void printdigraph(digr_T *dp)
list_width = 13;
if (dp->result != 0) {
+ if (previous != NULL) {
+ for (int i = 0; header_table[i].dg_header != NULL; i++) {
+ if (*previous < header_table[i].dg_start
+ && dp->result >= header_table[i].dg_start
+ && dp->result < header_table[i + 1].dg_start) {
+ digraph_header(_(header_table[i].dg_header));
+ break;
+ }
+ }
+ *previous = dp->result;
+ }
if (msg_col > Columns - list_width) {
msg_putchar('\n');
}
@@ -1804,7 +1887,7 @@ void ex_loadkeymap(exarg_T *eap)
// Get each line of the sourced file, break at the end.
for (;;) {
- line = eap->getline(0, eap->cookie, 0);
+ line = eap->getline(0, eap->cookie, 0, true);
if (line == NULL) {
break;
diff --git a/src/nvim/edit.c b/src/nvim/edit.c
index 16c4882975..ea38221dc7 100644
--- a/src/nvim/edit.c
+++ b/src/nvim/edit.c
@@ -28,6 +28,7 @@
#include "nvim/indent.h"
#include "nvim/indent_c.h"
#include "nvim/main.h"
+#include "nvim/extmark.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
@@ -142,6 +143,7 @@ struct compl_S {
compl_T *cp_prev;
char_u *cp_str; // matched text
char_u *(cp_text[CPT_COUNT]); // text for the menu
+ typval_T cp_user_data;
char_u *cp_fname; // file containing the match, allocated when
// cp_flags has CP_FREE_FNAME
int cp_flags; // CP_ values
@@ -183,7 +185,7 @@ static bool compl_used_match; // Selected one of the matches.
static int compl_was_interrupted = FALSE; /* didn't finish finding
completions. */
-static int compl_restarting = FALSE; /* don't insert match */
+static bool compl_restarting = false; // don't insert match
// When the first completion is done "compl_started" is set. When it's
// false the word to be completed must be located.
@@ -196,7 +198,7 @@ static int compl_matches = 0;
static char_u *compl_pattern = NULL;
static int compl_direction = FORWARD;
static int compl_shows_dir = FORWARD;
-static int compl_pending = 0; /* > 1 for postponed CTRL-N */
+static int compl_pending = 0; // > 1 for postponed CTRL-N
static pos_T compl_startpos;
static colnr_T compl_col = 0; /* column where the text starts
* that is being completed */
@@ -248,30 +250,30 @@ typedef struct insert_state {
#define BACKSPACE_WORD_NOT_SPACE 3
#define BACKSPACE_LINE 4
-static size_t spell_bad_len = 0; /* length of located bad word */
+static size_t spell_bad_len = 0; // length of located bad word
-static colnr_T Insstart_textlen; /* length of line when insert started */
-static colnr_T Insstart_blank_vcol; /* vcol for first inserted blank */
-static bool update_Insstart_orig = true; /* set Insstart_orig to Insstart */
+static colnr_T Insstart_textlen; // length of line when insert started
+static colnr_T Insstart_blank_vcol; // vcol for first inserted blank
+static bool update_Insstart_orig = true; // set Insstart_orig to Insstart
-static char_u *last_insert = NULL; /* the text of the previous insert,
- K_SPECIAL and CSI are escaped */
-static int last_insert_skip; /* nr of chars in front of previous insert */
-static int new_insert_skip; /* nr of chars in front of current insert */
-static int did_restart_edit; /* "restart_edit" when calling edit() */
+static char_u *last_insert = NULL; // the text of the previous insert,
+ // K_SPECIAL and CSI are escaped
+static int last_insert_skip; // nr of chars in front of previous insert
+static int new_insert_skip; // nr of chars in front of current insert
+static int did_restart_edit; // "restart_edit" when calling edit()
static bool can_cindent; // may do cindenting on this line
-static int old_indent = 0; /* for ^^D command in insert mode */
+static int old_indent = 0; // for ^^D command in insert mode
-static int revins_on; /* reverse insert mode on */
-static int revins_chars; /* how much to skip after edit */
-static int revins_legal; /* was the last char 'legal'? */
-static int revins_scol; /* start column of revins session */
+static int revins_on; // reverse insert mode on
+static int revins_chars; // how much to skip after edit
+static int revins_legal; // was the last char 'legal'?
+static int revins_scol; // start column of revins session
-static int ins_need_undo; /* call u_save() before inserting a
- char. Set when edit() is called.
- after that arrow_used is used. */
+static bool ins_need_undo; // call u_save() before inserting a
+ // char. Set when edit() is called.
+ // after that arrow_used is used.
static bool did_add_space = false; // auto_format() added an extra space
// under the cursor
@@ -463,8 +465,8 @@ static void insert_enter(InsertState *s)
change_warning(s->i == 0 ? 0 : s->i + 1);
}
- ui_cursor_shape(); /* may show different cursor shape */
- do_digraph(-1); /* clear digraphs */
+ ui_cursor_shape(); // may show different cursor shape
+ do_digraph(-1); // clear digraphs
// Get the current length of the redo buffer, those characters have to be
// skipped if we want to get to the inserted characters.
@@ -573,6 +575,10 @@ static int insert_check(VimState *state)
foldCheckClose();
}
+ if (bt_prompt(curbuf)) {
+ init_prompt(s->cmdchar);
+ }
+
// If we inserted a character at the last position of the last line in the
// window, scroll the window one line up. This avoids an extra redraw. This
// is detected when the cursor column is smaller after inserting something.
@@ -588,7 +594,7 @@ static int insert_check(VimState *state)
if (curwin->w_wcol < s->mincol - curbuf->b_p_ts
&& curwin->w_wrow == curwin->w_winrow
- + curwin->w_height_inner - 1 - p_so
+ + curwin->w_height_inner - 1 - get_scrolloff_value()
&& (curwin->w_cursor.lnum != curwin->w_topline
|| curwin->w_topfill > 0)) {
if (curwin->w_topfill > 0) {
@@ -637,7 +643,7 @@ static int insert_check(VimState *state)
static int insert_execute(VimState *state, int key)
{
- if (key == K_IGNORE) {
+ if (key == K_IGNORE || key == K_NOP) {
return -1; // get another key
}
InsertState *s = (InsertState *)state;
@@ -816,6 +822,16 @@ static int insert_handle_key(InsertState *s)
s->nomove = true;
return 0; // exit insert mode
}
+ if (s->c == Ctrl_C && bt_prompt(curbuf)) {
+ if (invoke_prompt_interrupt()) {
+ if (!bt_prompt(curbuf)) {
+ // buffer changed to a non-prompt buffer, get out of
+ // Insert mode
+ return 0;
+ }
+ break;
+ }
+ }
// when 'insertmode' set, and not halfway through a mapping, don't leave
// Insert mode
@@ -1142,6 +1158,15 @@ check_pum:
cmdwin_result = CAR;
return 0;
}
+ if (bt_prompt(curbuf)) {
+ invoke_prompt_callback();
+ if (!bt_prompt(curbuf)) {
+ // buffer changed to a non-prompt buffer, get out of
+ // Insert mode
+ return 0;
+ }
+ break;
+ }
if (!ins_eol(s->c) && !p_im) {
return 0; // out of memory
}
@@ -1233,6 +1258,7 @@ normalchar:
// Unmapped ALT/META chord behaves like ESC+c. #8213
stuffcharReadbuff(ESC);
stuffcharReadbuff(s->c);
+ u_sync(false);
break;
}
@@ -1395,9 +1421,8 @@ bool edit(int cmdchar, bool startln, long count)
* Only redraw when there are no characters available. This speeds up
* inserting sequences of characters (e.g., for CTRL-R).
*/
-static void
-ins_redraw (
- int ready /* not busy with something */
+static void ins_redraw(
+ bool ready // not busy with something
)
{
bool conceal_cursor_moved = false;
@@ -1478,7 +1503,7 @@ ins_redraw (
}
showruler(false);
setcursor();
- emsg_on_display = FALSE; /* may remove error message now */
+ emsg_on_display = false; // may remove error message now
}
/*
@@ -1487,24 +1512,25 @@ ins_redraw (
static void ins_ctrl_v(void)
{
int c;
- int did_putchar = FALSE;
+ bool did_putchar = false;
- /* may need to redraw when no more chars available now */
- ins_redraw(FALSE);
+ // may need to redraw when no more chars available now
+ ins_redraw(false);
if (redrawing() && !char_avail()) {
- edit_putchar('^', TRUE);
- did_putchar = TRUE;
+ edit_putchar('^', true);
+ did_putchar = true;
}
AppendToRedobuff(CTRL_V_STR);
add_to_showcmd_c(Ctrl_V);
c = get_literal();
- if (did_putchar)
- /* when the line fits in 'columns' the '^' is at the start of the next
- * line and will not removed by the redraw */
+ if (did_putchar) {
+ // when the line fits in 'columns' the '^' is at the start of the next
+ // line and will not removed by the redraw
edit_unputchar();
+ }
clear_showcmd();
insert_special(c, true, true);
revins_chars++;
@@ -1516,16 +1542,16 @@ static void ins_ctrl_v(void)
* Used while handling CTRL-K, CTRL-V, etc. in Insert mode.
*/
static int pc_status;
-#define PC_STATUS_UNSET 0 /* pc_bytes was not set */
-#define PC_STATUS_RIGHT 1 /* right halve of double-wide char */
-#define PC_STATUS_LEFT 2 /* left halve of double-wide char */
-#define PC_STATUS_SET 3 /* pc_bytes was filled */
-static char_u pc_bytes[MB_MAXBYTES + 1]; /* saved bytes */
+#define PC_STATUS_UNSET 0 // pc_bytes was not set
+#define PC_STATUS_RIGHT 1 // right halve of double-wide char
+#define PC_STATUS_LEFT 2 // left halve of double-wide char
+#define PC_STATUS_SET 3 // pc_bytes was filled
+static char_u pc_bytes[MB_MAXBYTES + 1]; // saved bytes
static int pc_attr;
static int pc_row;
static int pc_col;
-void edit_putchar(int c, int highlight)
+void edit_putchar(int c, bool highlight)
{
int attr;
@@ -1558,7 +1584,7 @@ void edit_putchar(int c, int highlight)
}
}
- /* save the character to be able to put it back */
+ // save the character to be able to put it back
if (pc_status == PC_STATUS_UNSET) {
grid_getbytes(&curwin->w_grid, pc_row, pc_col, pc_bytes, &pc_attr);
pc_status = PC_STATUS_SET;
@@ -1567,6 +1593,52 @@ void edit_putchar(int c, int highlight)
}
}
+// Return the effective prompt for the current buffer.
+char_u *prompt_text(void)
+{
+ if (curbuf->b_prompt_text == NULL) {
+ return (char_u *)"% ";
+ }
+ return curbuf->b_prompt_text;
+}
+
+// Prepare for prompt mode: Make sure the last line has the prompt text.
+// Move the cursor to this line.
+static void init_prompt(int cmdchar_todo)
+{
+ char_u *prompt = prompt_text();
+ char_u *text;
+
+ curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count;
+ text = get_cursor_line_ptr();
+ if (STRNCMP(text, prompt, STRLEN(prompt)) != 0) {
+ // prompt is missing, insert it or append a line with it
+ if (*text == NUL) {
+ ml_replace(curbuf->b_ml.ml_line_count, prompt, true);
+ } else {
+ ml_append(curbuf->b_ml.ml_line_count, prompt, 0, false);
+ }
+ curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count;
+ coladvance((colnr_T)MAXCOL);
+ changed_bytes(curbuf->b_ml.ml_line_count, 0);
+ }
+ if (cmdchar_todo == 'A') {
+ coladvance((colnr_T)MAXCOL);
+ }
+ if (cmdchar_todo == 'I' || curwin->w_cursor.col <= (int)STRLEN(prompt)) {
+ curwin->w_cursor.col = STRLEN(prompt);
+ }
+ // Make sure the cursor is in a valid position.
+ check_cursor();
+}
+
+// Return TRUE if the cursor is in the editable position of the prompt line.
+int prompt_curpos_editable(void)
+{
+ return curwin->w_cursor.lnum == curbuf->b_ml.ml_line_count
+ && curwin->w_cursor.col >= (int)STRLEN(prompt_text());
+}
+
/*
* Undo the previous edit_putchar().
*/
@@ -1635,29 +1707,29 @@ change_indent (
int type,
int amount,
int round,
- int replaced, /* replaced character, put on replace stack */
- int call_changed_bytes /* call changed_bytes() */
+ int replaced, // replaced character, put on replace stack
+ int call_changed_bytes // call changed_bytes()
)
{
int vcol;
int last_vcol;
- int insstart_less; /* reduction for Insstart.col */
+ int insstart_less; // reduction for Insstart.col
int new_cursor_col;
int i;
char_u *ptr;
int save_p_list;
int start_col;
colnr_T vc;
- colnr_T orig_col = 0; /* init for GCC */
- char_u *new_line, *orig_line = NULL; /* init for GCC */
+ colnr_T orig_col = 0; // init for GCC
+ char_u *new_line, *orig_line = NULL; // init for GCC
- /* VREPLACE mode needs to know what the line was like before changing */
+ // VREPLACE mode needs to know what the line was like before changing
if (State & VREPLACE_FLAG) {
- orig_line = vim_strsave(get_cursor_line_ptr()); /* Deal with NULL below */
+ orig_line = vim_strsave(get_cursor_line_ptr()); // Deal with NULL below
orig_col = curwin->w_cursor.col;
}
- /* for the following tricks we don't want list mode */
+ // for the following tricks we don't want list mode
save_p_list = curwin->w_p_list;
curwin->w_p_list = FALSE;
vc = getvcol_nolist(&curwin->w_cursor);
@@ -1670,7 +1742,7 @@ change_indent (
*/
start_col = curwin->w_cursor.col;
- /* determine offset from first non-blank */
+ // determine offset from first non-blank
new_cursor_col = curwin->w_cursor.col;
beginline(BL_WHITE);
new_cursor_col -= curwin->w_cursor.col;
@@ -1684,8 +1756,9 @@ change_indent (
if (new_cursor_col < 0)
vcol = get_indent() - vcol;
- if (new_cursor_col > 0) /* can't fix replace stack */
+ if (new_cursor_col > 0) { // can't fix replace stack
start_col = -1;
+ }
/*
* Set the new indent. The cursor will be put on the first non-blank.
@@ -1695,9 +1768,10 @@ change_indent (
else {
int save_State = State;
- /* Avoid being called recursively. */
- if (State & VREPLACE_FLAG)
+ // Avoid being called recursively.
+ if (State & VREPLACE_FLAG) {
State = INSERT;
+ }
shift_line(type == INDENT_DEC, round, 1, call_changed_bytes);
State = save_State;
}
@@ -1800,8 +1874,8 @@ change_indent (
*/
if (REPLACE_NORMAL(State) && start_col >= 0) {
while (start_col > (int)curwin->w_cursor.col) {
- replace_join(0); /* remove a NUL from the replace stack */
- --start_col;
+ replace_join(0); // remove a NUL from the replace stack
+ start_col--;
}
while (start_col < (int)curwin->w_cursor.col || replaced) {
replace_push(NUL);
@@ -1819,23 +1893,36 @@ change_indent (
* put it back again the way we wanted it.
*/
if (State & VREPLACE_FLAG) {
- /* Save new line */
+ // Save new line
new_line = vim_strsave(get_cursor_line_ptr());
- /* We only put back the new line up to the cursor */
+ // We only put back the new line up to the cursor
new_line[curwin->w_cursor.col] = NUL;
+ int new_col = curwin->w_cursor.col;
// Put back original line
ml_replace(curwin->w_cursor.lnum, orig_line, false);
curwin->w_cursor.col = orig_col;
- /* Backspace from cursor to start of line */
+ curbuf_splice_pending++;
+
+ // Backspace from cursor to start of line
backspace_until_column(0);
- /* Insert new stuff into line again */
+ // Insert new stuff into line again
ins_bytes(new_line);
xfree(new_line);
+
+ curbuf_splice_pending--;
+
+ // TODO(bfredl): test for crazy edge cases, like we stand on a TAB or
+ // something? does this even do the right text change then?
+ int delta = orig_col - new_col;
+ extmark_splice(curbuf, curwin->w_cursor.lnum-1, new_col,
+ 0, delta < 0 ? -delta : 0,
+ 0, delta > 0 ? delta : 0,
+ kExtmarkUndo);
}
}
@@ -1848,10 +1935,11 @@ void truncate_spaces(char_u *line)
{
int i;
- /* find start of trailing white space */
+ // find start of trailing white space
for (i = (int)STRLEN(line) - 1; i >= 0 && ascii_iswhite(line[i]); i--) {
- if (State & REPLACE_FLAG)
- replace_join(0); /* remove a NUL from the replace stack */
+ if (State & REPLACE_FLAG) {
+ replace_join(0); // remove a NUL from the replace stack
+ }
}
line[i + 1] = NUL;
}
@@ -1920,7 +2008,7 @@ static void ins_ctrl_x(void)
compl_cont_status |= CONT_INTRPT;
else
compl_cont_status = 0;
- /* We're not sure which CTRL-X mode it will be yet */
+ // We're not sure which CTRL-X mode it will be yet
ctrl_x_mode = CTRL_X_NOT_DEFINED_YET;
edit_submode = (char_u *)_(CTRL_X_MSG(ctrl_x_mode));
edit_submode_pre = NULL;
@@ -2069,8 +2157,8 @@ int ins_compl_add_infercase(char_u *str_arg, int len, bool icase, char_u *fname,
{
char_u *str = str_arg;
int i, c;
- int actual_len; /* Take multi-byte characters */
- int actual_compl_length; /* into account. */
+ int actual_len; // Take multi-byte characters
+ int actual_compl_length; // into account.
int min_len;
bool has_lower = false;
bool was_letter = false;
@@ -2090,16 +2178,15 @@ int ins_compl_add_infercase(char_u *str_arg, int len, bool icase, char_u *fname,
} else
actual_len = len;
- /* Find actual length of original text. */
- if (has_mbyte) {
+ // Find actual length of original text.
+ {
const char_u *p = compl_orig_text;
actual_compl_length = 0;
while (*p != NUL) {
MB_PTR_ADV(p);
actual_compl_length++;
}
- } else
- actual_compl_length = compl_length;
+ }
/* "actual_len" may be smaller than "actual_compl_length" when using
* thesaurus, only use the minimum when comparing. */
@@ -2205,7 +2292,7 @@ int ins_compl_add_infercase(char_u *str_arg, int len, bool icase, char_u *fname,
flags |= CP_ICASE;
}
- return ins_compl_add(str, len, fname, NULL, false, dir, flags, false);
+ return ins_compl_add(str, len, fname, NULL, false, NULL, dir, flags, false);
}
/// Add a match to the list of matches
@@ -2229,6 +2316,7 @@ static int ins_compl_add(char_u *const str, int len,
char_u *const fname,
char_u *const *const cptext,
const bool cptext_allocated,
+ typval_T *user_data,
const Direction cdir, int flags_arg, const bool adup)
FUNC_ATTR_NONNULL_ARG(1)
{
@@ -2269,7 +2357,7 @@ static int ins_compl_add(char_u *const str, int len,
} while (match != NULL && match != compl_first_match);
}
- /* Remove any popup menu before changing the list of matches. */
+ // Remove any popup menu before changing the list of matches.
ins_compl_del_pum();
/*
@@ -2317,6 +2405,10 @@ static int ins_compl_add(char_u *const str, int len,
}
}
+ if (user_data != NULL) {
+ match->cp_user_data = *user_data;
+ }
+
/*
* Link the new match structure in the list of matches.
*/
@@ -2325,16 +2417,18 @@ static int ins_compl_add(char_u *const str, int len,
else if (dir == FORWARD) {
match->cp_next = compl_curr_match->cp_next;
match->cp_prev = compl_curr_match;
- } else { /* BACKWARD */
+ } else { // BACKWARD
match->cp_next = compl_curr_match;
match->cp_prev = compl_curr_match->cp_prev;
}
- if (match->cp_next)
+ if (match->cp_next) {
match->cp_next->cp_prev = match;
- if (match->cp_prev)
+ }
+ if (match->cp_prev) {
match->cp_prev->cp_next = match;
- else /* if there's nothing before, it is the first match */
+ } else { // if there's nothing before, it is the first match
compl_first_match = match;
+ }
compl_curr_match = match;
/*
@@ -2375,7 +2469,7 @@ static void ins_compl_longest_match(compl_T *match)
int had_match;
if (compl_leader == NULL) {
- /* First match, use it as a whole. */
+ // First match, use it as a whole.
compl_leader = vim_strsave(match->cp_str);
had_match = (curwin->w_cursor.col > compl_col);
ins_compl_delete();
@@ -2388,7 +2482,7 @@ static void ins_compl_longest_match(compl_T *match)
ins_compl_delete();
compl_used_match = false;
} else {
- /* Reduce the text if this match differs from compl_leader. */
+ // Reduce the text if this match differs from compl_leader.
p = compl_leader;
s = match->cp_str;
while (*p != NUL) {
@@ -2405,7 +2499,7 @@ static void ins_compl_longest_match(compl_T *match)
}
if (*p != NUL) {
- /* Leader was shortened, need to change the inserted text. */
+ // Leader was shortened, need to change the inserted text.
*p = NUL;
had_match = (curwin->w_cursor.col > compl_col);
ins_compl_delete();
@@ -2433,7 +2527,7 @@ static void ins_compl_add_matches(int num_matches, char_u **matches, int icase)
int dir = compl_direction;
for (int i = 0; i < num_matches && add_r != FAIL; i++) {
- if ((add_r = ins_compl_add(matches[i], -1, NULL, NULL, false, dir,
+ if ((add_r = ins_compl_add(matches[i], -1, NULL, NULL, false, NULL, dir,
icase ? CP_ICASE : 0, false)) == OK) {
// If dir was BACKWARD then honor it just once.
dir = FORWARD;
@@ -2455,7 +2549,7 @@ static int ins_compl_make_cyclic(void)
* Find the end of the list.
*/
match = compl_first_match;
- /* there's always an entry for the compl_orig_text, it doesn't count. */
+ // there's always an entry for the compl_orig_text, it doesn't count.
while (match->cp_next != NULL && match->cp_next != compl_first_match) {
match = match->cp_next;
++count;
@@ -2502,13 +2596,13 @@ void set_completion(colnr_T startcol, list_T *list)
startcol = curwin->w_cursor.col;
compl_col = startcol;
compl_length = (int)curwin->w_cursor.col - (int)startcol;
- /* compl_pattern doesn't need to be set */
+ // compl_pattern doesn't need to be set
compl_orig_text = vim_strnsave(get_cursor_line_ptr() + compl_col,
compl_length);
if (p_ic) {
flags |= CP_ICASE;
}
- if (ins_compl_add(compl_orig_text, -1, NULL, NULL, false, 0,
+ if (ins_compl_add(compl_orig_text, -1, NULL, NULL, false, NULL, 0,
flags, false) != OK) {
return;
}
@@ -2635,10 +2729,10 @@ void ins_compl_show_pum(void)
if (!pum_wanted() || !pum_enough_matches())
return;
- /* Dirty hard-coded hack: remove any matchparen highlighting. */
+ // Dirty hard-coded hack: remove any matchparen highlighting.
do_cmdline_cmd("if exists('g:loaded_matchparen')|3match none|endif");
- /* Update the screen before drawing the popup menu over it. */
+ // Update the screen before drawing the popup menu over it.
update_screen(0);
if (compl_match_array == NULL) {
@@ -2729,17 +2823,19 @@ void ins_compl_show_pum(void)
compl = compl->cp_next;
} while (compl != NULL && compl != compl_first_match);
- if (!shown_match_ok) /* no displayed match at all */
+ if (!shown_match_ok) { // no displayed match at all
cur = -1;
+ }
} else {
- /* popup menu already exists, only need to find the current item.*/
- for (i = 0; i < compl_match_arraysize; ++i)
+ // popup menu already exists, only need to find the current item.
+ for (i = 0; i < compl_match_arraysize; i++) {
if (compl_match_array[i].pum_text == compl_shown_match->cp_str
|| compl_match_array[i].pum_text
== compl_shown_match->cp_text[CPT_ABBR]) {
cur = i;
break;
}
+ }
}
// In Replace mode when a $ is displayed at the end of the line only
@@ -2759,8 +2855,8 @@ void ins_compl_show_pum(void)
}
}
-#define DICT_FIRST (1) /* use just first element in "dict" */
-#define DICT_EXACT (2) /* "dict" is the exact name of a file */
+#define DICT_FIRST (1) // use just first element in "dict"
+#define DICT_EXACT (2) // "dict" is the exact name of a file
/*
* Add any identifiers that match the given pattern in the list of dictionary
@@ -2770,8 +2866,8 @@ static void
ins_compl_dictionaries (
char_u *dict_start,
char_u *pat,
- int flags, /* DICT_FIRST and/or DICT_EXACT */
- int thesaurus /* Thesaurus completion */
+ int flags, // DICT_FIRST and/or DICT_EXACT
+ int thesaurus // Thesaurus completion
)
{
char_u *dict = dict_start;
@@ -2793,9 +2889,9 @@ ins_compl_dictionaries (
}
buf = xmalloc(LSIZE);
- regmatch.regprog = NULL; /* so that we can goto theend */
+ regmatch.regprog = NULL; // so that we can goto theend
- /* If 'infercase' is set, don't use 'smartcase' here */
+ // If 'infercase' is set, don't use 'smartcase' here
save_p_scs = p_scs;
if (curbuf->b_p_inf)
p_scs = FALSE;
@@ -2818,10 +2914,10 @@ ins_compl_dictionaries (
goto theend;
}
- /* ignore case depends on 'ignorecase', 'smartcase' and "pat" */
+ // ignore case depends on 'ignorecase', 'smartcase' and "pat"
regmatch.rm_ic = ignorecase(pat);
while (*dict != NUL && !got_int && !compl_interrupted) {
- /* copy one dictionary file name into buf */
+ // copy one dictionary file name into buf
if (flags == DICT_EXACT) {
count = 1;
files = &dict;
@@ -2846,7 +2942,7 @@ ins_compl_dictionaries (
else
ptr = pat;
spell_dump_compl(ptr, regmatch.rm_ic, &dir, 0);
- } else if (count > 0) { /* avoid warning for using "files" uninit */
+ } else if (count > 0) { // avoid warning for using "files" uninit
ins_compl_files(count, files, thesaurus, flags,
&regmatch, buf, &dir);
if (flags != DICT_EXACT)
@@ -2912,20 +3008,18 @@ static void ins_compl_files(int count, char_u **files, int thesaurus, int flags,
break;
wstart = ptr;
- /* Find end of the word. */
- if (has_mbyte)
- /* Japanese words may have characters in
- * different classes, only separate words
- * with single-byte non-word characters. */
- while (*ptr != NUL) {
- int l = (*mb_ptr2len)(ptr);
-
- if (l < 2 && !vim_iswordc(*ptr))
- break;
- ptr += l;
+ // Find end of the word.
+ // Japanese words may have characters in
+ // different classes, only separate words
+ // with single-byte non-word characters.
+ while (*ptr != NUL) {
+ const int l = utfc_ptr2len(ptr);
+
+ if (l < 2 && !vim_iswordc(*ptr)) {
+ break;
}
- else
- ptr = find_word_end(ptr);
+ ptr += l;
+ }
// Add the word. Skip the regexp match.
if (wstart != regmatch->startp[0]) {
@@ -2934,15 +3028,17 @@ static void ins_compl_files(int count, char_u **files, int thesaurus, int flags,
}
}
}
- if (add_r == OK)
- /* if dir was BACKWARD then honor it just once */
+ if (add_r == OK) {
+ // if dir was BACKWARD then honor it just once
*dir = FORWARD;
- else if (add_r == FAIL)
+ } else if (add_r == FAIL) {
break;
- /* avoid expensive call to vim_regexec() when at end
- * of line */
- if (*ptr == '\n' || got_int)
+ }
+ // avoid expensive call to vim_regexec() when at end
+ // of line
+ if (*ptr == '\n' || got_int) {
break;
+ }
}
line_breakcheck();
ins_compl_check_keys(50, false);
@@ -3030,6 +3126,7 @@ static void ins_compl_free(void)
for (int i = 0; i < CPT_COUNT; i++) {
xfree(match->cp_text[i]);
}
+ tv_clear(&match->cp_user_data);
xfree(match);
} while (compl_curr_match != NULL && compl_curr_match != compl_first_match);
compl_first_match = compl_curr_match = NULL;
@@ -3048,7 +3145,9 @@ static void ins_compl_clear(void)
XFREE_CLEAR(compl_orig_text);
compl_enter_selects = false;
// clear v:completed_item
- set_vim_var_dict(VV_COMPLETED_ITEM, tv_dict_alloc());
+ dict_T *const d = tv_dict_alloc();
+ d->dv_lock = VAR_FIXED;
+ set_vim_var_dict(VV_COMPLETED_ITEM, d);
}
/// Check that Insert completion is active.
@@ -3123,8 +3222,11 @@ void get_complete_info(list_T *what_list, dict_T *retdict)
(char *)EMPTY_IF_NULL(match->cp_text[CPT_KIND]));
tv_dict_add_str(di, S_LEN("info"),
(char *)EMPTY_IF_NULL(match->cp_text[CPT_INFO]));
- tv_dict_add_str(di, S_LEN("user_data"),
- (char *)EMPTY_IF_NULL(match->cp_text[CPT_USER_DATA]));
+ if (match->cp_user_data.v_type == VAR_UNKNOWN) {
+ tv_dict_add_str(di, S_LEN("user_data"), "");
+ } else {
+ tv_dict_add_tv(di, S_LEN("user_data"), &match->cp_user_data);
+ }
}
match = match->cp_next;
} while (match != NULL && match != compl_first_match);
@@ -3187,9 +3289,10 @@ static int ins_compl_bs(void)
xfree(compl_leader);
compl_leader = vim_strnsave(line + compl_col, (int)(p - line) - compl_col);
ins_compl_new_leader();
- if (compl_shown_match != NULL)
- /* Make sure current match is not a hidden item. */
+ if (compl_shown_match != NULL) {
+ // Make sure current match is not a hidden item.
compl_curr_match = compl_shown_match;
+ }
return NUL;
}
@@ -3233,7 +3336,7 @@ static void ins_compl_new_leader(void)
compl_enter_selects = !compl_used_match;
- /* Show the popup menu with a different set of matches. */
+ // Show the popup menu with a different set of matches.
ins_compl_show_pum();
/* Don't let Enter select the original text when there is no popup menu.
@@ -3276,9 +3379,10 @@ static void ins_compl_addleader(int c)
ins_char(c);
}
- /* If we didn't complete finding matches we must search again. */
- if (ins_compl_need_restart())
+ // If we didn't complete finding matches we must search again.
+ if (ins_compl_need_restart()) {
ins_compl_restart();
+ }
xfree(compl_leader);
compl_leader = vim_strnsave(get_cursor_line_ptr() + compl_col,
@@ -3334,9 +3438,9 @@ static void ins_compl_addfrommatch(void)
compl_T *cp;
assert(compl_shown_match != NULL);
p = compl_shown_match->cp_str;
- if ((int)STRLEN(p) <= len) { /* the match is too short */
- /* When still at the original match use the first entry that matches
- * the leader. */
+ if ((int)STRLEN(p) <= len) { // the match is too short
+ // When still at the original match use the first entry that matches
+ // the leader.
if (compl_shown_match->cp_flags & CP_ORIGINAL_TEXT) {
p = NULL;
for (cp = compl_shown_match->cp_next; cp != NULL
@@ -3368,6 +3472,7 @@ static bool ins_compl_prep(int c)
{
char_u *ptr;
bool retval = false;
+ const int prev_mode = ctrl_x_mode;
/* Forget any previous 'special' messages if this is actually
* a ^X mode key - bar ^R, in which case we wait to see what it gives us.
@@ -3375,14 +3480,14 @@ static bool ins_compl_prep(int c)
if (c != Ctrl_R && vim_is_ctrl_x_key(c))
edit_submode_extra = NULL;
- /* Ignore end of Select mode mapping and mouse scroll buttons. */
+ // Ignore end of Select mode mapping and mouse scroll buttons.
if (c == K_SELECT || c == K_MOUSEDOWN || c == K_MOUSEUP
|| c == K_MOUSELEFT || c == K_MOUSERIGHT || c == K_EVENT
|| c == K_COMMAND) {
return retval;
}
- /* Set "compl_get_longest" when finding the first matches. */
+ // Set "compl_get_longest" when finding the first matches.
if (ctrl_x_mode == CTRL_X_NOT_DEFINED_YET
|| (ctrl_x_mode == CTRL_X_NORMAL && !compl_started)) {
compl_get_longest = (strstr((char *)p_cot, "longest") != NULL);
@@ -3415,7 +3520,7 @@ static bool ins_compl_prep(int c)
ctrl_x_mode = CTRL_X_DICTIONARY;
break;
case Ctrl_R:
- /* Simply allow ^R to happen without affecting ^X mode */
+ // Simply allow ^R to happen without affecting ^X mode
break;
case Ctrl_T:
ctrl_x_mode = CTRL_X_THESAURUS;
@@ -3429,9 +3534,9 @@ static bool ins_compl_prep(int c)
case 's':
case Ctrl_S:
ctrl_x_mode = CTRL_X_SPELL;
- ++emsg_off; /* Avoid getting the E756 error twice. */
+ emsg_off++; // Avoid getting the E756 error twice.
spell_back_to_badword();
- --emsg_off;
+ emsg_off--;
break;
case Ctrl_RSB:
ctrl_x_mode = CTRL_X_TAGS;
@@ -3530,10 +3635,10 @@ static bool ins_compl_prep(int c)
// When completing whole lines: fix indent for 'cindent'.
// Otherwise, break line if it's too long.
if (compl_cont_mode == CTRL_X_WHOLE_LINE) {
- /* re-indent the current line */
+ // re-indent the current line
if (want_cindent) {
do_c_expr_indent();
- want_cindent = FALSE; /* don't do it again */
+ want_cindent = false; // don't do it again
}
} else {
int prev_col = curwin->w_cursor.col;
@@ -3576,6 +3681,12 @@ static bool ins_compl_prep(int c)
auto_format(FALSE, TRUE);
+ // Trigger the CompleteDonePre event to give scripts a chance to
+ // act upon the completion before clearing the info, and restore
+ // ctrl_x_mode, so that complete_info() can be used.
+ ctrl_x_mode = prev_mode;
+ ins_apply_autocmds(EVENT_COMPLETEDONEPRE);
+
ins_compl_free();
compl_started = false;
compl_matches = 0;
@@ -3600,8 +3711,8 @@ static bool ins_compl_prep(int c)
*/
if (want_cindent && in_cinkeys(KEY_COMPLETE, ' ', inindent(0)))
do_c_expr_indent();
- /* Trigger the CompleteDone event to give scripts a chance to act
- * upon the completion. */
+ // Trigger the CompleteDone event to give scripts a chance to act
+ // upon the end of completion.
ins_apply_autocmds(EVENT_COMPLETEDONE);
}
} else if (ctrl_x_mode == CTRL_X_LOCAL_MSG)
@@ -3631,10 +3742,11 @@ static void ins_compl_fixRedoBufForLeader(char_u *ptr_arg)
char_u *ptr = ptr_arg;
if (ptr == NULL) {
- if (compl_leader != NULL)
+ if (compl_leader != NULL) {
ptr = compl_leader;
- else
- return; /* nothing to do */
+ } else {
+ return; // nothing to do
+ }
}
if (compl_orig_text != NULL) {
p = compl_orig_text;
@@ -3663,9 +3775,10 @@ static buf_T *ins_compl_next_buf(buf_T *buf, int flag)
{
static win_T *wp;
- if (flag == 'w') { /* just windows */
- if (buf == curbuf) /* first call for this flag/expansion */
+ if (flag == 'w') { // just windows
+ if (buf == curbuf) { // first call for this flag/expansion
wp = curwin;
+ }
assert(wp);
while ((wp = (wp->w_next != NULL ? wp->w_next : firstwin)) != curwin
&& wp->w_buffer->b_scanned)
@@ -3730,6 +3843,8 @@ expand_by_function(
case VAR_DICT:
matchdict = rettv.vval.v_dict;
break;
+ case VAR_SPECIAL:
+ FALLTHROUGH;
default:
// TODO(brammool): Give error message?
tv_clear(&rettv);
@@ -3741,7 +3856,7 @@ expand_by_function(
EMSG(_(e_complwin));
goto theend;
}
- curwin->w_cursor = pos; /* restore the cursor position */
+ curwin->w_cursor = pos; // restore the cursor position
validate_cursor();
if (!equalpos(curwin->w_cursor, pos)) {
EMSG(_(e_compldel));
@@ -3825,15 +3940,16 @@ int ins_compl_add_tv(typval_T *const tv, const Direction dir)
bool empty = false;
int flags = 0;
char *(cptext[CPT_COUNT]);
+ typval_T user_data;
+ user_data.v_type = VAR_UNKNOWN;
if (tv->v_type == VAR_DICT && tv->vval.v_dict != NULL) {
word = tv_dict_get_string(tv->vval.v_dict, "word", false);
cptext[CPT_ABBR] = tv_dict_get_string(tv->vval.v_dict, "abbr", true);
cptext[CPT_MENU] = tv_dict_get_string(tv->vval.v_dict, "menu", true);
cptext[CPT_KIND] = tv_dict_get_string(tv->vval.v_dict, "kind", true);
cptext[CPT_INFO] = tv_dict_get_string(tv->vval.v_dict, "info", true);
- cptext[CPT_USER_DATA] = tv_dict_get_string(tv->vval.v_dict,
- "user_data", true);
+ tv_dict_get_tv(tv->vval.v_dict, "user_data", &user_data);
if (tv_dict_get_number(tv->vval.v_dict, "icase")) {
flags |= CP_ICASE;
@@ -3855,7 +3971,7 @@ int ins_compl_add_tv(typval_T *const tv, const Direction dir)
return FAIL;
}
return ins_compl_add((char_u *)word, -1, NULL,
- (char_u **)cptext, true, dir, flags, dup);
+ (char_u **)cptext, true, &user_data, dir, flags, dup);
}
// Get the next expansion(s), using "compl_pattern".
@@ -3988,7 +4104,7 @@ static int ins_compl_get_exp(pos_T *ini)
type = CTRL_X_PATH_DEFINES;
else if (*e_cpt == ']' || *e_cpt == 't') {
type = CTRL_X_TAGS;
- vim_snprintf((char *)IObuff, IOSIZE, _("Scanning tags."));
+ vim_snprintf((char *)IObuff, IOSIZE, "%s", _("Scanning tags."));
(void)msg_trunc_attr(IObuff, true, HL_ATTR(HLF_R));
} else {
type = -1;
@@ -4047,12 +4163,14 @@ static int ins_compl_get_exp(pos_T *ini)
// Find up to TAG_MANY matches. Avoids that an enormous number
// of matches is found when compl_pattern is empty
+ g_tag_at_cursor = true;
if (find_tags(compl_pattern, &num_matches, &matches,
TAG_REGEXP | TAG_NAMES | TAG_NOIC | TAG_INS_COMP
| (l_ctrl_x_mode != CTRL_X_NORMAL ? TAG_VERBOSE : 0),
TAG_MANY, curbuf->b_ffname) == OK && num_matches > 0) {
ins_compl_add_matches(num_matches, matches, p_ic);
}
+ g_tag_at_cursor = false;
p_ic = save_p_ic;
break;
@@ -4116,7 +4234,7 @@ static int ins_compl_get_exp(pos_T *ini)
compl_direction,
compl_pattern, 1L,
SEARCH_KEEP + SEARCH_NFMSG,
- RE_LAST, (linenr_T)0, NULL, NULL);
+ RE_LAST, NULL);
}
msg_silent--;
if (!compl_started || set_match_pos) {
@@ -4303,7 +4421,9 @@ static void ins_compl_delete(void)
// causes flicker, thus we can't do that.
changed_cline_bef_curs();
// clear v:completed_item
- set_vim_var_dict(VV_COMPLETED_ITEM, tv_dict_alloc());
+ dict_T *const d = tv_dict_alloc();
+ d->dv_lock = VAR_FIXED;
+ set_vim_var_dict(VV_COMPLETED_ITEM, d);
}
// Insert the new text being completed.
@@ -4325,6 +4445,7 @@ static dict_T *ins_compl_dict_alloc(compl_T *match)
{
// { word, abbr, menu, kind, info }
dict_T *dict = tv_dict_alloc();
+ dict->dv_lock = VAR_FIXED;
tv_dict_add_str(
dict, S_LEN("word"),
(const char *)EMPTY_IF_NULL(match->cp_str));
@@ -4340,9 +4461,11 @@ static dict_T *ins_compl_dict_alloc(compl_T *match)
tv_dict_add_str(
dict, S_LEN("info"),
(const char *)EMPTY_IF_NULL(match->cp_text[CPT_INFO]));
- tv_dict_add_str(
- dict, S_LEN("user_data"),
- (const char *)EMPTY_IF_NULL(match->cp_text[CPT_USER_DATA]));
+ if (match->cp_user_data.v_type == VAR_UNKNOWN) {
+ tv_dict_add_str(dict, S_LEN("user_data"), "");
+ } else {
+ tv_dict_add_tv(dict, S_LEN("user_data"), &match->cp_user_data);
+ }
return dict;
}
@@ -4374,8 +4497,7 @@ ins_compl_next (
int num_matches = -1;
int todo = count;
compl_T *found_compl = NULL;
- int found_end = FALSE;
- int advance;
+ bool found_end = false;
const bool started = compl_started;
/* When user complete function return -1 for findstart which is next
@@ -4411,17 +4533,17 @@ ins_compl_next (
if (allow_get_expansion && insert_match
&& (!(compl_get_longest || compl_restarting) || compl_used_match))
- /* Delete old text to be replaced */
+ // Delete old text to be replaced
ins_compl_delete();
- /* When finding the longest common text we stick at the original text,
- * don't let CTRL-N or CTRL-P move to the first match. */
- advance = count != 1 || !allow_get_expansion || !compl_get_longest;
+ // When finding the longest common text we stick at the original text,
+ // don't let CTRL-N or CTRL-P move to the first match.
+ bool advance = count != 1 || !allow_get_expansion || !compl_get_longest;
- /* When restarting the search don't insert the first match either. */
+ // When restarting the search don't insert the first match either.
if (compl_restarting) {
- advance = FALSE;
- compl_restarting = FALSE;
+ advance = false;
+ compl_restarting = false;
}
/* Repeat this for when <PageUp> or <PageDown> is typed. But don't wrap
@@ -4455,10 +4577,10 @@ ins_compl_next (
++compl_pending;
}
- /* Find matches. */
+ // Find matches.
num_matches = ins_compl_get_exp(&compl_startpos);
- /* handle any pending completions */
+ // handle any pending completions
while (compl_pending != 0 && compl_direction == compl_shows_dir
&& advance) {
if (compl_pending > 0 && compl_shown_match->cp_next != NULL) {
@@ -4471,7 +4593,7 @@ ins_compl_next (
} else
break;
}
- found_end = FALSE;
+ found_end = false;
}
if ((compl_shown_match->cp_flags & CP_ORIGINAL_TEXT) == 0
&& compl_leader != NULL
@@ -4483,17 +4605,17 @@ ins_compl_next (
found_compl = compl_shown_match;
}
- /* Stop at the end of the list when we found a usable match. */
+ // Stop at the end of the list when we found a usable match.
if (found_end) {
if (found_compl != NULL) {
compl_shown_match = found_compl;
break;
}
- todo = 1; /* use first usable match after wrapping around */
+ todo = 1; // use first usable match after wrapping around
}
}
- /* Insert the text of the new completion, or the compl_leader. */
+ // Insert the text of the new completion, or the compl_leader.
if (compl_no_insert && !started) {
ins_bytes(compl_orig_text + ins_compl_len());
compl_used_match = false;
@@ -4587,9 +4709,10 @@ void ins_compl_check_keys(int frequency, int in_compl_func)
return;
}
- /* Only do this at regular intervals */
- if (++count < frequency)
+ // Only do this at regular intervals
+ if (++count < frequency) {
return;
+ }
count = 0;
/* Check for a typed key. Do use mappings, otherwise vim_is_ctrl_x_key()
@@ -4597,7 +4720,7 @@ void ins_compl_check_keys(int frequency, int in_compl_func)
int c = vpeekc_any();
if (c != NUL) {
if (vim_is_ctrl_x_key(c) && c != Ctrl_X && c != Ctrl_R) {
- c = safe_vgetc(); /* Eat the character */
+ c = safe_vgetc(); // Eat the character
compl_shows_dir = ins_compl_key2dir(c);
(void)ins_compl_next(false, ins_compl_key2count(c),
c != K_UP && c != K_DOWN, in_compl_func);
@@ -4666,8 +4789,9 @@ static int ins_compl_key2count(int c)
if (ins_compl_pum_key(c) && c != K_UP && c != K_DOWN) {
h = pum_get_height();
- if (h > 3)
- h -= 2; /* keep some context */
+ if (h > 3) {
+ h -= 2; // keep some context
+ }
return h;
}
return 1;
@@ -4705,8 +4829,8 @@ static bool ins_compl_use_match(int c)
static int ins_complete(int c, bool enable_pum)
{
char_u *line;
- int startcol = 0; /* column where searched text starts */
- colnr_T curs_col; /* cursor column */
+ int startcol = 0; // column where searched text starts
+ colnr_T curs_col; // cursor column
int n;
int save_w_wrow;
int save_w_leftcol;
@@ -4718,7 +4842,7 @@ static int ins_complete(int c, bool enable_pum)
insert_match = ins_compl_use_match(c);
if (!compl_started) {
- /* First time we hit ^N or ^P (in a row, I mean) */
+ // First time we hit ^N or ^P (in a row, I mean)
did_ai = false;
did_si = false;
@@ -4756,7 +4880,7 @@ static int ins_complete(int c, bool enable_pum)
compl_col = (colnr_T)getwhitecols(line);
compl_startpos.col = compl_col;
compl_startpos.lnum = curwin->w_cursor.lnum;
- compl_cont_status &= ~CONT_SOL; /* clear SOL if present */
+ compl_cont_status &= ~CONT_SOL; // clear SOL if present
} else {
/* S_IPOS was set when we inserted a word that was at the
* beginning of the line, which means that we'll go to SOL
@@ -4788,7 +4912,7 @@ static int ins_complete(int c, bool enable_pum)
} else
compl_cont_status &= CONT_LOCAL;
- if (!(compl_cont_status & CONT_ADDING)) { /* normal expansion */
+ if (!(compl_cont_status & CONT_ADDING)) { // normal expansion
compl_cont_mode = ctrl_x_mode;
if (ctrl_x_mode != CTRL_X_NORMAL) {
// Remove LOCAL if ctrl_x_mode != CTRL_X_NORMAL
@@ -4817,7 +4941,7 @@ static int ins_complete(int c, bool enable_pum)
} else if (compl_cont_status & CONT_ADDING) {
char_u *prefix = (char_u *)"\\<";
- /* we need up to 2 extra chars for the prefix */
+ // we need up to 2 extra chars for the prefix
compl_pattern = xmalloc(quote_meta(NULL, line + compl_col,
compl_length) + 2);
if (!vim_iswordp(line + compl_col)
@@ -4869,14 +4993,16 @@ static int ins_complete(int c, bool enable_pum)
} else if (CTRL_X_MODE_LINE_OR_EVAL(ctrl_x_mode)) {
compl_col = (colnr_T)getwhitecols(line);
compl_length = (int)curs_col - (int)compl_col;
- if (compl_length < 0) /* cursor in indent: empty pattern */
+ if (compl_length < 0) { // cursor in indent: empty pattern
compl_length = 0;
- if (p_ic)
+ }
+ if (p_ic) {
compl_pattern = str_foldcase(line + compl_col, compl_length, NULL, 0);
- else
+ } else {
compl_pattern = vim_strnsave(line + compl_col, compl_length);
+ }
} else if (ctrl_x_mode == CTRL_X_FILES) {
- /* Go back to just before the first filename character. */
+ // Go back to just before the first filename character.
if (startcol > 0) {
char_u *p = line + startcol;
@@ -4948,7 +5074,7 @@ static int ins_complete(int c, bool enable_pum)
EMSG(_(e_complwin));
return FAIL;
}
- curwin->w_cursor = pos; /* restore the cursor position */
+ curwin->w_cursor = pos; // restore the cursor position
validate_cursor();
if (!equalpos(curwin->w_cursor, pos)) {
EMSG(_(e_compldel));
@@ -5000,7 +5126,7 @@ static int ins_complete(int c, bool enable_pum)
spell_expand_check_cap(compl_col);
compl_length = (int)curs_col - compl_col;
}
- /* Need to obtain "line" again, it may have become invalid. */
+ // Need to obtain "line" again, it may have become invalid.
line = ml_get(curwin->w_cursor.lnum);
compl_pattern = vim_strnsave(line + compl_col, compl_length);
} else {
@@ -5011,7 +5137,7 @@ static int ins_complete(int c, bool enable_pum)
if (compl_cont_status & CONT_ADDING) {
edit_submode_pre = (char_u *)_(" Adding");
if (CTRL_X_MODE_LINE_OR_EVAL(ctrl_x_mode)) {
- /* Insert a new line, keep indentation but ignore 'comments' */
+ // Insert a new line, keep indentation but ignore 'comments'
char_u *old = curbuf->b_p_com;
curbuf->b_p_com = (char_u *)"";
@@ -5036,13 +5162,13 @@ static int ins_complete(int c, bool enable_pum)
* the redo buffer. */
ins_compl_fixRedoBufForLeader(NULL);
- /* Always add completion for the original text. */
+ // Always add completion for the original text.
xfree(compl_orig_text);
compl_orig_text = vim_strnsave(line + compl_col, compl_length);
if (p_ic) {
flags |= CP_ICASE;
}
- if (ins_compl_add(compl_orig_text, -1, NULL, NULL, false, 0,
+ if (ins_compl_add(compl_orig_text, -1, NULL, NULL, false, NULL, 0,
flags, false) != OK) {
XFREE_CLEAR(compl_pattern);
XFREE_CLEAR(compl_orig_text);
@@ -5073,8 +5199,9 @@ static int ins_complete(int c, bool enable_pum)
n = ins_compl_next(true, ins_compl_key2count(c), insert_match, false);
- if (n > 1) /* all matches have been found */
+ if (n > 1) { // all matches have been found
compl_matches = n;
+ }
compl_curr_match = compl_shown_match;
compl_direction = compl_shows_dir;
@@ -5085,7 +5212,7 @@ static int ins_complete(int c, bool enable_pum)
got_int = FALSE;
}
- /* we found no match if the list has only the "compl_orig_text"-entry */
+ // we found no match if the list has only the "compl_orig_text"-entry
if (compl_first_match == compl_first_match->cp_next) {
edit_submode_extra = (compl_cont_status & CONT_ADDING)
&& compl_length > 1
@@ -5121,7 +5248,7 @@ static int ins_complete(int c, bool enable_pum)
edit_submode_extra = (char_u *)_("The only match");
edit_submode_highl = HLF_COUNT;
} else {
- /* Update completion sequence number when needed. */
+ // Update completion sequence number when needed.
if (compl_curr_match->cp_number == -1) {
int number = 0;
compl_T *match;
@@ -5144,24 +5271,27 @@ static int ins_complete(int c, bool enable_pum)
match != NULL && match->cp_number == -1;
match = match->cp_next)
match->cp_number = ++number;
- } else { /* BACKWARD */
- /* search forwards (upwards) for the first valid (!= -1)
- * number. This should normally succeed already at the
- * first loop cycle, so it's fast! */
- for (match = compl_curr_match->cp_next; match != NULL
- && match != compl_first_match;
- match = match->cp_next)
+ } else { // BACKWARD
+ // search forwards (upwards) for the first valid (!= -1)
+ // number. This should normally succeed already at the
+ // first loop cycle, so it's fast!
+ for (match = compl_curr_match->cp_next;
+ match != NULL && match != compl_first_match;
+ match = match->cp_next) {
if (match->cp_number != -1) {
number = match->cp_number;
break;
}
- if (match != NULL)
- /* go down and assign all numbers which are not
- * assigned yet */
- for (match = match->cp_prev; match
- && match->cp_number == -1;
- match = match->cp_prev)
+ }
+ if (match != NULL) {
+ // go down and assign all numbers which are not
+ // assigned yet
+ for (match = match->cp_prev;
+ match && match->cp_number == -1;
+ match = match->cp_prev) {
match->cp_number = ++number;
+ }
+ }
}
}
@@ -5188,7 +5318,7 @@ static int ins_complete(int c, bool enable_pum)
}
}
- /* Show a message about what (completion) mode we're in. */
+ // Show a message about what (completion) mode we're in.
showmode();
if (!shortmess(SHM_COMPLETIONMENU)) {
if (edit_submode_extra != NULL) {
@@ -5220,7 +5350,7 @@ static int ins_complete(int c, bool enable_pum)
*/
static unsigned quote_meta(char_u *dest, char_u *src, int len)
{
- unsigned m = (unsigned)len + 1; /* one extra for the NUL */
+ unsigned m = (unsigned)len + 1; // one extra for the NUL
for (; --len >= 0; src++) {
switch (*src) {
@@ -5232,8 +5362,9 @@ static unsigned quote_meta(char_u *dest, char_u *src, int len)
break;
FALLTHROUGH;
case '~':
- if (!p_magic) /* quote these only if magic is set */
+ if (!p_magic) { // quote these only if magic is set
break;
+ }
FALLTHROUGH;
case '\\':
if (ctrl_x_mode == CTRL_X_DICTIONARY
@@ -5243,24 +5374,24 @@ static unsigned quote_meta(char_u *dest, char_u *src, int len)
case '^': // currently it's not needed.
case '$':
m++;
- if (dest != NULL)
+ if (dest != NULL) {
*dest++ = '\\';
+ }
break;
}
- if (dest != NULL)
+ if (dest != NULL) {
*dest++ = *src;
- /* Copy remaining bytes of a multibyte character. */
- if (has_mbyte) {
- int i, mb_len;
-
- mb_len = (*mb_ptr2len)(src) - 1;
- if (mb_len > 0 && len >= mb_len)
- for (i = 0; i < mb_len; ++i) {
- --len;
- ++src;
- if (dest != NULL)
- *dest++ = *src;
+ }
+ // Copy remaining bytes of a multibyte character.
+ const int mb_len = utfc_ptr2len(src) - 1;
+ if (mb_len > 0 && len >= mb_len) {
+ for (int i = 0; i < mb_len; i++) {
+ len--;
+ src++;
+ if (dest != NULL) {
+ *dest++ = *src;
}
+ }
}
}
if (dest != NULL)
@@ -5287,7 +5418,7 @@ int get_literal(void)
if (got_int)
return Ctrl_C;
- ++no_mapping; /* don't map the next key hits */
+ no_mapping++; // don't map the next key hits
cc = 0;
i = 0;
for (;; ) {
@@ -5325,20 +5456,23 @@ int get_literal(void)
if (cc > 255
&& unicode == 0
)
- cc = 255; /* limit range to 0-255 */
+ cc = 255; // limit range to 0-255
nc = 0;
- if (hex) { /* hex: up to two chars */
- if (i >= 2)
+ if (hex) { // hex: up to two chars
+ if (i >= 2) {
break;
- } else if (unicode) { /* Unicode: up to four or eight chars */
- if ((unicode == 'u' && i >= 4) || (unicode == 'U' && i >= 8))
+ }
+ } else if (unicode) { // Unicode: up to four or eight chars
+ if ((unicode == 'u' && i >= 4) || (unicode == 'U' && i >= 8)) {
break;
- } else if (i >= 3) /* decimal or octal: up to three chars */
+ }
+ } else if (i >= 3) { // decimal or octal: up to three chars
break;
+ }
}
- if (i == 0) { /* no number entered */
- if (nc == K_ZERO) { /* NUL is stored as NL */
+ if (i == 0) { // no number entered
+ if (nc == K_ZERO) { // NUL is stored as NL
cc = '\n';
nc = 0;
} else {
@@ -5354,7 +5488,7 @@ int get_literal(void)
--no_mapping;
if (nc)
vungetc(nc);
- got_int = FALSE; /* CTRL-C typed after CTRL-V is not an interrupt */
+ got_int = false; // CTRL-C typed after CTRL-V is not an interrupt
return cc;
}
@@ -5417,11 +5551,10 @@ static void insert_special(int c, int allow_modmask, int ctrlv)
* INSCHAR_DO_COM - format comments
* INSCHAR_COM_LIST - format comments with num list or 2nd line indent
*/
-void
-insertchar (
- int c, /* character to insert or NUL */
- int flags, /* INSCHAR_FORMAT, etc. */
- int second_indent /* indent for second line if >= 0 */
+void insertchar(
+ int c, // character to insert or NUL
+ int flags, // INSCHAR_FORMAT, etc.
+ int second_indent // indent for second line if >= 0
)
{
int textwidth;
@@ -5457,32 +5590,32 @@ insertchar (
|| ((!has_format_option(FO_INS_LONG)
|| Insstart_textlen <= (colnr_T)textwidth)
&& (!fo_ins_blank
- || Insstart_blank_vcol <= (colnr_T)textwidth
- )))))) {
- /* Format with 'formatexpr' when it's set. Use internal formatting
- * when 'formatexpr' isn't set or it returns non-zero. */
- int do_internal = TRUE;
+ || Insstart_blank_vcol <= (colnr_T)textwidth)))))) {
+ // Format with 'formatexpr' when it's set. Use internal formatting
+ // when 'formatexpr' isn't set or it returns non-zero.
+ bool do_internal = true;
colnr_T virtcol = get_nolist_virtcol()
+ char2cells(c != NUL ? c : gchar_cursor());
if (*curbuf->b_p_fex != NUL && (flags & INSCHAR_NO_FEX) == 0
&& (force_format || virtcol > (colnr_T)textwidth)) {
do_internal = (fex_format(curwin->w_cursor.lnum, 1L, c) != 0);
- /* It may be required to save for undo again, e.g. when setline()
- * was called. */
- ins_need_undo = TRUE;
+ // It may be required to save for undo again, e.g. when setline()
+ // was called.
+ ins_need_undo = true;
}
if (do_internal)
internal_format(textwidth, second_indent, flags, c == NUL, c);
}
- if (c == NUL) /* only formatting was wanted */
+ if (c == NUL) { // only formatting was wanted
return;
+ }
- /* Check whether this character should end a comment. */
+ // Check whether this character should end a comment.
if (did_ai && c == end_comment_pending) {
char_u *line;
- char_u lead_end[COM_MAX_LEN]; /* end-comment string */
+ char_u lead_end[COM_MAX_LEN]; // end-comment string
int middle_len, end_len;
int i;
@@ -5490,39 +5623,40 @@ insertchar (
* Need to remove existing (middle) comment leader and insert end
* comment leader. First, check what comment leader we can find.
*/
- i = get_leader_len(line = get_cursor_line_ptr(), &p, FALSE, TRUE);
- if (i > 0 && vim_strchr(p, COM_MIDDLE) != NULL) { /* Just checking */
- /* Skip middle-comment string */
- while (*p && p[-1] != ':') /* find end of middle flags */
- ++p;
+ i = get_leader_len(line = get_cursor_line_ptr(), &p, false, true);
+ if (i > 0 && vim_strchr(p, COM_MIDDLE) != NULL) { // Just checking
+ // Skip middle-comment string
+ while (*p && p[-1] != ':') { // find end of middle flags
+ p++;
+ }
middle_len = 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;
+ // Don't count trailing white space for middle_len
+ while (middle_len > 0 && ascii_iswhite(lead_end[middle_len - 1])) {
+ middle_len--;
+ }
- /* Find the end-comment string */
- while (*p && p[-1] != ':') /* find end of end flags */
- ++p;
+ // Find the end-comment string
+ while (*p && p[-1] != ':') { // find end of end flags
+ p++;
+ }
end_len = copy_option_part(&p, lead_end, COM_MAX_LEN, ",");
- /* Skip white space before the cursor */
+ // Skip white space before the cursor
i = curwin->w_cursor.col;
while (--i >= 0 && ascii_iswhite(line[i]))
;
i++;
- /* Skip to before the middle leader */
+ // Skip to before the middle leader
i -= middle_len;
- /* Check some expected things before we go on */
+ // Check some expected things before we go on
if (i >= 0 && lead_end[end_len - 1] == end_comment_pending) {
- /* Backspace over all the stuff we want to replace */
+ // 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.
- */
+ // Insert the end-comment string, except for the last
+ // character, which will get inserted as normal later.
ins_bytes_len(lead_end, end_len - 1);
}
}
@@ -5580,8 +5714,8 @@ insertchar (
buf[i++] = c;
}
- do_digraph(-1); /* clear digraphs */
- do_digraph(buf[i-1]); /* may be the start of a digraph */
+ do_digraph(-1); // clear digraphs
+ do_digraph(buf[i-1]); // may be the start of a digraph
buf[i] = NUL;
ins_str(buf);
if (flags & INSCHAR_CTRLV) {
@@ -5603,10 +5737,11 @@ insertchar (
AppendCharToRedobuff(c);
} else {
ins_char(c);
- if (flags & INSCHAR_CTRLV)
+ if (flags & INSCHAR_CTRLV) {
redo_literal(c);
- else
+ } else {
AppendCharToRedobuff(c);
+ }
}
}
}
@@ -5623,7 +5758,7 @@ internal_format (
int second_indent,
int flags,
int format_only,
- int c /* character to be inserted (can be NUL) */
+ int c // character to be inserted (can be NUL)
)
{
int cc;
@@ -5634,7 +5769,7 @@ internal_format (
int fo_white_par = has_format_option(FO_WHITE_PAR);
int first_line = TRUE;
colnr_T leader_len;
- int no_leader = FALSE;
+ bool no_leader = false;
int do_comments = (flags & INSCHAR_DO_COM);
int has_lbr = curwin->w_p_lbr;
@@ -5659,10 +5794,10 @@ internal_format (
* Repeat breaking lines, until the current line is not too long.
*/
while (!got_int) {
- int startcol; /* Cursor column at entry */
- int wantcol; /* column at textwidth border */
- int foundcol; /* column for start of spaces */
- int end_foundcol = 0; /* column for start of word */
+ int startcol; // Cursor column at entry
+ int wantcol; // column at textwidth border
+ int foundcol; // column for start of spaces
+ int end_foundcol = 0; // column for start of word
colnr_T len;
colnr_T virtcol;
int orig_col = 0;
@@ -5675,33 +5810,37 @@ internal_format (
if (virtcol <= (colnr_T)textwidth)
break;
- if (no_leader)
- do_comments = FALSE;
- else if (!(flags & INSCHAR_FORMAT)
- && has_format_option(FO_WRAP_COMS))
- do_comments = TRUE;
+ if (no_leader) {
+ do_comments = false;
+ } else if (!(flags & INSCHAR_FORMAT)
+ && has_format_option(FO_WRAP_COMS)) {
+ do_comments = true;
+ }
- /* Don't break until after the comment leader */
- if (do_comments)
- leader_len = get_leader_len(get_cursor_line_ptr(), NULL, FALSE, TRUE);
- else
+ // Don't break until after the comment leader
+ if (do_comments) {
+ leader_len = get_leader_len(get_cursor_line_ptr(), NULL, false, true);
+ } else {
leader_len = 0;
+ }
- /* If the line doesn't start with a comment leader, then don't
- * start one in a following broken line. Avoids that a %word
- * moved to the start of the next line causes all following lines
- * to start with %. */
- if (leader_len == 0)
- no_leader = TRUE;
+ // If the line doesn't start with a comment leader, then don't
+ // start one in a following broken line. Avoids that a %word
+ // moved to the start of the next line causes all following lines
+ // to start with %.
+ if (leader_len == 0) {
+ no_leader = true;
+ }
if (!(flags & INSCHAR_FORMAT)
&& leader_len == 0
- && !has_format_option(FO_WRAP))
-
+ && !has_format_option(FO_WRAP)) {
break;
- if ((startcol = curwin->w_cursor.col) == 0)
+ }
+ if ((startcol = curwin->w_cursor.col) == 0) {
break;
+ }
- /* find column of textwidth border */
+ // find column of textwidth border
coladvance((colnr_T)textwidth);
wantcol = curwin->w_cursor.col;
@@ -5721,7 +5860,7 @@ internal_format (
else
cc = gchar_cursor();
if (WHITECHAR(cc)) {
- /* remember position of blank just before text */
+ // remember position of blank just before text
end_col = curwin->w_cursor.col;
// find start of sequence of blanks
@@ -5752,18 +5891,21 @@ internal_format (
}
if (has_format_option(FO_ONE_LETTER)) {
- /* do not break after one-letter words */
- if (curwin->w_cursor.col == 0)
- break; /* one-letter word at begin */
- /* do not break "#a b" when 'tw' is 2 */
- if (curwin->w_cursor.col <= leader_len)
+ // do not break after one-letter words
+ if (curwin->w_cursor.col == 0) {
+ break; // one-letter word at begin
+ }
+ // do not break "#a b" when 'tw' is 2
+ if (curwin->w_cursor.col <= leader_len) {
break;
+ }
col = curwin->w_cursor.col;
dec_cursor();
cc = gchar_cursor();
- if (WHITECHAR(cc))
- continue; /* one-letter, continue */
+ if (WHITECHAR(cc)) {
+ continue; // one-letter, continue
+ }
curwin->w_cursor.col = col;
}
@@ -5774,14 +5916,15 @@ internal_format (
if (curwin->w_cursor.col <= (colnr_T)wantcol)
break;
} else if (cc >= 0x100 && fo_multibyte) {
- /* Break after or before a multi-byte character. */
+ // Break after or before a multi-byte character.
if (curwin->w_cursor.col != startcol) {
- /* Don't break until after the comment leader */
- if (curwin->w_cursor.col < leader_len)
+ // Don't break until after the comment leader
+ if (curwin->w_cursor.col < leader_len) {
break;
+ }
col = curwin->w_cursor.col;
inc_cursor();
- /* Don't change end_foundcol if already set. */
+ // Don't change end_foundcol if already set.
if (foundcol != curwin->w_cursor.col) {
foundcol = curwin->w_cursor.col;
end_foundcol = foundcol;
@@ -5799,11 +5942,13 @@ internal_format (
dec_cursor();
cc = gchar_cursor();
- if (WHITECHAR(cc))
- continue; /* break with space */
- /* Don't break until after the comment leader */
- if (curwin->w_cursor.col < leader_len)
+ if (WHITECHAR(cc)) {
+ continue; // break with space
+ }
+ // Don't break until after the comment leader
+ if (curwin->w_cursor.col < leader_len) {
break;
+ }
curwin->w_cursor.col = col;
@@ -5817,12 +5962,12 @@ internal_format (
dec_cursor();
}
- if (foundcol == 0) { /* no spaces, cannot break line */
+ if (foundcol == 0) { // no spaces, cannot break line
curwin->w_cursor.col = startcol;
break;
}
- /* Going to break the line, remove any "$" now. */
+ // Going to break the line, remove any "$" now.
undisplay_dollar();
/*
@@ -5830,10 +5975,11 @@ internal_format (
* stack functions. VREPLACE does not use this, and backspaces
* over the text instead.
*/
- if (State & VREPLACE_FLAG)
- orig_col = startcol; /* Will start backspacing from here */
- else
+ if (State & VREPLACE_FLAG) {
+ orig_col = startcol; // Will start backspacing from here
+ } else {
replace_offset = startcol - end_foundcol;
+ }
/*
* adjust startcol for spaces that will be deleted and
@@ -5856,13 +6002,15 @@ internal_format (
curwin->w_cursor.col = orig_col;
saved_text[startcol] = NUL;
- /* Backspace over characters that will move to the next line */
- if (!fo_white_par)
+ // Backspace over characters that will move to the next line
+ if (!fo_white_par) {
backspace_until_column(foundcol);
+ }
} else {
- /* put cursor after pos. to break line */
- if (!fo_white_par)
+ // put cursor after pos. to break line
+ if (!fo_white_par) {
curwin->w_cursor.col = foundcol;
+ }
}
/*
@@ -5880,32 +6028,29 @@ internal_format (
replace_offset = 0;
if (first_line) {
if (!(flags & INSCHAR_COM_LIST)) {
- /*
- * This section is for auto-wrap of numeric lists. When not
- * in insert mode (i.e. format_lines()), the INSCHAR_COM_LIST
- * flag will be set and open_line() will handle it (as seen
- * above). The code here (and in get_number_indent()) will
- * recognize comments if needed...
- */
- if (second_indent < 0 && has_format_option(FO_Q_NUMBER))
- second_indent =
- get_number_indent(curwin->w_cursor.lnum - 1);
+ // This section is for auto-wrap of numeric lists. When not
+ // in insert mode (i.e. format_lines()), the INSCHAR_COM_LIST
+ // flag will be set and open_line() will handle it (as seen
+ // above). The code here (and in get_number_indent()) will
+ // recognize comments if needed...
+ if (second_indent < 0 && has_format_option(FO_Q_NUMBER)) {
+ second_indent = get_number_indent(curwin->w_cursor.lnum - 1);
+ }
if (second_indent >= 0) {
- if (State & VREPLACE_FLAG)
- change_indent(INDENT_SET, second_indent,
- FALSE, NUL, TRUE);
- else if (leader_len > 0 && second_indent - leader_len > 0) {
- int i;
+ if (State & VREPLACE_FLAG) {
+ change_indent(INDENT_SET, second_indent, false, NUL, true);
+ } else if (leader_len > 0 && second_indent - leader_len > 0) {
int padding = second_indent - leader_len;
- /* We started at the first_line of a numbered list
- * that has a comment. the open_line() function has
- * inserted the proper comment leader and positioned
- * the cursor at the end of the split line. Now we
- * add the additional whitespace needed after the
- * comment leader for the numbered list. */
- for (i = 0; i < padding; i++)
+ // We started at the first_line of a numbered list
+ // that has a comment. the open_line() function has
+ // inserted the proper comment leader and positioned
+ // the cursor at the end of the split line. Now we
+ // add the additional whitespace needed after the
+ // comment leader for the numbered list.
+ for (int i = 0; i < padding; i++) {
ins_str((char_u *)" ");
+ }
changed_bytes(curwin->w_cursor.lnum, leader_len);
} else {
(void)set_indent(second_indent, SIN_CHANGED);
@@ -5943,8 +6088,9 @@ internal_format (
line_breakcheck();
}
- if (save_char != NUL) /* put back space after cursor */
+ if (save_char != NUL) { // put back space after cursor
pchar_cursor(save_char);
+ }
curwin->w_p_lbr = has_lbr;
@@ -5961,10 +6107,9 @@ internal_format (
* The caller must have saved the cursor line for undo, following ones will be
* saved here.
*/
-void
-auto_format (
- int trailblank, /* when TRUE also format with trailing blank */
- int prev_line /* may start in previous line */
+void auto_format(
+ bool trailblank, // when true also format with trailing blank
+ bool prev_line // may start in previous line
)
{
pos_T pos;
@@ -5983,11 +6128,11 @@ auto_format (
// may remove added space
check_auto_format(false);
- /* Don't format in Insert mode when the cursor is on a trailing blank, the
- * user might insert normal text next. Also skip formatting when "1" is
- * in 'formatoptions' and there is a single character before the cursor.
- * Otherwise the line would be broken and when typing another non-white
- * next they are not joined back together. */
+ // Don't format in Insert mode when the cursor is on a trailing blank, the
+ // user might insert normal text next. Also skip formatting when "1" is
+ // in 'formatoptions' and there is a single character before the cursor.
+ // Otherwise the line would be broken and when typing another non-white
+ // next they are not joined back together.
wasatend = (pos.col == (colnr_T)STRLEN(old));
if (*old != NUL && !trailblank && wasatend) {
dec_cursor();
@@ -6030,16 +6175,16 @@ auto_format (
saved_cursor.lnum = 0;
if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) {
- /* "cannot happen" */
+ // "cannot happen"
curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count;
coladvance((colnr_T)MAXCOL);
} else
check_cursor_col();
- /* Insert mode: If the cursor is now after the end of the line while it
- * previously wasn't, the line was broken. Because of the rule above we
- * need to add a space when 'w' is in 'formatoptions' to keep a paragraph
- * formatted. */
+ // Insert mode: If the cursor is now after the end of the line while it
+ // previously wasn't, the line was broken. Because of the rule above we
+ // need to add a space when 'w' is in 'formatoptions' to keep a paragraph
+ // formatted.
if (!wasatend && has_format_option(FO_WHITE_PAR)) {
new = get_cursor_line_ptr();
len = (colnr_T)STRLEN(new);
@@ -6098,22 +6243,21 @@ static void check_auto_format(
* if invalid value, use 0.
* Set default to window width (maximum 79) for "gq" operator.
*/
-int
-comp_textwidth (
- int ff /* force formatting (for "gq" command) */
+int comp_textwidth(
+ int ff // force formatting (for "gq" command)
)
{
int textwidth;
textwidth = curbuf->b_p_tw;
if (textwidth == 0 && curbuf->b_p_wm) {
- /* The width is the window width minus 'wrapmargin' minus all the
- * things that add to the margin. */
+ // The width is the window width minus 'wrapmargin' minus all the
+ // things that add to the margin.
textwidth = curwin->w_width_inner - curbuf->b_p_wm;
if (cmdwin_type != 0) {
textwidth -= 1;
}
- textwidth -= curwin->w_p_fdc;
+ textwidth -= win_fdccol_count(curwin);
textwidth -= win_signcol_count(curwin);
if (curwin->w_p_nu || curwin->w_p_rnu)
@@ -6149,7 +6293,9 @@ static void redo_literal(int c)
// start_arrow() is called when an arrow key is used in insert mode.
// For undo/redo it resembles hitting the <ESC> key.
-static void start_arrow(pos_T *end_insert_pos /* can be NULL */)
+static void start_arrow(
+ pos_T *end_insert_pos // can be NULL
+)
{
start_arrow_common(end_insert_pos, true);
}
@@ -6222,8 +6368,8 @@ int stop_arrow(void)
Insstart_textlen = (colnr_T)linetabsize(get_cursor_line_ptr());
if (u_save_cursor() == OK) {
- arrow_used = FALSE;
- ins_need_undo = FALSE;
+ arrow_used = false;
+ ins_need_undo = false;
}
ai_col = 0;
if (State & VREPLACE_FLAG) {
@@ -6234,11 +6380,12 @@ int stop_arrow(void)
AppendToRedobuff("1i"); // Pretend we start an insertion.
new_insert_skip = 2;
} else if (ins_need_undo) {
- if (u_save_cursor() == OK)
- ins_need_undo = FALSE;
+ if (u_save_cursor() == OK) {
+ ins_need_undo = false;
+ }
}
- /* Always open fold at the cursor line when inserting something. */
+ // Always open fold at the cursor line when inserting something.
foldOpenCursor();
return arrow_used || ins_need_undo ? FAIL : OK;
@@ -6252,15 +6399,15 @@ int stop_arrow(void)
static void
stop_insert (
pos_T *end_insert_pos,
- int esc, /* called by ins_esc() */
- int nomove /* <c-\><c-o>, don't move cursor */
+ int esc, // called by ins_esc()
+ int nomove // <c-\><c-o>, don't move cursor
)
{
int cc;
char_u *ptr;
stop_redo_ins();
- replace_flush(); /* abandon replace stack */
+ replace_flush(); // abandon replace stack
/*
* Save the inserted text for later redo with ^@ and CTRL-A.
@@ -6277,16 +6424,16 @@ stop_insert (
xfree(ptr);
if (!arrow_used && end_insert_pos != NULL) {
- /* Auto-format now. It may seem strange to do this when stopping an
- * insertion (or moving the cursor), but it's required when appending
- * a line and having it end in a space. But only do it when something
- * was actually inserted, otherwise undo won't work. */
+ // Auto-format now. It may seem strange to do this when stopping an
+ // insertion (or moving the cursor), but it's required when appending
+ // a line and having it end in a space. But only do it when something
+ // was actually inserted, otherwise undo won't work.
if (!ins_need_undo && has_format_option(FO_AUTO)) {
pos_T tpos = curwin->w_cursor;
- /* When the cursor is at the end of the line after a space the
- * formatting will move it to the following word. Avoid that by
- * moving the cursor onto the space. */
+ // When the cursor is at the end of the line after a space the
+ // formatting will move it to the following word. Avoid that by
+ // moving the cursor onto the space.
cc = 'x';
if (curwin->w_cursor.col > 0 && gchar_cursor() == NUL) {
dec_cursor();
@@ -6312,11 +6459,11 @@ stop_insert (
// If a space was inserted for auto-formatting, remove it now.
check_auto_format(true);
- /* If we just did an auto-indent, remove the white space from the end
- * of the line, and put the cursor back.
- * Do this when ESC was used or moving the cursor up/down.
- * Check for the old position still being valid, just in case the text
- * got changed unexpectedly. */
+ // If we just did an auto-indent, remove the white space from the end
+ // of the line, and put the cursor back.
+ // Do this when ESC was used or moving the cursor up/down.
+ // Check for the old position still being valid, just in case the text
+ // got changed unexpectedly.
if (!nomove && did_ai && (esc || (vim_strchr(p_cpo, CPO_INDENT) == NULL
&& curwin->w_cursor.lnum !=
end_insert_pos->lnum))
@@ -6324,7 +6471,7 @@ stop_insert (
pos_T tpos = curwin->w_cursor;
curwin->w_cursor = *end_insert_pos;
- check_cursor_col(); /* make sure it is not past the line */
+ check_cursor_col(); // make sure it is not past the line
for (;; ) {
if (gchar_cursor() == NUL && curwin->w_cursor.col > 0)
--curwin->w_cursor.col;
@@ -6336,10 +6483,10 @@ stop_insert (
break; // should not happen
}
}
- if (curwin->w_cursor.lnum != tpos.lnum)
+ if (curwin->w_cursor.lnum != tpos.lnum) {
curwin->w_cursor = tpos;
- else {
- /* reset tpos, could have been invalidated in the loop above */
+ } else {
+ // reset tpos, could have been invalidated in the loop above
tpos = curwin->w_cursor;
tpos.col++;
if (cc != NUL && gchar_pos(&tpos) == NUL) {
@@ -6347,8 +6494,8 @@ stop_insert (
}
}
- /* <C-S-Right> may have started Visual mode, adjust the position for
- * deleted characters. */
+ // <C-S-Right> may have started Visual mode, adjust the position for
+ // deleted characters.
if (VIsual_active && VIsual.lnum == curwin->w_cursor.lnum) {
int len = (int)STRLEN(get_cursor_line_ptr());
@@ -6364,8 +6511,8 @@ stop_insert (
can_si = false;
can_si_back = false;
- /* Set '[ and '] to the inserted text. When end_insert_pos is NULL we are
- * now in a different buffer. */
+ // Set '[ and '] to the inserted text. When end_insert_pos is NULL we are
+ // now in a different buffer.
if (end_insert_pos != NULL) {
curbuf->b_op_start = Insstart;
curbuf->b_op_start_orig = Insstart_orig;
@@ -6384,9 +6531,10 @@ void set_last_insert(int c)
xfree(last_insert);
last_insert = xmalloc(MB_MAXBYTES * 3 + 5);
s = last_insert;
- /* Use the CTRL-V only when entering a special char */
- if (c < ' ' || c == DEL)
+ // Use the CTRL-V only when entering a special char
+ if (c < ' ' || c == DEL) {
*s++ = Ctrl_V;
+ }
s = add_char2buf(c, s);
*s++ = ESC;
*s++ = NUL;
@@ -6472,7 +6620,7 @@ int oneright(void)
if (virtual_active()) {
pos_T prevpos = curwin->w_cursor;
- /* Adjust for multi-wide char (excluding TAB) */
+ // Adjust for multi-wide char (excluding TAB)
ptr = get_cursor_pos_ptr();
coladvance(getviscol() + ((*ptr != TAB && vim_isprintc(utf_ptr2char(ptr))) ?
ptr2cells(ptr) : 1));
@@ -6483,20 +6631,18 @@ int oneright(void)
}
ptr = get_cursor_pos_ptr();
- if (*ptr == NUL)
- return FAIL; /* already at the very end */
+ if (*ptr == NUL) {
+ return FAIL; // already at the very end
+ }
- if (has_mbyte)
- l = (*mb_ptr2len)(ptr);
- else
- l = 1;
+ l = utfc_ptr2len(ptr);
- /* move "l" bytes right, but don't end up on the NUL, unless 'virtualedit'
- * contains "onemore". */
+ // move "l" bytes right, but don't end up on the NUL, unless 'virtualedit'
+ // contains "onemore".
if (ptr[l] == NUL
- && (ve_flags & VE_ONEMORE) == 0
- )
+ && (ve_flags & VE_ONEMORE) == 0) {
return FAIL;
+ }
curwin->w_cursor.col += l;
curwin->w_set_curswant = TRUE;
@@ -6512,25 +6658,23 @@ int oneleft(void)
if (v == 0)
return FAIL;
- /* We might get stuck on 'showbreak', skip over it. */
+ // We might get stuck on 'showbreak', skip over it.
width = 1;
for (;; ) {
coladvance(v - width);
- /* getviscol() is slow, skip it when 'showbreak' is empty,
- 'breakindent' is not set and there are no multi-byte
- characters */
- if ((*p_sbr == NUL
- && !curwin->w_p_bri
- && !has_mbyte
- ) || getviscol() < v)
+ // getviscol() is slow, skip it when 'showbreak' is empty,
+ // 'breakindent' is not set and there are no multi-byte
+ // characters
+ if (getviscol() < v) {
break;
- ++width;
+ }
+ width++;
}
if (curwin->w_cursor.coladd == 1) {
char_u *ptr;
- /* Adjust for multi-wide char (not a TAB) */
+ // Adjust for multi-wide char (not a TAB)
ptr = get_cursor_pos_ptr();
if (*ptr != TAB && vim_isprintc(utf_ptr2char(ptr))
&& ptr2cells(ptr) > 1) {
@@ -6548,17 +6692,16 @@ int oneleft(void)
curwin->w_set_curswant = TRUE;
--curwin->w_cursor.col;
- /* if the character on the left of the current cursor is a multi-byte
- * character, move to its first byte */
- if (has_mbyte)
- mb_adjust_cursor();
+ // if the character on the left of the current cursor is a multi-byte
+ // character, move to its first byte
+ mb_adjust_cursor();
return OK;
}
int
cursor_up (
long n,
- int upd_topline /* When TRUE: update topline */
+ int upd_topline // When TRUE: update topline
)
{
linenr_T lnum;
@@ -6576,19 +6719,21 @@ cursor_up (
/*
* Count each sequence of folded lines as one logical line.
*/
- /* go to the start of the current fold */
+ // go to the start of the current fold
(void)hasFolding(lnum, &lnum, NULL);
while (n--) {
- /* move up one line */
- --lnum;
- if (lnum <= 1)
+ // move up one line
+ lnum--;
+ if (lnum <= 1) {
break;
- /* If we entered a fold, move to the beginning, unless in
- * Insert mode or when 'foldopen' contains "all": it will open
- * in a moment. */
- if (n > 0 || !((State & INSERT) || (fdo_flags & FDO_ALL)))
+ }
+ // If we entered a fold, move to the beginning, unless in
+ // Insert mode or when 'foldopen' contains "all": it will open
+ // in a moment.
+ if (n > 0 || !((State & INSERT) || (fdo_flags & FDO_ALL))) {
(void)hasFolding(lnum, &lnum, NULL);
+ }
}
if (lnum < 1)
lnum = 1;
@@ -6597,11 +6742,12 @@ cursor_up (
curwin->w_cursor.lnum = lnum;
}
- /* try to advance to the column we want to be at */
+ // try to advance to the column we want to be at
coladvance(curwin->w_curswant);
- if (upd_topline)
- update_topline(); /* make sure curwin->w_topline is valid */
+ if (upd_topline) {
+ update_topline(); // make sure curwin->w_topline is valid
+ }
return OK;
}
@@ -6612,14 +6758,14 @@ cursor_up (
int
cursor_down (
long n,
- int upd_topline /* When TRUE: update topline */
+ int upd_topline // When TRUE: update topline
)
{
linenr_T lnum;
if (n > 0) {
lnum = curwin->w_cursor.lnum;
- /* Move to last line of fold, will fail if it's the end-of-file. */
+ // Move to last line of fold, will fail if it's the end-of-file.
(void)hasFolding(lnum, NULL, &lnum);
// This fails if the cursor is already in the last line.
@@ -6631,7 +6777,7 @@ cursor_down (
else if (hasAnyFolding(curwin)) {
linenr_T last;
- /* count each sequence of folded lines as one logical line */
+ // count each sequence of folded lines as one logical line
while (n--) {
if (hasFolding(lnum, NULL, &last))
lnum = last + 1;
@@ -6647,11 +6793,12 @@ cursor_down (
curwin->w_cursor.lnum = lnum;
}
- /* try to advance to the column we want to be at */
+ // try to advance to the column we want to be at
coladvance(curwin->w_curswant);
- if (upd_topline)
- update_topline(); /* make sure curwin->w_topline is valid */
+ if (upd_topline) {
+ update_topline(); // make sure curwin->w_topline is valid
+ }
return OK;
}
@@ -6661,11 +6808,10 @@ cursor_down (
* Last_insert actually is a copy of the redo buffer, so we
* first have to remove the command.
*/
-int
-stuff_inserted (
- int c, /* Command character to be inserted */
- long count, /* Repeat this many times */
- int no_esc /* Don't add an ESC at the end */
+int stuff_inserted(
+ int c, // Command character to be inserted
+ long count, // Repeat this many times
+ int no_esc // Don't add an ESC at the end
)
{
char_u *esc_ptr;
@@ -6679,18 +6825,18 @@ stuff_inserted (
return FAIL;
}
- /* may want to stuff the command character, to start Insert mode */
- if (c != NUL)
+ // may want to stuff the command character, to start Insert mode
+ if (c != NUL) {
stuffcharReadbuff(c);
+ }
if ((esc_ptr = STRRCHR(ptr, ESC)) != NULL) {
// remove the ESC.
*esc_ptr = NUL;
}
- /* when the last char is either "0" or "^" it will be quoted if no ESC
- * comes after it OR if it will inserted more than once and "ptr"
- * starts with ^D. -- Acevedo
- */
+ // when the last char is either "0" or "^" it will be quoted if no ESC
+ // comes after it OR if it will inserted more than once and "ptr"
+ // starts with ^D. -- Acevedo
last_ptr = (esc_ptr ? esc_ptr : ptr + STRLEN(ptr)) - 1;
if (last_ptr >= ptr && (*last_ptr == '0' || *last_ptr == '^')
&& (no_esc || (*ptr == Ctrl_D && count > 1))) {
@@ -6711,12 +6857,14 @@ stuff_inserted (
if (last)
*last_ptr = last;
- if (esc_ptr != NULL)
- *esc_ptr = ESC; /* put the ESC back */
+ if (esc_ptr != NULL) {
+ *esc_ptr = ESC; // put the ESC back
+ }
- /* may want to stuff a trailing ESC, to get out of Insert mode */
- if (!no_esc)
+ // may want to stuff a trailing ESC, to get out of Insert mode
+ if (!no_esc) {
stuffcharReadbuff(ESC);
+ }
return OK;
}
@@ -6741,8 +6889,9 @@ char_u *get_last_insert_save(void)
return NULL;
s = vim_strsave(last_insert + last_insert_skip);
len = (int)STRLEN(s);
- if (len > 0 && s[len - 1] == ESC) /* remove trailing ESC */
+ if (len > 0 && s[len - 1] == ESC) { // remove trailing ESC
s[len - 1] = NUL;
+ }
return s;
}
@@ -6784,8 +6933,8 @@ static bool echeck_abbr(int c)
*/
static char_u *replace_stack = NULL;
-static ssize_t replace_stack_nr = 0; /* next entry in replace stack */
-static ssize_t replace_stack_len = 0; /* max. number of entries */
+static ssize_t replace_stack_nr = 0; // next entry in replace stack
+static ssize_t replace_stack_len = 0; // max. number of entries
/// Push character that is replaced onto the the replace stack.
///
@@ -6839,9 +6988,8 @@ static int replace_pop(void)
* Join the top two items on the replace stack. This removes to "off"'th NUL
* encountered.
*/
-static void
-replace_join (
- int off /* offset for which NUL to remove */
+static void replace_join(
+ int off // offset for which NUL to remove
)
{
int i;
@@ -6864,7 +7012,7 @@ static void replace_pop_ins(void)
int cc;
int oldState = State;
- State = NORMAL; /* don't want REPLACE here */
+ State = NORMAL; // don't want REPLACE here
while ((cc = replace_pop()) > 0) {
mb_replace_pop_ins(cc);
dec_cursor();
@@ -6883,39 +7031,42 @@ static void mb_replace_pop_ins(int cc)
int i;
int c;
- if (has_mbyte && (n = MB_BYTE2LEN(cc)) > 1) {
+ if ((n = MB_BYTE2LEN(cc)) > 1) {
buf[0] = cc;
for (i = 1; i < n; ++i)
buf[i] = replace_pop();
ins_bytes_len(buf, n);
- } else
+ } else {
ins_char(cc);
+ }
- if (enc_utf8)
- /* Handle composing chars. */
- for (;; ) {
- c = replace_pop();
- if (c == -1) /* stack empty */
- break;
- if ((n = MB_BYTE2LEN(c)) == 1) {
- /* Not a multi-byte char, put it back. */
- replace_push(c);
- break;
+ // Handle composing chars.
+ for (;; ) {
+ c = replace_pop();
+ if (c == -1) { // stack empty
+ break;
+ }
+ if ((n = MB_BYTE2LEN(c)) == 1) {
+ // Not a multi-byte char, put it back.
+ replace_push(c);
+ break;
+ } else {
+ buf[0] = c;
+ assert(n > 1);
+ for (i = 1; i < n; i++) {
+ buf[i] = replace_pop();
+ }
+ if (utf_iscomposing(utf_ptr2char(buf))) {
+ ins_bytes_len(buf, n);
} else {
- buf[0] = c;
- assert(n > 1);
- for (i = 1; i < n; ++i)
- buf[i] = replace_pop();
- if (utf_iscomposing(utf_ptr2char(buf)))
- ins_bytes_len(buf, n);
- else {
- /* Not a composing char, put it back. */
- for (i = n - 1; i >= 0; --i)
- replace_push(buf[i]);
- break;
+ // Not a composing char, put it back.
+ for (i = n - 1; i >= 0; i--) {
+ replace_push(buf[i]);
}
+ break;
}
}
+ }
}
/*
@@ -6953,8 +7104,8 @@ static void replace_do_bs(int limit_col)
cc = replace_pop();
if (cc > 0) {
if (l_State & VREPLACE_FLAG) {
- /* Get the number of screen cells used by the character we are
- * going to delete. */
+ // Get the number of screen cells used by the character we are
+ // going to delete.
getvcol(curwin, &curwin->w_cursor, NULL, &start_vcol, NULL);
orig_vcols = chartabsize(get_cursor_pos_ptr(), start_vcol);
}
@@ -6971,7 +7122,7 @@ static void replace_do_bs(int limit_col)
replace_pop_ins();
if (l_State & VREPLACE_FLAG) {
- /* Get the number of screen cells used by the inserted characters */
+ // Get the number of screen cells used by the inserted characters
p = get_cursor_pos_ptr();
ins_len = (int)STRLEN(p) - orig_len;
vcol = start_vcol;
@@ -6981,8 +7132,8 @@ static void replace_do_bs(int limit_col)
}
vcol -= start_vcol;
- /* Delete spaces that were inserted after the cursor to keep the
- * text aligned. */
+ // Delete spaces that were inserted after the cursor to keep the
+ // text aligned.
curwin->w_cursor.col += ins_len;
while (vcol > orig_vcols && gchar_cursor() == ' ') {
del_char(false);
@@ -6991,7 +7142,7 @@ static void replace_do_bs(int limit_col)
curwin->w_cursor.col -= ins_len;
}
- /* mark the buffer as changed and prepare for displaying */
+ // mark the buffer as changed and prepare for displaying
changed_bytes(curwin->w_cursor.lnum, curwin->w_cursor.col);
} else if (cc == 0)
(void)del_char_after_col(limit_col);
@@ -7059,10 +7210,11 @@ bool in_cinkeys(int keytyped, int when, bool line_is_empty)
return false;
}
- if (*curbuf->b_p_inde != NUL)
- look = curbuf->b_p_indk; /* 'indentexpr' set: use 'indentkeys' */
- else
- look = curbuf->b_p_cink; /* 'indentexpr' empty: use 'cinkeys' */
+ if (*curbuf->b_p_inde != NUL) {
+ look = curbuf->b_p_indk; // 'indentexpr' set: use 'indentkeys'
+ } else {
+ look = curbuf->b_p_cink; // 'indentexpr' empty: use 'cinkeys'
+ }
while (*look) {
/*
* Find out if we want to try a match with this key, depending on
@@ -7261,10 +7413,12 @@ bool in_cinkeys(int keytyped, int when, bool line_is_empty)
*/
int hkmap(int c)
{
- if (p_hkmapp) { /* phonetic mapping, by Ilya Dogolazky */
- enum {hALEF=0, BET, GIMEL, DALET, HEI, VAV, ZAIN, HET, TET, IUD,
- KAFsofit, hKAF, LAMED, MEMsofit, MEM, NUNsofit, NUN, SAMEH, AIN,
- PEIsofit, PEI, ZADIsofit, ZADI, KOF, RESH, hSHIN, TAV};
+ if (p_hkmapp) { // phonetic mapping, by Ilya Dogolazky
+ enum {
+ hALEF = 0, BET, GIMEL, DALET, HEI, VAV, ZAIN, HET, TET, IUD,
+ KAFsofit, hKAF, LAMED, MEMsofit, MEM, NUNsofit, NUN, SAMEH, AIN,
+ PEIsofit, PEI, ZADIsofit, ZADI, KOF, RESH, hSHIN, TAV
+ };
static char_u map[26] =
{(char_u)hALEF /*a*/, (char_u)BET /*b*/, (char_u)hKAF /*c*/,
(char_u)DALET /*d*/, (char_u)-1 /*e*/, (char_u)PEIsofit /*f*/,
@@ -7276,28 +7430,27 @@ int hkmap(int c)
(char_u)VAV /*v*/, (char_u)hSHIN /*w*/, (char_u)-1 /*x*/,
(char_u)AIN /*y*/, (char_u)ZADI /*z*/};
- if (c == 'N' || c == 'M' || c == 'P' || c == 'C' || c == 'Z')
+ if (c == 'N' || c == 'M' || c == 'P' || c == 'C' || c == 'Z') {
return (int)(map[CharOrd(c)] - 1 + p_aleph);
- /* '-1'='sofit' */
- else if (c == 'x')
+ } else if (c == 'x') { // '-1'='sofit'
return 'X';
- else if (c == 'q')
- return '\''; /* {geresh}={'} */
- else if (c == 246)
- return ' '; /* \"o --> ' ' for a german keyboard */
- else if (c == 228)
- return ' '; /* \"a --> ' ' -- / -- */
- else if (c == 252)
- return ' '; /* \"u --> ' ' -- / -- */
- /* NOTE: islower() does not do the right thing for us on Linux so we
- * do this the same was as 5.7 and previous, so it works correctly on
- * all systems. Specifically, the e.g. Delete and Arrow keys are
- * munged and won't work if e.g. searching for Hebrew text.
- */
- else if (c >= 'a' && c <= 'z')
+ } else if (c == 'q') {
+ return '\''; // {geresh}={'}
+ } else if (c == 246) {
+ return ' '; // \"o --> ' ' for a german keyboard
+ } else if (c == 228) {
+ return ' '; // \"a --> ' ' -- / --
+ } else if (c == 252) {
+ return ' '; // \"u --> ' ' -- / --
+ } else if (c >= 'a' && c <= 'z') {
+ // NOTE: islower() does not do the right thing for us on Linux so we
+ // do this the same was as 5.7 and previous, so it works correctly on
+ // all systems. Specifically, the e.g. Delete and Arrow keys are
+ // munged and won't work if e.g. searching for Hebrew text.
return (int)(map[CharOrdLow(c)] + p_aleph);
- else
+ } else {
return c;
+ }
} else {
switch (c) {
case '`': return ';';
@@ -7306,7 +7459,7 @@ int hkmap(int c)
case 'q': return '/';
case 'w': return '\'';
- /* Hebrew letters - set offset from 'a' */
+ // Hebrew letters - set offset from 'a'
case ',': c = '{'; break;
case '.': c = 'v'; break;
case ';': c = 't'; break;
@@ -7336,10 +7489,10 @@ static void ins_reg(void)
*/
pc_status = PC_STATUS_UNSET;
if (redrawing() && !char_avail()) {
- /* may need to redraw when no more chars available now */
- ins_redraw(FALSE);
+ // may need to redraw when no more chars available now
+ ins_redraw(false);
- edit_putchar('"', TRUE);
+ edit_putchar('"', true);
add_to_showcmd_c(Ctrl_R);
}
@@ -7352,7 +7505,7 @@ static void ins_reg(void)
regname = plain_vgetc();
LANGMAP_ADJUST(regname, TRUE);
if (regname == Ctrl_R || regname == Ctrl_O || regname == Ctrl_P) {
- /* Get a third key for literal register insertion */
+ // Get a third key for literal register insertion
literally = regname;
add_to_showcmd_c(literally);
regname = plain_vgetc();
@@ -7360,9 +7513,9 @@ static void ins_reg(void)
}
--no_mapping;
- /* Don't call u_sync() while typing the expression or giving an error
- * message for it. Only call it explicitly. */
- ++no_u_sync;
+ // Don't call u_sync() while typing the expression or giving an error
+ // message for it. Only call it explicitly.
+ no_u_sync++;
if (regname == '=') {
pos_T curpos = curwin->w_cursor;
@@ -7381,7 +7534,7 @@ static void ins_reg(void)
need_redraw = true; // remove the '"'
} else {
if (literally == Ctrl_O || literally == Ctrl_P) {
- /* Append the command to the redo buffer. */
+ // Append the command to the redo buffer.
AppendCharToRedobuff(Ctrl_R);
AppendCharToRedobuff(literally);
AppendCharToRedobuff(regname);
@@ -7398,19 +7551,22 @@ static void ins_reg(void)
need_redraw = true;
}
}
- --no_u_sync;
- if (u_sync_once == 1)
- ins_need_undo = TRUE;
+ no_u_sync--;
+ if (u_sync_once == 1) {
+ ins_need_undo = true;
+ }
u_sync_once = 0;
clear_showcmd();
- /* If the inserted register is empty, we need to remove the '"' */
- if (need_redraw || stuff_empty())
+ // If the inserted register is empty, we need to remove the '"'
+ if (need_redraw || stuff_empty()) {
edit_unputchar();
+ }
- /* Disallow starting Visual mode here, would get a weird mode. */
- if (!vis_active && VIsual_active)
+ // Disallow starting Visual mode here, would get a weird mode.
+ if (!vis_active && VIsual_active) {
end_visual_mode();
+ }
}
/*
@@ -7420,7 +7576,7 @@ static void ins_ctrl_g(void)
{
int c;
- /* Right after CTRL-X the cursor will be after the ruler. */
+ // Right after CTRL-X the cursor will be after the ruler.
setcursor();
/*
@@ -7431,24 +7587,25 @@ static void ins_ctrl_g(void)
c = plain_vgetc();
--no_mapping;
switch (c) {
- /* CTRL-G k and CTRL-G <Up>: cursor up to Insstart.col */
+ // CTRL-G k and CTRL-G <Up>: cursor up to Insstart.col
case K_UP:
case Ctrl_K:
case 'k': ins_up(TRUE);
break;
- /* CTRL-G j and CTRL-G <Down>: cursor down to Insstart.col */
+ // CTRL-G j and CTRL-G <Down>: cursor down to Insstart.col
case K_DOWN:
case Ctrl_J:
case 'j': ins_down(TRUE);
break;
- /* CTRL-G u: start new undoable edit */
- case 'u': u_sync(TRUE);
- ins_need_undo = TRUE;
+ // CTRL-G u: start new undoable edit
+ case 'u':
+ u_sync(true);
+ ins_need_undo = true;
- /* Need to reset Insstart, esp. because a BS that joins
- * a line to the previous one must save for undo. */
+ // Need to reset Insstart, esp. because a BS that joins
+ // a line to the previous one must save for undo.
update_Insstart_orig = false;
Insstart = curwin->w_cursor;
break;
@@ -7460,7 +7617,7 @@ static void ins_ctrl_g(void)
dont_sync_undo = kNone;
break;
- /* Unknown CTRL-G command, reserved for future expansion. */
+ // Unknown CTRL-G command, reserved for future expansion.
default: vim_beep(BO_CTRLG);
}
}
@@ -7482,7 +7639,7 @@ static void ins_ctrl_hat(void)
}
set_iminsert_global();
showmode();
- /* Show/unshow value of 'keymap' in status lines. */
+ // Show/unshow value of 'keymap' in status lines.
status_redraw_curbuf();
}
@@ -7523,10 +7680,11 @@ static bool ins_esc(long *count, int cmdchar, bool nomove)
*count = 0;
}
- if (--*count > 0) { /* repeat what was typed */
- /* Vi repeats the insert without replacing characters. */
- if (vim_strchr(p_cpo, CPO_REPLCNT) != NULL)
+ if (--*count > 0) { // repeat what was typed
+ // Vi repeats the insert without replacing characters.
+ if (vim_strchr(p_cpo, CPO_REPLCNT) != NULL) {
State &= ~REPLACE_FLAG;
+ }
(void)start_redo_ins();
if (cmdchar == 'r' || cmdchar == 'v') {
@@ -7541,12 +7699,13 @@ static bool ins_esc(long *count, int cmdchar, bool nomove)
undisplay_dollar();
}
- /* When an autoindent was removed, curswant stays after the
- * indent */
- if (restart_edit == NUL && (colnr_T)temp == curwin->w_cursor.col)
- curwin->w_set_curswant = TRUE;
+ // When an autoindent was removed, curswant stays after the
+ // indent
+ if (restart_edit == NUL && (colnr_T)temp == curwin->w_cursor.col) {
+ curwin->w_set_curswant = true;
+ }
- /* Remember the last Insert position in the '^ mark. */
+ // Remember the last Insert position in the '^ mark.
if (!cmdmod.keepjumps) {
RESET_FMARK(&curbuf->b_last_insert, curwin->w_cursor, curbuf->b_fnum);
}
@@ -7567,23 +7726,23 @@ static bool ins_esc(long *count, int cmdchar, bool nomove)
) {
if (curwin->w_cursor.coladd > 0 || ve_flags == VE_ALL) {
oneleft();
- if (restart_edit != NUL)
- ++curwin->w_cursor.coladd;
+ if (restart_edit != NUL) {
+ curwin->w_cursor.coladd++;
+ }
} else {
- --curwin->w_cursor.col;
- /* Correct cursor for multi-byte character. */
- if (has_mbyte)
- mb_adjust_cursor();
+ curwin->w_cursor.col--;
+ // Correct cursor for multi-byte character.
+ mb_adjust_cursor();
}
}
State = NORMAL;
- /* need to position cursor again (e.g. when on a TAB ) */
+ // need to position cursor again (e.g. when on a TAB )
changed_cline_bef_curs();
setmouse();
- ui_cursor_shape(); /* may show different cursor shape */
+ ui_cursor_shape(); // may show different cursor shape
// When recording or for CTRL-O, need to display the new mode.
// Otherwise remove the mode message.
@@ -7679,7 +7838,7 @@ static void ins_insert(int replaceState)
}
AppendCharToRedobuff(K_INS);
showmode();
- ui_cursor_shape(); /* may show different cursor shape */
+ ui_cursor_shape(); // may show different cursor shape
}
/*
@@ -7693,10 +7852,11 @@ static void ins_ctrl_o(void)
restart_edit = 'R';
else
restart_edit = 'I';
- if (virtual_active())
- ins_at_eol = FALSE; /* cursor always keeps its column */
- else
+ if (virtual_active()) {
+ ins_at_eol = false; // cursor always keeps its column
+ } else {
ins_at_eol = (gchar_cursor() == NUL);
+ }
}
/*
@@ -7778,11 +7938,12 @@ static void ins_bs_one(colnr_T *vcolp)
dec_cursor();
getvcol(curwin, &curwin->w_cursor, vcolp, NULL, NULL);
if (State & REPLACE_FLAG) {
- /* Don't delete characters before the insert point when in
- * Replace mode */
+ // Don't delete characters before the insert point when in
+ // Replace mode
if (curwin->w_cursor.lnum != Insstart.lnum
- || curwin->w_cursor.col >= Insstart.col)
+ || curwin->w_cursor.col >= Insstart.col) {
replace_do_bs(-1);
+ }
} else {
(void)del_char(false);
}
@@ -7801,13 +7962,13 @@ static bool ins_bs(int c, int mode, int *inserted_space_p)
{
linenr_T lnum;
int cc;
- int temp = 0; /* init for GCC */
+ int temp = 0; // init for GCC
colnr_T save_col;
colnr_T mincol;
bool did_backspace = false;
int in_indent;
int oldState;
- int cpc[MAX_MCO]; /* composing characters */
+ int cpc[MAX_MCO]; // composing characters
// can't delete anything in an empty file
// can't backup past first character in buffer
@@ -7871,23 +8032,22 @@ static bool ins_bs(int c, int mode, int *inserted_space_p)
* cc >= 0: NL was replaced, put original characters back
*/
cc = -1;
- if (State & REPLACE_FLAG)
- cc = replace_pop(); /* returns -1 if NL was inserted */
- /*
- * In replace mode, in the line we started replacing, we only move the
- * cursor.
- */
+ if (State & REPLACE_FLAG) {
+ cc = replace_pop(); // returns -1 if NL was inserted
+ }
+ // In replace mode, in the line we started replacing, we only move the
+ // cursor.
if ((State & REPLACE_FLAG) && curwin->w_cursor.lnum <= lnum) {
dec_cursor();
} else {
if (!(State & VREPLACE_FLAG)
|| curwin->w_cursor.lnum > orig_line_count) {
- temp = gchar_cursor(); /* remember current char */
- --curwin->w_cursor.lnum;
+ temp = gchar_cursor(); // remember current char
+ curwin->w_cursor.lnum--;
- /* When "aw" is in 'formatoptions' we must delete the space at
- * the end of the line, otherwise the line will be broken
- * again when auto-formatting. */
+ // When "aw" is in 'formatoptions' we must delete the space at
+ // the end of the line, otherwise the line will be broken
+ // again when auto-formatting.
if (has_format_option(FO_AUTO)
&& has_format_option(FO_WHITE_PAR)) {
char_u *ptr = ml_get_buf(curbuf, curwin->w_cursor.lnum,
@@ -7928,20 +8088,19 @@ static bool ins_bs(int c, int mode, int *inserted_space_p)
curwin->w_cursor.col = save_col;
cc = replace_pop();
}
- /* restore the characters that NL replaced */
+ // restore the characters that NL replaced
replace_pop_ins();
State = oldState;
}
}
did_ai = false;
} else {
- /*
- * Delete character(s) before the cursor.
- */
- if (revins_on) /* put cursor on last inserted char */
+ // Delete character(s) before the cursor.
+ if (revins_on) { // put cursor on last inserted char
dec_cursor();
+ }
mincol = 0;
- /* keep indent */
+ // keep indent
if (mode == BACKSPACE_LINE
&& (curbuf->b_p_ai
|| cindent_on()
@@ -7976,9 +8135,9 @@ static bool ins_bs(int c, int mode, int *inserted_space_p)
ts = get_sw_value(curbuf);
else
ts = get_sts_value();
- /* Compute the virtual column where we want to be. Since
- * 'showbreak' may get in the way, need to get the last column of
- * the previous character. */
+ // Compute the virtual column where we want to be. Since
+ // 'showbreak' may get in the way, need to get the last column of
+ // the previous character.
getvcol(curwin, &curwin->w_cursor, &vcol, NULL, NULL);
start_vcol = vcol;
dec_cursor();
@@ -7986,22 +8145,23 @@ static bool ins_bs(int c, int mode, int *inserted_space_p)
inc_cursor();
want_vcol = (want_vcol / ts) * ts;
- /* delete characters until we are at or before want_vcol */
+ // delete characters until we are at or before want_vcol
while (vcol > want_vcol
- && (cc = *(get_cursor_pos_ptr() - 1), ascii_iswhite(cc)))
+ && (cc = *(get_cursor_pos_ptr() - 1), ascii_iswhite(cc))) {
ins_bs_one(&vcol);
+ }
- /* insert extra spaces until we are at want_vcol */
+ // insert extra spaces until we are at want_vcol
while (vcol < want_vcol) {
- /* Remember the first char we inserted */
+ // Remember the first char we inserted
if (curwin->w_cursor.lnum == Insstart_orig.lnum
&& curwin->w_cursor.col < Insstart_orig.col) {
Insstart_orig.col = curwin->w_cursor.col;
}
- if (State & VREPLACE_FLAG)
+ if (State & VREPLACE_FLAG) {
ins_char(' ');
- else {
+ } else {
ins_str((char_u *)" ");
if ((State & REPLACE_FLAG))
replace_push(NUL);
@@ -8009,18 +8169,16 @@ static bool ins_bs(int c, int mode, int *inserted_space_p)
getvcol(curwin, &curwin->w_cursor, &vcol, NULL, NULL);
}
- /* If we are now back where we started delete one character. Can
- * happen when using 'sts' and 'linebreak'. */
- if (vcol >= start_vcol)
+ // If we are now back where we started delete one character. Can
+ // happen when using 'sts' and 'linebreak'.
+ if (vcol >= start_vcol) {
ins_bs_one(&vcol);
-
- // Delete upto starting point, start of line or previous word.
+ }
} else {
- int cclass = 0, prev_cclass = 0;
+ // Delete upto starting point, start of line or previous word.
+ int prev_cclass = 0;
- if (has_mbyte) {
- cclass = mb_get_class(get_cursor_pos_ptr());
- }
+ int cclass = mb_get_class(get_cursor_pos_ptr());
do {
if (!revins_on) { // put cursor on char to be deleted
dec_cursor();
@@ -8088,21 +8246,22 @@ static bool ins_bs(int c, int mode, int *inserted_space_p)
// with.
AppendCharToRedobuff(c);
- /* If deleted before the insertion point, adjust it */
+ // If deleted before the insertion point, adjust it
if (curwin->w_cursor.lnum == Insstart_orig.lnum
&& curwin->w_cursor.col < Insstart_orig.col) {
Insstart_orig.col = curwin->w_cursor.col;
}
- /* vi behaviour: the cursor moves backward but the character that
- * was there remains visible
- * Vim behaviour: the cursor moves backward and the character that
- * was there is erased from the screen.
- * We can emulate the vi behaviour by pretending there is a dollar
- * displayed even when there isn't.
- * --pkv Sun Jan 19 01:56:40 EST 2003 */
- if (vim_strchr(p_cpo, CPO_BACKSPACE) != NULL && dollar_vcol == -1)
+ // vi behaviour: the cursor moves backward but the character that
+ // was there remains visible
+ // Vim behaviour: the cursor moves backward and the character that
+ // was there is erased from the screen.
+ // We can emulate the vi behaviour by pretending there is a dollar
+ // displayed even when there isn't.
+ // --pkv Sun Jan 19 01:56:40 EST 2003
+ if (vim_strchr(p_cpo, CPO_BACKSPACE) != NULL && dollar_vcol == -1) {
dollar_vcol = curwin->w_virtcol;
+ }
// When deleting a char the cursor line must never be in a closed fold.
// E.g., when 'foldmethod' is indent and deleting the first non-white
@@ -8127,10 +8286,14 @@ static void ins_mouse(int c)
win_T *new_curwin = curwin;
if (curwin != old_curwin && win_valid(old_curwin)) {
- /* Mouse took us to another window. We need to go back to the
- * previous one to stop insert there properly. */
+ // Mouse took us to another window. We need to go back to the
+ // previous one to stop insert there properly.
curwin = old_curwin;
curbuf = curwin->w_buffer;
+ if (bt_prompt(curbuf)) {
+ // Restart Insert mode when re-entering the prompt buffer.
+ curbuf->b_prompt_insert = 'A';
+ }
}
start_arrow(curwin == old_curwin ? &tpos : NULL);
if (curwin != new_curwin && win_valid(new_curwin)) {
@@ -8140,7 +8303,7 @@ static void ins_mouse(int c)
can_cindent = true;
}
- /* redraw status lines (in case another window became active) */
+ // redraw status lines (in case another window became active)
redraw_statuslines();
}
@@ -8163,7 +8326,7 @@ static void ins_mousescroll(int dir)
if (curwin == old_curwin)
undisplay_dollar();
- /* Don't scroll the window in which completion is being done. */
+ // Don't scroll the window in which completion is being done.
if (!pum_visible()
|| curwin != old_curwin
) {
@@ -8205,9 +8368,10 @@ static void ins_left(void)
if (!end_change) {
AppendCharToRedobuff(K_LEFT);
}
- /* If exit reversed string, position is fixed */
- if (revins_scol != -1 && (int)curwin->w_cursor.col >= revins_scol)
+ // If exit reversed string, position is fixed
+ if (revins_scol != -1 && (int)curwin->w_cursor.col >= revins_scol) {
revins_legal++;
+ }
revins_chars++;
} else if (vim_strchr(p_ww, '[') != NULL && curwin->w_cursor.lnum > 1) {
// if 'whichwrap' set for cursor in insert mode may go to previous line.
@@ -8300,14 +8464,13 @@ static void ins_right(void)
revins_legal++;
if (revins_chars)
revins_chars--;
- }
- /* if 'whichwrap' set for cursor in insert mode, may move the
- * cursor to the next line */
- else if (vim_strchr(p_ww, ']') != NULL
- && curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) {
+ } else if (vim_strchr(p_ww, ']') != NULL
+ && curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) {
+ // if 'whichwrap' set for cursor in insert mode, may move the
+ // cursor to the next line
start_arrow(&curwin->w_cursor);
- curwin->w_set_curswant = TRUE;
- ++curwin->w_cursor.lnum;
+ curwin->w_set_curswant = true;
+ curwin->w_cursor.lnum++;
curwin->w_cursor.col = 0;
} else {
vim_beep(BO_CRSR);
@@ -8336,9 +8499,8 @@ static void ins_s_right(void)
dont_sync_undo = kFalse;
}
-static void
-ins_up (
- int startcol /* when TRUE move to Insstart.col */
+static void ins_up(
+ bool startcol // when true move to Insstart.col
)
{
pos_T tpos;
@@ -8368,7 +8530,7 @@ static void ins_pageup(void)
undisplay_dollar();
if (mod_mask & MOD_MASK_CTRL) {
- /* <C-PageUp>: tab page back */
+ // <C-PageUp>: tab page back
if (first_tabpage->tp_next != NULL) {
start_arrow(&curwin->w_cursor);
goto_tabpage(-1);
@@ -8385,9 +8547,8 @@ static void ins_pageup(void)
}
}
-static void
-ins_down (
- int startcol /* when TRUE move to Insstart.col */
+static void ins_down(
+ bool startcol // when true move to Insstart.col
)
{
pos_T tpos;
@@ -8417,7 +8578,7 @@ static void ins_pagedown(void)
undisplay_dollar();
if (mod_mask & MOD_MASK_CTRL) {
- /* <C-PageDown>: tab page forward */
+ // <C-PageDown>: tab page forward
if (first_tabpage->tp_next != NULL) {
start_arrow(&curwin->w_cursor);
goto_tabpage(0);
@@ -8479,6 +8640,7 @@ static bool ins_tab(void)
} else { // otherwise use "tabstop"
temp = (int)curbuf->b_p_ts;
}
+
temp -= get_nolist_virtcol() % temp;
/*
@@ -8488,12 +8650,13 @@ static bool ins_tab(void)
*/
ins_char(' ');
while (--temp > 0) {
- if (State & VREPLACE_FLAG)
+ if (State & VREPLACE_FLAG) {
ins_char(' ');
- else {
+ } else {
ins_str((char_u *)" ");
- if (State & REPLACE_FLAG) /* no char replaced */
+ if (State & REPLACE_FLAG) { // no char replaced
replace_push(NUL);
+ }
}
}
@@ -8502,7 +8665,7 @@ static bool ins_tab(void)
*/
if (!curbuf->b_p_et && (get_sts_value() || (p_sta && ind))) {
char_u *ptr;
- char_u *saved_line = NULL; /* init for GCC */
+ char_u *saved_line = NULL; // init for GCC
pos_T pos;
pos_T fpos;
pos_T *cursor;
@@ -8524,18 +8687,19 @@ static bool ins_tab(void)
cursor = &curwin->w_cursor;
}
- /* When 'L' is not in 'cpoptions' a tab always takes up 'ts' spaces. */
- if (vim_strchr(p_cpo, CPO_LISTWM) == NULL)
- curwin->w_p_list = FALSE;
+ // When 'L' is not in 'cpoptions' a tab always takes up 'ts' spaces.
+ if (vim_strchr(p_cpo, CPO_LISTWM) == NULL) {
+ curwin->w_p_list = false;
+ }
- /* Find first white before the cursor */
+ // Find first white before the cursor
fpos = curwin->w_cursor;
while (fpos.col > 0 && ascii_iswhite(ptr[-1])) {
--fpos.col;
--ptr;
}
- /* In Replace mode, don't change characters before the insert point. */
+ // In Replace mode, don't change characters before the insert point.
if ((State & REPLACE_FLAG)
&& fpos.lnum == Insstart.lnum
&& fpos.col < Insstart.col) {
@@ -8543,12 +8707,12 @@ static bool ins_tab(void)
fpos.col = Insstart.col;
}
- /* compute virtual column numbers of first white and cursor */
+ // compute virtual column numbers of first white and cursor
getvcol(curwin, &fpos, &vcol, NULL, NULL);
getvcol(curwin, cursor, &want_vcol, NULL, NULL);
- /* Use as many TABs as possible. Beware of 'breakindent', 'showbreak'
- and 'linebreak' adding extra virtual columns. */
+ // Use as many TABs as possible. Beware of 'breakindent', 'showbreak'
+ // and 'linebreak' adding extra virtual columns.
while (ascii_iswhite(*ptr)) {
i = lbr_chartabsize(NULL, (char_u *)"\t", vcol);
if (vcol + i > want_vcol)
@@ -8556,10 +8720,11 @@ static bool ins_tab(void)
if (*ptr != TAB) {
*ptr = TAB;
if (change_col < 0) {
- change_col = fpos.col; /* Column of first change */
- /* May have to adjust Insstart */
- if (fpos.lnum == Insstart.lnum && fpos.col < Insstart.col)
+ change_col = fpos.col; // Column of first change
+ // May have to adjust Insstart
+ if (fpos.lnum == Insstart.lnum && fpos.col < Insstart.col) {
Insstart.col = fpos.col;
+ }
}
}
++fpos.col;
@@ -8571,29 +8736,30 @@ static bool ins_tab(void)
int repl_off = 0;
char_u *line = ptr;
- /* Skip over the spaces we need. */
+ // Skip over the spaces we need.
while (vcol < want_vcol && *ptr == ' ') {
vcol += lbr_chartabsize(line, ptr, vcol);
++ptr;
++repl_off;
}
if (vcol > want_vcol) {
- /* Must have a char with 'showbreak' just before it. */
- --ptr;
- --repl_off;
+ // Must have a char with 'showbreak' just before it.
+ ptr--;
+ repl_off--;
}
fpos.col += repl_off;
- /* Delete following spaces. */
+ // Delete following spaces.
i = cursor->col - fpos.col;
if (i > 0) {
STRMOVE(ptr, ptr + i);
- /* correct replace stack. */
+ // correct replace stack.
if ((State & REPLACE_FLAG)
- && !(State & VREPLACE_FLAG)
- )
- for (temp = i; --temp >= 0; )
+ && !(State & VREPLACE_FLAG)) {
+ for (temp = i; --temp >= 0; ) {
replace_join(repl_off);
+ }
+ }
}
cursor->col -= i;
@@ -8603,11 +8769,11 @@ static bool ins_tab(void)
* spacing.
*/
if (State & VREPLACE_FLAG) {
- /* Backspace from real cursor to change_col */
+ // Backspace from real cursor to change_col
backspace_until_column(change_col);
- /* Insert each char in saved_line from changed_col to
- * ptr-cursor */
+ // Insert each char in saved_line from changed_col to
+ // ptr-cursor
ins_bytes_len(saved_line + change_col,
cursor->col - change_col);
}
@@ -8651,10 +8817,11 @@ static bool ins_eol(int c)
* in open_line().
*/
- /* Put cursor on NUL if on the last char and coladd is 1 (happens after
- * CTRL-O). */
- if (virtual_active() && curwin->w_cursor.coladd > 0)
+ // Put cursor on NUL if on the last char and coladd is 1 (happens after
+ // CTRL-O).
+ if (virtual_active() && curwin->w_cursor.coladd > 0) {
coladvance(getviscol());
+ }
// NL in reverse insert will always start in the end of current line.
if (revins_on) {
@@ -8682,15 +8849,15 @@ static int ins_digraph(void)
{
int c;
int cc;
- int did_putchar = FALSE;
+ bool did_putchar = false;
pc_status = PC_STATUS_UNSET;
if (redrawing() && !char_avail()) {
- /* may need to redraw when no more chars available now */
- ins_redraw(FALSE);
+ // may need to redraw when no more chars available now
+ ins_redraw(false);
- edit_putchar('?', TRUE);
- did_putchar = TRUE;
+ edit_putchar('?', true);
+ did_putchar = true;
add_to_showcmd_c(Ctrl_K);
}
@@ -8706,21 +8873,21 @@ static int ins_digraph(void)
edit_unputchar();
}
- if (IS_SPECIAL(c) || mod_mask) { /* special key */
+ if (IS_SPECIAL(c) || mod_mask) { // special key
clear_showcmd();
insert_special(c, TRUE, FALSE);
return NUL;
}
if (c != ESC) {
- did_putchar = FALSE;
+ did_putchar = false;
if (redrawing() && !char_avail()) {
- /* may need to redraw when no more chars available now */
- ins_redraw(FALSE);
+ // may need to redraw when no more chars available now
+ ins_redraw(false);
if (char2cells(c) == 1) {
- ins_redraw(FALSE);
- edit_putchar(c, TRUE);
- did_putchar = TRUE;
+ ins_redraw(false);
+ edit_putchar(c, true);
+ did_putchar = true;
}
add_to_showcmd_c(c);
}
@@ -8759,7 +8926,7 @@ int ins_copychar(linenr_T lnum)
return NUL;
}
- /* try to advance to the cursor column */
+ // try to advance to the cursor column
temp = 0;
line = ptr = ml_get(lnum);
prev_ptr = ptr;
@@ -8809,8 +8976,8 @@ static int ins_ctrl_ey(int tc)
curbuf->b_p_tw = tw_save;
revins_chars++;
revins_legal++;
- c = Ctrl_V; /* pretend CTRL-V is last character */
- auto_format(FALSE, TRUE);
+ c = Ctrl_V; // pretend CTRL-V is last character
+ auto_format(false, true);
}
}
return c;
@@ -8845,9 +9012,10 @@ static void ins_try_si(int c)
*/
ptr = ml_get(pos->lnum);
i = pos->col;
- if (i > 0) /* skip blanks before '{' */
- while (--i > 0 && ascii_iswhite(ptr[i]))
- ;
+ if (i > 0) { // skip blanks before '{'
+ while (--i > 0 && ascii_iswhite(ptr[i])) {
+ }
+ }
curwin->w_cursor.lnum = pos->lnum;
curwin->w_cursor.col = i;
if (ptr[i] == ')' && (pos = findmatch(NULL, '(')) != NULL)
@@ -8870,9 +9038,10 @@ static void ins_try_si(int c)
while (curwin->w_cursor.lnum > 1) {
ptr = skipwhite(ml_get(--(curwin->w_cursor.lnum)));
- /* ignore empty lines and lines starting with '#'. */
- if (*ptr != '#' && *ptr != NUL)
+ // ignore empty lines and lines starting with '#'.
+ if (*ptr != '#' && *ptr != NUL) {
break;
+ }
}
if (get_indent() >= i)
temp = FALSE;
@@ -8887,14 +9056,15 @@ static void ins_try_si(int c)
* set indent of '#' always to 0
*/
if (curwin->w_cursor.col > 0 && can_si && c == '#') {
- /* remember current indent for next line */
+ // remember current indent for next line
old_indent = get_indent();
(void)set_indent(0, SIN_CHANGED);
}
- /* Adjust ai_col, the char at this position can be deleted. */
- if (ai_col > curwin->w_cursor.col)
+ // Adjust ai_col, the char at this position can be deleted.
+ if (ai_col > curwin->w_cursor.col) {
ai_col = curwin->w_cursor.col;
+ }
}
/*
@@ -8959,7 +9129,8 @@ static int ins_apply_autocmds(event_T event)
// If u_savesub() was called then we are not prepared to start
// a new line. Call u_save() with no contents to fix that.
- if (tick != buf_get_changedtick(curbuf)) {
+ // Except when leaving Insert mode.
+ if (event != EVENT_INSERTLEAVE && tick != buf_get_changedtick(curbuf)) {
u_save(curwin->w_cursor.lnum, (linenr_T)(curwin->w_cursor.lnum + 1));
}
diff --git a/src/nvim/edit.h b/src/nvim/edit.h
index 92dab37a70..09f401ee82 100644
--- a/src/nvim/edit.h
+++ b/src/nvim/edit.h
@@ -10,8 +10,7 @@
#define CPT_MENU 1 // "menu"
#define CPT_KIND 2 // "kind"
#define CPT_INFO 3 // "info"
-#define CPT_USER_DATA 4 // "user data"
-#define CPT_COUNT 5 // Number of entries
+#define CPT_COUNT 4 // Number of entries
// values for cp_flags
typedef enum {
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index 7766c793e4..0cad5fd6c1 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -5,183 +5,65 @@
* eval.c: Expression evaluation.
*/
-#include <assert.h>
-#include <float.h>
-#include <inttypes.h>
-#include <stdarg.h>
-#include <string.h>
-#include <stdlib.h>
-#include <stdbool.h>
#include <math.h>
-#include <limits.h>
-#include <msgpack.h>
-#include "nvim/assert.h"
-#include "nvim/vim.h"
-#include "nvim/ascii.h"
#ifdef HAVE_LOCALE_H
# include <locale.h>
#endif
-#include "nvim/eval.h"
+
+#include "nvim/ascii.h"
#include "nvim/buffer.h"
#include "nvim/change.h"
#include "nvim/channel.h"
#include "nvim/charset.h"
-#include "nvim/context.h"
#include "nvim/cursor.h"
-#include "nvim/diff.h"
#include "nvim/edit.h"
-#include "nvim/ex_cmds.h"
+#include "nvim/eval/userfunc.h"
+#include "nvim/eval.h"
+#include "nvim/eval/encode.h"
+#include "nvim/eval/executor.h"
+#include "nvim/eval/gc.h"
+#include "nvim/eval/typval.h"
#include "nvim/ex_cmds2.h"
#include "nvim/ex_docmd.h"
-#include "nvim/ex_eval.h"
#include "nvim/ex_getln.h"
+#include "nvim/ex_session.h"
#include "nvim/fileio.h"
-#include "nvim/os/fileio.h"
-#include "nvim/func_attr.h"
-#include "nvim/fold.h"
#include "nvim/getchar.h"
-#include "nvim/hashtab.h"
-#include "nvim/iconv.h"
-#include "nvim/if_cscope.h"
-#include "nvim/indent_c.h"
-#include "nvim/indent.h"
+#include "nvim/lua/executor.h"
#include "nvim/mark.h"
-#include "nvim/math.h"
-#include "nvim/mbyte.h"
#include "nvim/memline.h"
-#include "nvim/memory.h"
-#include "nvim/menu.h"
-#include "nvim/message.h"
#include "nvim/misc1.h"
-#include "nvim/keymap.h"
-#include "nvim/map.h"
-#include "nvim/file_search.h"
-#include "nvim/garray.h"
#include "nvim/move.h"
-#include "nvim/normal.h"
#include "nvim/ops.h"
#include "nvim/option.h"
-#include "nvim/os_unix.h"
+#include "nvim/os/input.h"
+#include "nvim/os/os.h"
+#include "nvim/os/shell.h"
#include "nvim/path.h"
-#include "nvim/popupmnu.h"
-#include "nvim/profile.h"
#include "nvim/quickfix.h"
#include "nvim/regexp.h"
#include "nvim/screen.h"
#include "nvim/search.h"
-#include "nvim/sha256.h"
#include "nvim/sign.h"
-#include "nvim/spell.h"
-#include "nvim/state.h"
-#include "nvim/strings.h"
#include "nvim/syntax.h"
-#include "nvim/tag.h"
#include "nvim/ui.h"
-#include "nvim/main.h"
-#include "nvim/mouse.h"
-#include "nvim/terminal.h"
#include "nvim/undo.h"
#include "nvim/version.h"
#include "nvim/window.h"
-#include "nvim/eval/encode.h"
-#include "nvim/eval/decode.h"
-#include "nvim/os/os.h"
-#include "nvim/event/libuv_process.h"
-#include "nvim/os/pty_process.h"
-#include "nvim/event/rstream.h"
-#include "nvim/event/wstream.h"
-#include "nvim/event/time.h"
-#include "nvim/os/time.h"
-#include "nvim/msgpack_rpc/channel.h"
-#include "nvim/msgpack_rpc/server.h"
-#include "nvim/msgpack_rpc/helpers.h"
-#include "nvim/api/private/helpers.h"
-#include "nvim/api/vim.h"
-#include "nvim/os/dl.h"
-#include "nvim/os/input.h"
-#include "nvim/event/loop.h"
-#include "nvim/lib/kvec.h"
-#include "nvim/lib/khash.h"
-#include "nvim/lib/queue.h"
-#include "nvim/lua/executor.h"
-#include "nvim/eval/typval.h"
-#include "nvim/eval/executor.h"
-#include "nvim/eval/gc.h"
-#include "nvim/macros.h"
-// TODO(ZyX-I): Remove DICT_MAXNEST, make users be non-recursive instead
-
-#define DICT_MAXNEST 100 /* maximum nesting of lists and dicts */
-// Character used as separator in autoload function/variable names.
-#define AUTOLOAD_CHAR '#'
+// TODO(ZyX-I): Remove DICT_MAXNEST, make users be non-recursive instead
-/*
- * Structure returned by get_lval() and used by set_var_lval().
- * For a plain name:
- * "name" points to the variable name.
- * "exp_name" is NULL.
- * "tv" is NULL
- * For a magic braces name:
- * "name" points to the expanded variable name.
- * "exp_name" is non-NULL, to be freed later.
- * "tv" is NULL
- * For an index in a list:
- * "name" points to the (expanded) variable name.
- * "exp_name" NULL or non-NULL, to be freed later.
- * "tv" points to the (first) list item value
- * "li" points to the (first) list item
- * "range", "n1", "n2" and "empty2" indicate what items are used.
- * For an existing Dict item:
- * "name" points to the (expanded) variable name.
- * "exp_name" NULL or non-NULL, to be freed later.
- * "tv" points to the dict item value
- * "newkey" is NULL
- * For a non-existing Dict item:
- * "name" points to the (expanded) variable name.
- * "exp_name" NULL or non-NULL, to be freed later.
- * "tv" points to the Dictionary typval_T
- * "newkey" is the key for the new item.
- */
-typedef struct lval_S {
- const char *ll_name; ///< Start of variable name (can be NULL).
- size_t ll_name_len; ///< Length of the .ll_name.
- char *ll_exp_name; ///< NULL or expanded name in allocated memory.
- typval_T *ll_tv; ///< Typeval of item being used. If "newkey"
- ///< isn't NULL it's the Dict to which to add the item.
- listitem_T *ll_li; ///< The list item or NULL.
- list_T *ll_list; ///< The list or NULL.
- int ll_range; ///< TRUE when a [i:j] range was used.
- long ll_n1; ///< First index for list.
- long ll_n2; ///< Second index for list range.
- int ll_empty2; ///< Second index is empty: [i:].
- dict_T *ll_dict; ///< The Dictionary or NULL.
- dictitem_T *ll_di; ///< The dictitem or NULL.
- char_u *ll_newkey; ///< New key for Dict in allocated memory or NULL.
-} lval_T;
+#define DICT_MAXNEST 100 // maximum nesting of lists and dicts
static char *e_letunexp = N_("E18: Unexpected characters in :let");
static char *e_missbrac = N_("E111: Missing ']'");
-static char *e_listarg = N_("E686: Argument of %s must be a List");
-static char *e_listdictarg = N_(
- "E712: Argument of %s must be a List or Dictionary");
-static char *e_listreq = N_("E714: List required");
-static char *e_dictreq = N_("E715: Dictionary required");
-static char *e_stringreq = N_("E928: String required");
-static char *e_toomanyarg = N_("E118: Too many arguments for function: %s");
-static char *e_dictkey = N_("E716: Key not present in Dictionary: %s");
-static char *e_funcexts = N_(
- "E122: Function %s already exists, add ! to replace it");
-static char *e_funcdict = N_("E717: Dictionary entry already exists");
-static char *e_funcref = N_("E718: Funcref required");
static char *e_dictrange = N_("E719: Cannot use [:] with a Dictionary");
-static char *e_nofunc = N_("E130: Unknown function: %s");
static char *e_illvar = N_("E461: Illegal variable name: %s");
static char *e_cannot_mod = N_("E995: Cannot modify existing variable");
-static const char *e_readonlyvar = N_(
- "E46: Cannot change read-only variable \"%.*s\"");
+static char *e_invalwindow = N_("E957: Invalid window number");
// TODO(ZyX-I): move to eval/executor
static char *e_letwrong = N_("E734: Wrong variable type for %s=");
@@ -200,10 +82,8 @@ static ScopeDictDictItem globvars_var;
*/
static hashtab_T compat_hashtab;
-hashtab_T func_hashtab;
-
-// Used for checking if local variables or arguments used in a lambda.
-static int *eval_lavars_used = NULL;
+/// Used for checking if local variables or arguments used in a lambda.
+bool *eval_lavars_used = NULL;
/*
* Array to hold the hashtab with variables local to each sourced script.
@@ -218,98 +98,25 @@ static garray_T ga_scripts = {0, 0, sizeof(scriptvar_T *), 4, NULL};
#define SCRIPT_SV(id) (((scriptvar_T **)ga_scripts.ga_data)[(id) - 1])
#define SCRIPT_VARS(id) (SCRIPT_SV(id)->sv_dict.dv_hashtab)
-static int echo_attr = 0; /* attributes used for ":echo" */
-
-/// Describe data to return from find_some_match()
-typedef enum {
- kSomeMatch, ///< Data for match().
- kSomeMatchEnd, ///< Data for matchend().
- kSomeMatchList, ///< Data for matchlist().
- kSomeMatchStr, ///< Data for matchstr().
- kSomeMatchStrPos, ///< Data for matchstrpos().
-} SomeMatchType;
-
-/// trans_function_name() flags
-typedef enum {
- TFN_INT = 1, ///< May use internal function name
- TFN_QUIET = 2, ///< Do not emit error messages.
- TFN_NO_AUTOLOAD = 4, ///< Do not use script autoloading.
- TFN_NO_DEREF = 8, ///< Do not dereference a Funcref.
- TFN_READ_ONLY = 16, ///< Will not change the variable.
-} TransFunctionNameFlags;
-
-/// get_lval() flags
-typedef enum {
- GLV_QUIET = TFN_QUIET, ///< Do not emit error messages.
- GLV_NO_AUTOLOAD = TFN_NO_AUTOLOAD, ///< Do not use script autoloading.
- GLV_READ_ONLY = TFN_READ_ONLY, ///< Indicates that caller will not change
- ///< the value (prevents error message).
-} GetLvalFlags;
-
-// flags used in uf_flags
-#define FC_ABORT 0x01 // abort function on error
-#define FC_RANGE 0x02 // function accepts range
-#define FC_DICT 0x04 // Dict function, uses "self"
-#define FC_CLOSURE 0x08 // closure, uses outer scope variables
-#define FC_DELETED 0x10 // :delfunction used while uf_refcount > 0
-#define FC_REMOVED 0x20 // function redefined while uf_refcount > 0
-#define FC_SANDBOX 0x40 // function defined in the sandbox
+static int echo_attr = 0; // attributes used for ":echo"
// The names of packages that once were loaded are remembered.
static garray_T ga_loaded = { 0, 0, sizeof(char_u *), 4, NULL };
-#define FUNCARG(fp, j) ((char_u **)(fp->uf_args.ga_data))[j]
-#define FUNCLINE(fp, j) ((char_u **)(fp->uf_lines.ga_data))[j]
-
-/// Short variable name length
-#define VAR_SHORT_LEN 20
-/// Number of fixed variables used for arguments
-#define FIXVAR_CNT 12
-
-struct funccall_S {
- ufunc_T *func; ///< Function being called.
- int linenr; ///< Next line to be executed.
- int returned; ///< ":return" used.
- /// Fixed variables for arguments.
- TV_DICTITEM_STRUCT(VAR_SHORT_LEN + 1) fixvar[FIXVAR_CNT];
- dict_T l_vars; ///< l: local function variables.
- ScopeDictDictItem l_vars_var; ///< Variable for l: scope.
- dict_T l_avars; ///< a: argument variables.
- ScopeDictDictItem l_avars_var; ///< Variable for a: scope.
- list_T l_varlist; ///< List for a:000.
- listitem_T l_listitems[MAX_FUNC_ARGS]; ///< List items for a:000.
- typval_T *rettv; ///< Return value.
- linenr_T breakpoint; ///< Next line with breakpoint or zero.
- int dbg_tick; ///< Debug_tick when breakpoint was set.
- int level; ///< Top nesting level of executed function.
- proftime_T prof_child; ///< Time spent in a child.
- funccall_T *caller; ///< Calling function or NULL.
- int fc_refcount; ///< Number of user functions that reference this funccall.
- int fc_copyID; ///< CopyID used for garbage collection.
- garray_T fc_funcs; ///< List of ufunc_T* which keep a reference to "func".
-};
-
-///< Structure used by trans_function_name()
-typedef struct {
- dict_T *fd_dict; ///< Dictionary used.
- char_u *fd_newkey; ///< New key in "dict" in allocated memory.
- dictitem_T *fd_di; ///< Dictionary item used.
-} funcdict_T;
-
/*
* Info used by a ":for" loop.
*/
typedef struct {
- int fi_semicolon; /* TRUE if ending in '; var]' */
- int fi_varcount; /* nr of variables in the list */
- listwatch_T fi_lw; /* keep an eye on the item used. */
- list_T *fi_list; /* list being used */
+ int fi_semicolon; // TRUE if ending in '; var]'
+ int fi_varcount; // nr of variables in the list
+ listwatch_T fi_lw; // keep an eye on the item used.
+ list_T *fi_list; // list being used
} forinfo_T;
-/* values for vv_flags: */
-#define VV_COMPAT 1 /* compatible, also used without "v:" */
-#define VV_RO 2 /* read-only */
-#define VV_RO_SBX 4 /* read-only in the sandbox */
+// values for vv_flags:
+#define VV_COMPAT 1 // compatible, also used without "v:"
+#define VV_RO 2 // read-only
+#define VV_RO_SBX 4 // read-only in the sandbox
#define VV(idx, name, type, flags) \
[idx] = { \
@@ -406,8 +213,8 @@ static struct vimvar {
VV(VV_ERRORS, "errors", VAR_LIST, 0),
VV(VV_MSGPACK_TYPES, "msgpack_types", VAR_DICT, VV_RO),
VV(VV_EVENT, "event", VAR_DICT, VV_RO),
- VV(VV_FALSE, "false", VAR_SPECIAL, VV_RO),
- VV(VV_TRUE, "true", VAR_SPECIAL, VV_RO),
+ VV(VV_FALSE, "false", VAR_BOOL, VV_RO),
+ VV(VV_TRUE, "true", VAR_BOOL, VV_RO),
VV(VV_NULL, "null", VAR_SPECIAL, VV_RO),
VV(VV__NULL_LIST, "_null_list", VAR_LIST, VV_RO),
VV(VV__NULL_DICT, "_null_dict", VAR_DICT, VV_RO),
@@ -422,90 +229,38 @@ static struct vimvar {
VV(VV_TYPE_BOOL, "t_bool", VAR_NUMBER, VV_RO),
VV(VV_ECHOSPACE, "echospace", VAR_NUMBER, VV_RO),
VV(VV_EXITING, "exiting", VAR_NUMBER, VV_RO),
+ VV(VV_LUA, "lua", VAR_PARTIAL, VV_RO),
+ VV(VV_ARGV, "argv", VAR_LIST, VV_RO),
};
#undef VV
-/* shorthand */
+// shorthand
#define vv_type vv_di.di_tv.v_type
#define vv_nr vv_di.di_tv.vval.v_number
+#define vv_bool vv_di.di_tv.vval.v_bool
#define vv_special vv_di.di_tv.vval.v_special
#define vv_float vv_di.di_tv.vval.v_float
#define vv_str vv_di.di_tv.vval.v_string
#define vv_list vv_di.di_tv.vval.v_list
#define vv_dict vv_di.di_tv.vval.v_dict
+#define vv_partial vv_di.di_tv.vval.v_partial
#define vv_tv vv_di.di_tv
/// Variable used for v:
static ScopeDictDictItem vimvars_var;
+static partial_T *vvlua_partial;
+
/// v: hashtab
#define vimvarht vimvardict.dv_hashtab
-typedef struct {
- TimeWatcher tw;
- int timer_id;
- int repeat_count;
- int refcount;
- int emsg_count; ///< Errors in a repeating timer.
- long timeout;
- bool stopped;
- bool paused;
- Callback callback;
-} timer_T;
-
-typedef void (*FunPtr)(void);
-
-/// Prototype of C function that implements VimL function
-typedef void (*VimLFunc)(typval_T *args, typval_T *rvar, FunPtr data);
-
-/// Structure holding VimL function definition
-typedef struct fst {
- char *name; ///< Name of the function.
- uint8_t min_argc; ///< Minimal number of arguments.
- uint8_t max_argc; ///< Maximal number of arguments.
- VimLFunc func; ///< Function implementation.
- FunPtr data; ///< Userdata for function implementation.
-} VimLFuncDef;
-
-KHASH_MAP_INIT_STR(functions, VimLFuncDef)
-
-/// Type of assert_* check being performed
-typedef enum
-{
- ASSERT_EQUAL,
- ASSERT_NOTEQUAL,
- ASSERT_MATCH,
- ASSERT_NOTMATCH,
- ASSERT_INRANGE,
- ASSERT_OTHER,
-} assert_type_T;
-
-/// Type for dict_list function
-typedef enum {
- kDictListKeys, ///< List dictionary keys.
- kDictListValues, ///< List dictionary values.
- kDictListItems, ///< List dictionary contents: [keys, values].
-} DictListType;
-
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "eval.c.generated.h"
#endif
-#define FNE_INCL_BR 1 /* find_name_end(): include [] in name */
-#define FNE_CHECK_START 2 /* find_name_end(): check name starts with
- valid character */
-
static uint64_t last_timer_id = 1;
static PMap(uint64_t) *timers = NULL;
-/// Dummy va_list for passing to vim_snprintf
-///
-/// Used because:
-/// - passing a NULL pointer doesn't work when va_list isn't a pointer
-/// - locally in the function results in a "used before set" warning
-/// - using va_start() to initialize it gives "function with fixed args" error
-static va_list dummy_ap;
-
static const char *const msgpack_type_names[] = {
[kMPNil] = "nil",
[kMPBoolean] = "boolean",
@@ -572,7 +327,7 @@ void eval_init(void)
init_var_dict(&vimvardict, &vimvars_var, VAR_SCOPE);
vimvardict.dv_lock = VAR_FIXED;
hash_init(&compat_hashtab);
- hash_init(&func_hashtab);
+ func_init();
for (size_t i = 0; i < ARRAY_SIZE(vimvars); i++) {
p = &vimvars[i];
@@ -585,12 +340,14 @@ void eval_init(void)
else
p->vv_di.di_flags = DI_FLAGS_FIX;
- /* add to v: scope dict, unless the value is not always available */
- if (p->vv_type != VAR_UNKNOWN)
+ // add to v: scope dict, unless the value is not always available
+ if (p->vv_type != VAR_UNKNOWN) {
hash_add(&vimvarht, p->vv_di.di_key);
- if (p->vv_flags & VV_COMPAT)
- /* add to compat scope dict */
+ }
+ if (p->vv_flags & VV_COMPAT) {
+ // add to compat scope dict
hash_add(&compat_hashtab, p->vv_di.di_key);
+ }
}
vimvars[VV_VERSION].vv_nr = VIM_VERSION_100;
@@ -632,13 +389,20 @@ void eval_init(void)
set_vim_var_nr(VV_TYPE_FLOAT, VAR_TYPE_FLOAT);
set_vim_var_nr(VV_TYPE_BOOL, VAR_TYPE_BOOL);
- set_vim_var_special(VV_FALSE, kSpecialVarFalse);
- set_vim_var_special(VV_TRUE, kSpecialVarTrue);
+ set_vim_var_bool(VV_FALSE, kBoolVarFalse);
+ set_vim_var_bool(VV_TRUE, kBoolVarTrue);
set_vim_var_special(VV_NULL, kSpecialVarNull);
set_vim_var_special(VV_EXITING, kSpecialVarNull);
set_vim_var_nr(VV_ECHOSPACE, sc_col - 1);
+ vimvars[VV_LUA].vv_type = VAR_PARTIAL;
+ vvlua_partial = xcalloc(1, sizeof(partial_T));
+ vimvars[VV_LUA].vv_partial = vvlua_partial;
+ // this value shouldn't be printed, but if it is, do not crash
+ vvlua_partial->pt_name = xmallocz(0);
+ vvlua_partial->pt_refcount++;
+
set_reg_var(0); // default for v:register is not 0 but '"'
}
@@ -657,16 +421,16 @@ void eval_clear(void)
}
}
hash_clear(&vimvarht);
- hash_init(&vimvarht); /* garbage_collect() will access it */
+ hash_init(&vimvarht); // garbage_collect() will access it
hash_clear(&compat_hashtab);
free_scriptnames();
free_locales();
- /* global variables */
+ // global variables
vars_clear(&globvarht);
- /* autoloaded script names */
+ // autoloaded script names
ga_clear_strings(&ga_loaded);
/* Script-local variables. First clear all the variables and in a second
@@ -681,60 +445,13 @@ void eval_clear(void)
// unreferenced lists and dicts
(void)garbage_collect(false);
- // functions
+ // functions not garbage collected
free_all_functions();
}
#endif
/*
- * Return the name of the executed function.
- */
-char_u *func_name(void *cookie)
-{
- return ((funccall_T *)cookie)->func->uf_name;
-}
-
-/*
- * Return the address holding the next breakpoint line for a funccall cookie.
- */
-linenr_T *func_breakpoint(void *cookie)
-{
- return &((funccall_T *)cookie)->breakpoint;
-}
-
-/*
- * Return the address holding the debug tick for a funccall cookie.
- */
-int *func_dbg_tick(void *cookie)
-{
- return &((funccall_T *)cookie)->dbg_tick;
-}
-
-/*
- * Return the nesting level for a funccall cookie.
- */
-int func_level(void *cookie)
-{
- return ((funccall_T *)cookie)->level;
-}
-
-/* pointer to funccal for currently active function */
-funccall_T *current_funccal = NULL;
-
-// Pointer to list of previously used funccal, still around because some
-// item in it is still being used.
-funccall_T *previous_funccal = NULL;
-
-/*
- * Return TRUE when a function was ended by a ":return" command.
- */
-int current_func_returned(void)
-{
- return current_funccal->returned;
-}
-
-/*
* Set an internal variable to a string value. Creates the variable if it does
* not already exist.
*/
@@ -760,25 +477,25 @@ static char_u *redir_varname = NULL;
int
var_redir_start(
char_u *name,
- int append /* append to an existing variable */
+ int append // append to an existing variable
)
{
int save_emsg;
int err;
typval_T tv;
- /* Catch a bad name early. */
+ // Catch a bad name early.
if (!eval_isnamec1(*name)) {
EMSG(_(e_invarg));
return FAIL;
}
- /* Make a copy of the name, it is used in redir_lval until redir ends. */
+ // Make a copy of the name, it is used in redir_lval until redir ends.
redir_varname = vim_strsave(name);
redir_lval = xcalloc(1, sizeof(lval_T));
- /* The output is stored in growarray "redir_ga" until redirection ends. */
+ // The output is stored in growarray "redir_ga" until redirection ends.
ga_init(&redir_ga, (int)sizeof(char), 500);
// Parse the variable name (can be a dict or list entry).
@@ -787,12 +504,13 @@ var_redir_start(
if (redir_endp == NULL || redir_lval->ll_name == NULL
|| *redir_endp != NUL) {
clear_lval(redir_lval);
- if (redir_endp != NULL && *redir_endp != NUL)
- /* Trailing characters are present after the variable name */
+ if (redir_endp != NULL && *redir_endp != NUL) {
+ // Trailing characters are present after the variable name
EMSG(_(e_trailing));
- else
+ } else {
EMSG(_(e_invarg));
- redir_endp = NULL; /* don't store a value, only cleanup */
+ }
+ redir_endp = NULL; // don't store a value, only cleanup
var_redir_stop();
return FAIL;
}
@@ -812,7 +530,7 @@ var_redir_start(
err = did_emsg;
did_emsg |= save_emsg;
if (err) {
- redir_endp = NULL; /* don't store a value, only cleanup */
+ redir_endp = NULL; // don't store a value, only cleanup
var_redir_stop();
return FAIL;
}
@@ -836,10 +554,11 @@ void var_redir_str(char_u *value, int value_len)
if (redir_lval == NULL)
return;
- if (value_len == -1)
- len = (int)STRLEN(value); /* Append the entire string */
- else
- len = value_len; /* Append only "value_len" characters */
+ if (value_len == -1) {
+ len = (int)STRLEN(value); // Append the entire string
+ } else {
+ len = value_len; // Append only "value_len" characters
+ }
ga_grow(&redir_ga, len);
memmove((char *)redir_ga.ga_data + redir_ga.ga_len, value, len);
@@ -855,9 +574,9 @@ void var_redir_stop(void)
typval_T tv;
if (redir_lval != NULL) {
- /* If there was no error: assign the text to the variable. */
+ // If there was no error: assign the text to the variable.
if (redir_endp != NULL) {
- ga_append(&redir_ga, NUL); /* Append the trailing NUL. */
+ ga_append(&redir_ga, NUL); // Append the trailing NUL.
tv.v_type = VAR_STRING;
tv.vval.v_string = redir_ga.ga_data;
// Call get_lval() again, if it's inside a Dict or List it may
@@ -958,7 +677,7 @@ eval_to_bool(
char_u *arg,
bool *error,
char_u **nextcmd,
- int skip /* only parse, don't execute */
+ int skip // only parse, don't execute
)
{
typval_T tv;
@@ -1006,8 +725,8 @@ static int eval1_emsg(char_u **arg, typval_T *rettv, bool evaluate)
return ret;
}
-static int eval_expr_typval(const typval_T *expr, typval_T *argv,
- int argc, typval_T *rettv)
+int eval_expr_typval(const typval_T *expr, typval_T *argv,
+ int argc, typval_T *rettv)
FUNC_ATTR_NONNULL_ARG(1, 2, 4)
{
int dummy;
@@ -1052,7 +771,7 @@ static int eval_expr_typval(const typval_T *expr, typval_T *argv,
/// Like eval_to_bool() but using a typval_T instead of a string.
/// Works for string, funcref and partial.
-static bool eval_expr_to_bool(const typval_T *expr, bool *error)
+bool eval_expr_to_bool(const typval_T *expr, bool *error)
FUNC_ATTR_NONNULL_ARG(1, 2)
{
typval_T argv, rettv;
@@ -1155,17 +874,19 @@ char_u *eval_to_string(char_u *arg, char_u **nextcmd, int convert)
char_u *eval_to_string_safe(char_u *arg, char_u **nextcmd, int use_sandbox)
{
char_u *retval;
- void *save_funccalp;
+ funccal_entry_T funccal_entry;
- save_funccalp = save_funccal();
- if (use_sandbox)
- ++sandbox;
- ++textlock;
- retval = eval_to_string(arg, nextcmd, FALSE);
- if (use_sandbox)
- --sandbox;
- --textlock;
- restore_funccal(save_funccalp);
+ save_funccal(&funccal_entry);
+ if (use_sandbox) {
+ sandbox++;
+ }
+ textlock++;
+ retval = eval_to_string(arg, nextcmd, false);
+ if (use_sandbox) {
+ sandbox--;
+ }
+ textlock--;
+ restore_funccal();
return retval;
}
@@ -1199,7 +920,7 @@ varnumber_T eval_to_number(char_u *expr)
* Save the current typeval in "save_tv".
* When not used yet add the variable to the v: hashtable.
*/
-static void prepare_vimvar(int idx, typval_T *save_tv)
+void prepare_vimvar(int idx, typval_T *save_tv)
{
*save_tv = vimvars[idx].vv_tv;
if (vimvars[idx].vv_type == VAR_UNKNOWN)
@@ -1210,7 +931,7 @@ static void prepare_vimvar(int idx, typval_T *save_tv)
* Restore v: variable "idx" to typeval "save_tv".
* When no longer defined, remove the variable from the v: hashtable.
*/
-static void restore_vimvar(int idx, typval_T *save_tv)
+void restore_vimvar(int idx, typval_T *save_tv)
{
hashitem_T *hi;
@@ -1226,7 +947,7 @@ static void restore_vimvar(int idx, typval_T *save_tv)
}
/// If there is a window for "curbuf", make it the current window.
-static void find_win_for_curbuf(void)
+void find_win_for_curbuf(void)
{
for (wininfo_T *wip = curbuf->b_wininfo; wip != NULL; wip = wip->wi_next) {
if (wip->wi_win != NULL) {
@@ -1313,12 +1034,25 @@ int call_vim_function(
{
int doesrange;
int ret;
+ int len = (int)STRLEN(func);
+ partial_T *pt = NULL;
+
+ if (len >= 6 && !memcmp(func, "v:lua.", 6)) {
+ func += 6;
+ len = check_luafunc_name((const char *)func, false);
+ if (len == 0) {
+ ret = FAIL;
+ goto fail;
+ }
+ pt = vvlua_partial;
+ }
rettv->v_type = VAR_UNKNOWN; // tv_clear() uses this.
- ret = call_func(func, (int)STRLEN(func), rettv, argc, argv, NULL,
+ ret = call_func(func, len, rettv, argc, argv, NULL,
curwin->w_cursor.lnum, curwin->w_cursor.lnum,
- &doesrange, true, NULL, NULL);
+ &doesrange, true, pt, NULL);
+fail:
if (ret == FAIL) {
tv_clear(rettv);
}
@@ -1396,33 +1130,15 @@ void *call_func_retlist(const char_u *func, int argc, typval_T *argv)
}
/*
- * Save the current function call pointer, and set it to NULL.
- * Used when executing autocommands and for ":source".
- */
-void *save_funccal(void)
-{
- funccall_T *fc = current_funccal;
-
- current_funccal = NULL;
- return (void *)fc;
-}
-
-void restore_funccal(void *vfc)
-{
- funccall_T *fc = (funccall_T *)vfc;
-
- current_funccal = fc;
-}
-
-/*
* Prepare profiling for entering a child or something else that is not
* counted for the script/function itself.
* Should always be called in pair with prof_child_exit().
*/
-void prof_child_enter(proftime_T *tm /* place to store waittime */
- )
+void prof_child_enter(
+ proftime_T *tm // place to store waittime
+)
{
- funccall_T *fc = current_funccal;
+ funccall_T *fc = get_current_funccal();
if (fc != NULL && fc->func->uf_profiling) {
fc->prof_child = profile_start();
@@ -1435,10 +1151,11 @@ void prof_child_enter(proftime_T *tm /* place to store waittime */
* Take care of time spent in a child.
* Should always be called after prof_child_enter().
*/
-void prof_child_exit(proftime_T *tm /* where waittime was stored */
- )
+void prof_child_exit(
+ proftime_T *tm // where waittime was stored
+)
{
- funccall_T *fc = current_funccal;
+ funccall_T *fc = get_current_funccal();
if (fc != NULL && fc->func->uf_profiling) {
fc->prof_child = profile_end(fc->prof_child);
@@ -1461,7 +1178,6 @@ int eval_foldexpr(char_u *arg, int *cp)
{
typval_T tv;
varnumber_T retval;
- char_u *s;
int use_sandbox = was_set_insecurely((char_u *)"foldexpr",
OPT_LOCAL);
@@ -1470,20 +1186,21 @@ int eval_foldexpr(char_u *arg, int *cp)
++sandbox;
++textlock;
*cp = NUL;
- if (eval0(arg, &tv, NULL, TRUE) == FAIL)
+ if (eval0(arg, &tv, NULL, true) == FAIL) {
retval = 0;
- else {
- /* If the result is a number, just return the number. */
- if (tv.v_type == VAR_NUMBER)
+ } else {
+ // If the result is a number, just return the number.
+ if (tv.v_type == VAR_NUMBER) {
retval = tv.vval.v_number;
- else if (tv.v_type != VAR_STRING || tv.vval.v_string == NULL)
+ } else if (tv.v_type != VAR_STRING || tv.vval.v_string == NULL) {
retval = 0;
- else {
- /* If the result is a string, check if there is a non-digit before
- * the number. */
- s = tv.vval.v_string;
- if (!ascii_isdigit(*s) && *s != '-')
+ } else {
+ // If the result is a string, check if there is a non-digit before
+ // the number.
+ char_u *s = tv.vval.v_string;
+ if (!ascii_isdigit(*s) && *s != '-') {
*cp = *s++;
+ }
retval = atol((char *)s);
}
tv_clear(&tv);
@@ -1504,6 +1221,116 @@ void ex_const(exarg_T *eap)
ex_let_const(eap, true);
}
+// Get a list of lines from a HERE document. The here document is a list of
+// lines surrounded by a marker.
+// cmd << {marker}
+// {line1}
+// {line2}
+// ....
+// {marker}
+//
+// The {marker} is a string. If the optional 'trim' word is supplied before the
+// marker, then the leading indentation before the lines (matching the
+// indentation in the 'cmd' line) is stripped.
+// Returns a List with {lines} or NULL.
+static list_T *
+heredoc_get(exarg_T *eap, char_u *cmd)
+{
+ char_u *marker;
+ char_u *p;
+ int marker_indent_len = 0;
+ int text_indent_len = 0;
+ char_u *text_indent = NULL;
+
+ if (eap->getline == NULL) {
+ EMSG(_("E991: cannot use =<< 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++;
+ }
+ text_indent_len = -1;
+ }
+
+ // The marker is the next word.
+ if (*cmd != NUL && *cmd != '"') {
+ marker = skipwhite(cmd);
+ p = skiptowhite(marker);
+ if (*skipwhite(p) != NUL && *skipwhite(p) != '"') {
+ EMSG(_(e_trailing));
+ return NULL;
+ }
+ *p = NUL;
+ if (islower(*marker)) {
+ EMSG(_("E221: Marker cannot start with lower case letter"));
+ return NULL;
+ }
+ } else {
+ EMSG(_("E172: Missing marker"));
+ return NULL;
+ }
+
+ list_T *l = tv_list_alloc(0);
+ for (;;) {
+ int mi = 0;
+ int ti = 0;
+
+ char_u *theline = eap->getline(NUL, eap->cookie, 0, false);
+ if (theline == NULL) {
+ EMSG2(_("E990: Missing end marker '%s'"), marker);
+ break;
+ }
+
+ // with "trim": skip the indent matching the :let line to find the
+ // marker
+ if (marker_indent_len > 0
+ && STRNCMP(theline, *eap->cmdlinep, marker_indent_len) == 0) {
+ mi = marker_indent_len;
+ }
+ if (STRCMP(marker, theline + mi) == 0) {
+ xfree(theline);
+ break;
+ }
+ if (text_indent_len == -1 && *theline != NUL) {
+ // set the text indent from the first line.
+ p = theline;
+ text_indent_len = 0;
+ while (ascii_iswhite(*p)) {
+ p++;
+ text_indent_len++;
+ }
+ text_indent = vim_strnsave(theline, text_indent_len);
+ }
+ // with "trim": skip the indent matching the first line
+ if (text_indent != NULL) {
+ for (ti = 0; ti < text_indent_len; ti++) {
+ if (theline[ti] != text_indent[ti]) {
+ break;
+ }
+ }
+ }
+
+ tv_list_append_string(l, (char *)(theline + ti), -1);
+ xfree(theline);
+ }
+ xfree(text_indent);
+
+ return l;
+}
+
// ":let" list all variable values
// ":let var1 var2" list variable values
// ":let var = expr" assignment command.
@@ -1560,6 +1387,19 @@ 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] == '<') {
+ // HERE document
+ list_T *l = heredoc_get(eap, expr + 3);
+ 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, op);
+ }
+ tv_clear(&rettv);
+ }
} else {
op[0] = '=';
op[1] = NUL;
@@ -1700,10 +1540,10 @@ static const char_u *skip_var_list(const char_u *arg, int *var_count,
const char_u *s;
if (*arg == '[') {
- /* "[var, var]": find the matching ']'. */
+ // "[var, var]": find the matching ']'.
p = arg;
for (;; ) {
- p = skipwhite(p + 1); /* skip whites after '[', ';' or ',' */
+ p = skipwhite(p + 1); // skip whites after '[', ';' or ','
s = skip_var_one(p);
if (s == p) {
EMSG2(_(e_invarg2), p);
@@ -1746,8 +1586,8 @@ static const char_u *skip_var_one(const char_u *arg)
* List variables for hashtab "ht" with prefix "prefix".
* If "empty" is TRUE also list NULL strings as empty strings.
*/
-static void list_hashtable_vars(hashtab_T *ht, const char *prefix, int empty,
- int *first)
+void list_hashtable_vars(hashtab_T *ht, const char *prefix, int empty,
+ int *first)
{
hashitem_T *hi;
dictitem_T *di;
@@ -1824,17 +1664,6 @@ static void list_script_vars(int *first)
}
/*
- * List function variables, if there is a function.
- */
-static void list_func_vars(int *first)
-{
- if (current_funccal != NULL) {
- list_hashtable_vars(&current_funccal->l_vars.dv_hashtab, "l:", false,
- first);
- }
-}
-
-/*
* List variables in "arg".
*/
static const char *list_arg_vars(exarg_T *eap, const char *arg, int *first)
@@ -2009,12 +1838,15 @@ static char_u *ex_let_one(char_u *arg, typval_T *const tv,
int opt_type;
long numval;
char *stringval = NULL;
+ const char *s = NULL;
const char c1 = *p;
*p = NUL;
varnumber_T n = tv_get_number(tv);
- const char *s = tv_get_string_chk(tv); // != NULL if number or string.
+ if (tv->v_type != VAR_BOOL && tv->v_type != VAR_SPECIAL) {
+ s = tv_get_string_chk(tv); // != NULL if number or string.
+ }
if (s != NULL && op != NULL && *op != '=') {
opt_type = get_option_value(arg, &numval, (char_u **)&stringval,
opt_flags);
@@ -2040,7 +1872,8 @@ static char_u *ex_let_one(char_u *arg, typval_T *const tv,
}
}
}
- if (s != NULL) {
+ if (s != NULL || tv->v_type == VAR_BOOL
+ || tv->v_type == VAR_SPECIAL) {
set_option_value((const char *)arg, n, s, opt_flags);
arg_end = (char_u *)p;
}
@@ -2128,9 +1961,9 @@ static char_u *ex_let_one(char_u *arg, typval_T *const tv,
///
/// @return A pointer to just after the name, including indexes. Returns NULL
/// for a parsing error, but it is still needed to free items in lp.
-static char_u *get_lval(char_u *const name, typval_T *const rettv,
- lval_T *const lp, const bool unlet, const bool skip,
- const int flags, const int fne_flags)
+char_u *get_lval(char_u *const name, typval_T *const rettv,
+ lval_T *const lp, const bool unlet, const bool skip,
+ const int flags, const int fne_flags)
FUNC_ATTR_NONNULL_ARG(1, 3)
{
dictitem_T *v;
@@ -2141,7 +1974,7 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv,
hashtab_T *ht;
int quiet = flags & GLV_QUIET;
- /* Clear everything in "lp". */
+ // Clear everything in "lp".
memset(lp, 0, sizeof(lval_T));
if (skip) {
@@ -2159,7 +1992,7 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv,
(const char_u **)&expr_end,
fne_flags);
if (expr_start != NULL) {
- /* Don't expand the name when we already know there is an error. */
+ // Don't expand the name when we already know there is an error.
if (unlet && !ascii_iswhite(*p) && !ends_excmd(*p)
&& *p != '[' && *p != '.') {
EMSG(_(e_trailing));
@@ -2239,7 +2072,7 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv,
}
p = key + len;
} else {
- /* Get the index [expr] or the first index [expr: ]. */
+ // Get the index [expr] or the first index [expr: ].
p = skipwhite(p + 1);
if (*p == ':') {
empty1 = true;
@@ -2255,7 +2088,7 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv,
}
}
- /* Optionally get the second index [ :expr]. */
+ // Optionally get the second index [ :expr].
if (*p == ':') {
if (lp->ll_tv->v_type == VAR_DICT) {
if (!quiet) {
@@ -2301,8 +2134,8 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv,
return NULL;
}
- /* Skip to past ']'. */
- ++p;
+ // Skip to past ']'.
+ p++;
}
if (lp->ll_tv->v_type == VAR_DICT) {
@@ -2339,6 +2172,13 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv,
}
}
+ if (lp->ll_di != NULL && tv_is_luafunc(&lp->ll_di->di_tv)
+ && len == -1 && rettv == NULL) {
+ tv_clear(&var1);
+ EMSG2(e_illvar, "v:['lua']");
+ return NULL;
+ }
+
if (lp->ll_di == NULL) {
// Can't add "v:" or "a:" variable.
if (lp->ll_dict == &vimvardict
@@ -2444,7 +2284,7 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv,
/*
* Clear lval "lp" that was filled by get_lval().
*/
-static void clear_lval(lval_T *lp)
+void clear_lval(lval_T *lp)
{
xfree(lp->ll_exp_name);
xfree(lp->ll_newkey);
@@ -2718,10 +2558,10 @@ void set_context_for_expression(expand_T *xp, char_u *arg, cmdidx_T cmdidx)
int c;
char_u *p;
- if (cmdidx == CMD_let) {
+ if (cmdidx == CMD_let || cmdidx == CMD_const) {
xp->xp_context = EXPAND_USER_VARS;
if (vim_strpbrk(arg, (char_u *)"\"'+-*/%.=!?~|&$([<>,#") == NULL) {
- /* ":let var1 var2 ...": find last space. */
+ // ":let var1 var2 ...": find last space.
for (p = arg + STRLEN(arg); p >= arg; ) {
xp->xp_pattern = p;
MB_PTR_BACK(arg, p);
@@ -2750,7 +2590,7 @@ void set_context_for_expression(expand_T *xp, char_u *arg, cmdidx_T cmdidx)
}
} else if (c == '$') {
- /* environment variable */
+ // environment variable
xp->xp_context = EXPAND_ENV_VARS;
} else if (c == '=') {
got_eq = TRUE;
@@ -2762,18 +2602,20 @@ void set_context_for_expression(expand_T *xp, char_u *arg, cmdidx_T cmdidx)
} else if ((c == '<' || c == '#')
&& xp->xp_context == EXPAND_FUNCTIONS
&& vim_strchr(xp->xp_pattern, '(') == NULL) {
- /* Function name can start with "<SNR>" and contain '#'. */
+ // Function name can start with "<SNR>" and contain '#'.
break;
} else if (cmdidx != CMD_let || got_eq) {
- if (c == '"') { /* string */
- while ((c = *++xp->xp_pattern) != NUL && c != '"')
- if (c == '\\' && xp->xp_pattern[1] != NUL)
- ++xp->xp_pattern;
+ if (c == '"') { // string
+ while ((c = *++xp->xp_pattern) != NUL && c != '"') {
+ if (c == '\\' && xp->xp_pattern[1] != NUL) {
+ xp->xp_pattern++;
+ }
+ }
xp->xp_context = EXPAND_NOTHING;
- } else if (c == '\'') { /* literal string */
- /* Trick: '' is like stopping and starting a literal string. */
- while ((c = *++xp->xp_pattern) != NUL && c != '\'')
- /* skip */;
+ } else if (c == '\'') { // literal string
+ // Trick: '' is like stopping and starting a literal string.
+ while ((c = *++xp->xp_pattern) != NUL && c != '\'') {
+ }
xp->xp_context = EXPAND_NOTHING;
} else if (c == '|') {
if (xp->xp_pattern[1] == '|') {
@@ -2788,136 +2630,14 @@ void set_context_for_expression(expand_T *xp, char_u *arg, cmdidx_T cmdidx)
* anyway. */
xp->xp_context = EXPAND_EXPRESSION;
arg = xp->xp_pattern;
- if (*arg != NUL)
- while ((c = *++arg) != NUL && (c == ' ' || c == '\t'))
- /* skip */;
- }
- xp->xp_pattern = arg;
-}
-
-// TODO(ZyX-I): move to eval/ex_cmds
-
-/*
- * ":1,25call func(arg1, arg2)" function call.
- */
-void ex_call(exarg_T *eap)
-{
- char_u *arg = eap->arg;
- char_u *startarg;
- char_u *name;
- char_u *tofree;
- int len;
- typval_T rettv;
- linenr_T lnum;
- int doesrange;
- bool failed = false;
- funcdict_T fudi;
- partial_T *partial = NULL;
-
- if (eap->skip) {
- // trans_function_name() doesn't work well when skipping, use eval0()
- // instead to skip to any following command, e.g. for:
- // :if 0 | call dict.foo().bar() | endif.
- emsg_skip++;
- if (eval0(eap->arg, &rettv, &eap->nextcmd, false) != FAIL) {
- tv_clear(&rettv);
- }
- emsg_skip--;
- return;
- }
-
- tofree = trans_function_name(&arg, false, TFN_INT, &fudi, &partial);
- if (fudi.fd_newkey != NULL) {
- // Still need to give an error message for missing key.
- EMSG2(_(e_dictkey), fudi.fd_newkey);
- xfree(fudi.fd_newkey);
- }
- if (tofree == NULL) {
- return;
- }
-
- // Increase refcount on dictionary, it could get deleted when evaluating
- // the arguments.
- if (fudi.fd_dict != NULL) {
- fudi.fd_dict->dv_refcount++;
- }
-
- // If it is the name of a variable of type VAR_FUNC or VAR_PARTIAL use its
- // contents. For VAR_PARTIAL get its partial, unless we already have one
- // from trans_function_name().
- len = (int)STRLEN(tofree);
- name = deref_func_name((const char *)tofree, &len,
- partial != NULL ? NULL : &partial, false);
-
- // Skip white space to allow ":call func ()". Not good, but required for
- // backward compatibility.
- startarg = skipwhite(arg);
- rettv.v_type = VAR_UNKNOWN; // tv_clear() uses this.
-
- if (*startarg != '(') {
- EMSG2(_("E107: Missing parentheses: %s"), eap->arg);
- goto end;
- }
-
- lnum = eap->line1;
- for (; lnum <= eap->line2; lnum++) {
- if (eap->addr_count > 0) { // -V560
- if (lnum > curbuf->b_ml.ml_line_count) {
- // If the function deleted lines or switched to another buffer
- // the line number may become invalid.
- EMSG(_(e_invrange));
- break;
+ if (*arg != NUL) {
+ while ((c = *++arg) != NUL && (c == ' ' || c == '\t')) {
}
- curwin->w_cursor.lnum = lnum;
- curwin->w_cursor.col = 0;
- curwin->w_cursor.coladd = 0;
- }
- arg = startarg;
- if (get_func_tv(name, (int)STRLEN(name), &rettv, &arg,
- eap->line1, eap->line2, &doesrange,
- true, partial, fudi.fd_dict) == FAIL) {
- failed = true;
- break;
- }
-
- // Handle a function returning a Funcref, Dictionary or List.
- if (handle_subscript((const char **)&arg, &rettv, true, true)
- == FAIL) {
- failed = true;
- break;
- }
-
- tv_clear(&rettv);
- if (doesrange) {
- break;
- }
-
- // Stop when immediately aborting on error, or when an interrupt
- // occurred or an exception was thrown but not caught.
- // get_func_tv() returned OK, so that the check for trailing
- // characters below is executed.
- if (aborting()) {
- break;
- }
- }
-
- if (!failed) {
- // Check for trailing illegal characters and a following command.
- if (!ends_excmd(*arg)) {
- emsg_severe = TRUE;
- EMSG(_(e_trailing));
- } else {
- eap->nextcmd = check_nextcmd(arg);
}
}
-
-end:
- tv_dict_unref(fudi.fd_dict);
- xfree(tofree);
+ xp->xp_pattern = arg;
}
-// TODO(ZyX-I): move to eval/ex_cmds
-
/*
* ":unlet[!] var1 ... " command.
*/
@@ -3098,7 +2818,7 @@ static int do_unlet_var(lval_T *const lp, char_u *const name_end, int forceit)
/// @param[in] fonceit If true, do not complain if variable doesn’t exist.
///
/// @return OK if it existed, FAIL otherwise.
-int do_unlet(const char *const name, const size_t name_len, const int forceit)
+int do_unlet(const char *const name, const size_t name_len, const bool forceit)
FUNC_ATTR_NONNULL_ALL
{
const char *varname;
@@ -3106,22 +2826,22 @@ int do_unlet(const char *const name, const size_t name_len, const int forceit)
hashtab_T *ht = find_var_ht_dict(name, name_len, &varname, &dict);
if (ht != NULL && *varname != NUL) {
- dict_T *d;
- if (ht == &globvarht) {
- d = &globvardict;
- } else if (current_funccal != NULL
- && ht == &current_funccal->l_vars.dv_hashtab) {
- d = &current_funccal->l_vars;
- } else if (ht == &compat_hashtab) {
- d = &vimvardict;
- } else {
- dictitem_T *const di = find_var_in_ht(ht, *name, "", 0, false);
- d = di->di_tv.vval.v_dict;
- }
+ dict_T *d = get_current_funccal_dict(ht);
if (d == NULL) {
- internal_error("do_unlet()");
- return FAIL;
+ if (ht == &globvarht) {
+ d = &globvardict;
+ } else if (ht == &compat_hashtab) {
+ d = &vimvardict;
+ } else {
+ dictitem_T *const di = find_var_in_ht(ht, *name, "", 0, false);
+ d = di->di_tv.vval.v_dict;
+ }
+ if (d == NULL) {
+ internal_error("do_unlet()");
+ return FAIL;
+ }
}
+
hashitem_T *hi = hash_find(ht, (const char_u *)varname);
if (HASHITEM_EMPTY(hi)) {
hi = find_hi_in_scoped_ht((const char *)name, &ht);
@@ -3200,7 +2920,7 @@ static int do_lock_var(lval_T *lp, char_u *const name_end, const int deep,
} else if (lp->ll_range) {
listitem_T *li = lp->ll_li;
- /* (un)lock a range of List items. */
+ // (un)lock a range of List items.
while (li != NULL && (lp->ll_empty2 || lp->ll_n2 >= lp->ll_n1)) {
tv_item_lock(TV_LIST_ITEM_TV(li), deep, lock);
li = TV_LIST_ITEM_NEXT(lp->ll_list, li);
@@ -3250,7 +2970,7 @@ static char_u *cat_prefix_varname(int prefix, char_u *name)
if (len > varnamebuflen) {
xfree(varnamebuf);
- len += 10; /* some additional space */
+ len += 10; // some additional space
varnamebuf = xmalloc(len);
varnamebuflen = len;
}
@@ -3279,7 +2999,7 @@ char_u *get_user_var_name(expand_T *xp, int idx)
tdone = 0;
}
- /* Global variables */
+ // Global variables
if (gdone < globvarht.ht_used) {
if (gdone++ == 0)
hi = globvarht.ht_array;
@@ -3292,7 +3012,7 @@ char_u *get_user_var_name(expand_T *xp, int idx)
return hi->hi_key;
}
- /* b: variables */
+ // b: variables
ht = &curbuf->b_vars->dv_hashtab;
if (bdone < ht->ht_used) {
if (bdone++ == 0)
@@ -3304,7 +3024,7 @@ char_u *get_user_var_name(expand_T *xp, int idx)
return cat_prefix_varname('b', hi->hi_key);
}
- /* w: variables */
+ // w: variables
ht = &curwin->w_vars->dv_hashtab;
if (wdone < ht->ht_used) {
if (wdone++ == 0)
@@ -3316,7 +3036,7 @@ char_u *get_user_var_name(expand_T *xp, int idx)
return cat_prefix_varname('w', hi->hi_key);
}
- /* t: variables */
+ // t: variables
ht = &curtab->tp_vars->dv_hashtab;
if (tdone < ht->ht_used) {
if (tdone++ == 0)
@@ -3342,7 +3062,7 @@ char_u *get_user_var_name(expand_T *xp, int idx)
/// Return TRUE if "pat" matches "text".
/// Does not use 'cpo' and always uses 'magic'.
-static int pattern_match(char_u *pat, char_u *text, int ic)
+static int pattern_match(char_u *pat, char_u *text, bool ic)
{
int matches = 0;
regmatch_T regmatch;
@@ -3428,7 +3148,7 @@ int eval0(char_u *arg, typval_T *rettv, char_u **nextcmd, int evaluate)
*
* Return OK or FAIL.
*/
-static int eval1(char_u **arg, typval_T *rettv, int evaluate)
+int eval1(char_u **arg, typval_T *rettv, int evaluate)
{
int result;
typval_T var2;
@@ -3457,8 +3177,9 @@ static int eval1(char_u **arg, typval_T *rettv, int evaluate)
* Get the second variable.
*/
*arg = skipwhite(*arg + 1);
- if (eval1(arg, rettv, evaluate && result) == FAIL) /* recursive! */
+ if (eval1(arg, rettv, evaluate && result) == FAIL) { // recursive!
return FAIL;
+ }
/*
* Check for the ":".
@@ -3652,10 +3373,10 @@ static int eval4(char_u **arg, typval_T *rettv, int evaluate)
char_u *p;
int i;
exptype_T type = TYPE_UNKNOWN;
- int type_is = FALSE; /* TRUE for "is" and "isnot" */
+ bool type_is = false; // true for "is" and "isnot"
int len = 2;
varnumber_T n1, n2;
- int ic;
+ bool ic;
/*
* Get the first variable.
@@ -3693,7 +3414,7 @@ static int eval4(char_u **arg, typval_T *rettv, int evaluate)
}
if (!isalnum(p[len]) && p[len] != '_') {
type = len == 2 ? TYPE_EQUAL : TYPE_NEQUAL;
- type_is = TRUE;
+ type_is = true;
}
}
break;
@@ -3703,23 +3424,18 @@ static int eval4(char_u **arg, typval_T *rettv, int evaluate)
* If there is a comparative operator, use it.
*/
if (type != TYPE_UNKNOWN) {
- /* extra question mark appended: ignore case */
+ // extra question mark appended: ignore case
if (p[len] == '?') {
- ic = TRUE;
- ++len;
- }
- /* extra '#' appended: match case */
- else if (p[len] == '#') {
- ic = FALSE;
- ++len;
- }
- /* nothing appended: use 'ignorecase' */
- else
+ ic = true;
+ len++;
+ } else if (p[len] == '#') { // extra '#' appended: match case
+ ic = false;
+ len++;
+ } else { // nothing appended: use 'ignorecase'
ic = p_ic;
+ }
- /*
- * Get the second variable.
- */
+ // Get the second variable.
*arg = skipwhite(p + len);
if (eval5(arg, &var2, evaluate) == FAIL) {
tv_clear(rettv);
@@ -3864,7 +3580,7 @@ static int eval4(char_u **arg, typval_T *rettv, int evaluate)
const char *const s1 = tv_get_string_buf(rettv, buf1);
const char *const s2 = tv_get_string_buf(&var2, buf2);
if (type != TYPE_MATCH && type != TYPE_NOMATCH) {
- i = mb_strcmp_ic((bool)ic, s1, s2);
+ i = mb_strcmp_ic(ic, s1, s2);
} else {
i = 0;
}
@@ -4025,7 +3741,7 @@ static int eval5(char_u **arg, typval_T *rettv, int evaluate)
}
tv_clear(rettv);
- /* If there is a float on either side the result is a float. */
+ // If there is a float on either side the result is a float.
if (rettv->v_type == VAR_FLOAT || var2.v_type == VAR_FLOAT) {
if (op == '+')
f1 = f1 + f2;
@@ -4464,7 +4180,7 @@ eval_index(
char_u **arg,
typval_T *rettv,
int evaluate,
- int verbose /* give error messages */
+ int verbose // give error messages
)
{
bool empty1 = false;
@@ -4488,6 +4204,7 @@ eval_index(
}
return FAIL;
}
+ case VAR_BOOL:
case VAR_SPECIAL: {
if (verbose) {
EMSG(_("E909: Cannot index a special variable"));
@@ -4560,7 +4277,7 @@ eval_index(
}
}
- /* Check for the ']'. */
+ // Check for the ']'.
if (**arg != ']') {
if (verbose) {
EMSG(_(e_missbrac));
@@ -4571,12 +4288,12 @@ eval_index(
}
return FAIL;
}
- *arg = skipwhite(*arg + 1); /* skip the ']' */
+ *arg = skipwhite(*arg + 1); // skip the ']'
}
if (evaluate) {
n1 = 0;
- if (!empty1 && rettv->v_type != VAR_DICT) {
+ if (!empty1 && rettv->v_type != VAR_DICT && !tv_is_luafunc(rettv)) {
n1 = tv_get_number(&var1);
tv_clear(&var1);
}
@@ -4700,7 +4417,7 @@ eval_index(
if (len == -1) {
tv_clear(&var1);
}
- if (item == NULL) {
+ if (item == NULL || tv_is_luafunc(&item->di_tv)) {
return FAIL;
}
@@ -4709,6 +4426,7 @@ eval_index(
*rettv = var1;
break;
}
+ case VAR_BOOL:
case VAR_SPECIAL:
case VAR_FUNC:
case VAR_FLOAT:
@@ -4732,8 +4450,8 @@ eval_index(
/// @param[in] evaluate If not true, rettv is not populated.
///
/// @return OK or FAIL.
-static int get_option_tv(const char **const arg, typval_T *const rettv,
- const bool evaluate)
+int get_option_tv(const char **const arg, typval_T *const rettv,
+ const bool evaluate)
FUNC_ATTR_NONNULL_ARG(1)
{
long numval;
@@ -4763,28 +4481,29 @@ static int get_option_tv(const char **const arg, typval_T *const rettv,
opt_type = get_option_value((char_u *)(*arg), &numval,
rettv == NULL ? NULL : &stringval, opt_flags);
- if (opt_type == -3) { /* invalid name */
- if (rettv != NULL)
+ if (opt_type == -3) { // invalid name
+ if (rettv != NULL) {
EMSG2(_("E113: Unknown option: %s"), *arg);
+ }
ret = FAIL;
} else if (rettv != NULL) {
- if (opt_type == -2) { /* hidden string option */
+ if (opt_type == -2) { // hidden string option
rettv->v_type = VAR_STRING;
rettv->vval.v_string = NULL;
- } else if (opt_type == -1) { /* hidden number option */
+ } else if (opt_type == -1) { // hidden number option
rettv->v_type = VAR_NUMBER;
rettv->vval.v_number = 0;
- } else if (opt_type == 1) { /* number option */
+ } else if (opt_type == 1) { // number option
rettv->v_type = VAR_NUMBER;
rettv->vval.v_number = numval;
- } else { /* string option */
+ } else { // string option
rettv->v_type = VAR_STRING;
rettv->vval.v_string = stringval;
}
} else if (working && (opt_type == -2 || opt_type == -1))
ret = FAIL;
- *option_end = c; /* put back for error messages */
+ *option_end = c; // put back for error messages
*arg = option_end;
return ret;
@@ -4818,7 +4537,7 @@ static int get_string_tv(char_u **arg, typval_T *rettv, int evaluate)
return FAIL;
}
- /* If only parsing, set *arg and return here */
+ // If only parsing, set *arg and return here
if (!evaluate) {
*arg = p + 1;
return OK;
@@ -4842,9 +4561,9 @@ static int get_string_tv(char_u **arg, typval_T *rettv, int evaluate)
case 'r': *name++ = CAR; ++p; break;
case 't': *name++ = TAB; ++p; break;
- case 'X': /* hex: "\x1", "\x12" */
+ case 'X': // hex: "\x1", "\x12"
case 'x':
- case 'u': /* Unicode: "\u0023" */
+ case 'u': // Unicode: "\u0023"
case 'U':
if (ascii_isxdigit(p[1])) {
int n, nr;
@@ -4873,7 +4592,7 @@ static int get_string_tv(char_u **arg, typval_T *rettv, int evaluate)
}
break;
- /* octal: "\1", "\12", "\123" */
+ // octal: "\1", "\12", "\123"
case '0':
case '1':
case '2':
@@ -4942,7 +4661,7 @@ static int get_lit_string_tv(char_u **arg, typval_T *rettv, int evaluate)
return FAIL;
}
- /* If only parsing return after setting "*arg" */
+ // If only parsing return after setting "*arg"
if (!evaluate) {
*arg = p + 1;
return OK;
@@ -5125,9 +4844,6 @@ int get_copyID(void)
return current_copyID;
}
-// Used by get_func_tv()
-static garray_T funcargs = GA_EMPTY_INIT_VALUE;
-
/*
* Garbage collection for lists and dictionaries.
*
@@ -5174,11 +4890,7 @@ bool garbage_collect(bool testing)
// Don't free variables in the previous_funccal list unless they are only
// referenced through previous_funccal. This must be first, because if
// the item is referenced elsewhere the funccal must not be freed.
- for (funccall_T *fc = previous_funccal; fc != NULL; fc = fc->caller) {
- fc->fc_copyID = copyID + 1;
- ABORTING(set_ref_in_ht)(&fc->l_vars.dv_hashtab, copyID + 1, NULL);
- ABORTING(set_ref_in_ht)(&fc->l_avars.dv_hashtab, copyID + 1, NULL);
- }
+ ABORTING(set_ref_in_previous_funccal)(copyID);
// script-local variables
for (int i = 1; i <= ga_scripts.ga_len; ++i) {
@@ -5201,6 +4913,10 @@ bool garbage_collect(bool testing)
}
// buffer ShaDa additional data
ABORTING(set_ref_dict)(buf->additional_data, copyID);
+
+ // buffer callback functions
+ set_ref_in_callback(&buf->b_prompt_callback, copyID, NULL, NULL);
+ set_ref_in_callback(&buf->b_prompt_interrupt, copyID, NULL, NULL);
}
FOR_ALL_TAB_WINDOWS(tp, wp) {
@@ -5251,14 +4967,10 @@ bool garbage_collect(bool testing)
ABORTING(set_ref_in_ht)(&globvarht, copyID, NULL);
// function-local variables
- for (funccall_T *fc = current_funccal; fc != NULL; fc = fc->caller) {
- fc->fc_copyID = copyID;
- ABORTING(set_ref_in_ht)(&fc->l_vars.dv_hashtab, copyID, NULL);
- ABORTING(set_ref_in_ht)(&fc->l_avars.dv_hashtab, copyID, NULL);
- }
+ ABORTING(set_ref_in_call_stack)(copyID);
// named functions (matters for closures)
- ABORTING(set_ref_in_functions(copyID));
+ ABORTING(set_ref_in_functions)(copyID);
// Channels
{
@@ -5279,10 +4991,7 @@ bool garbage_collect(bool testing)
}
// function call arguments, if v:testing is set.
- for (int i = 0; i < funcargs.ga_len; i++) {
- ABORTING(set_ref_in_item)(((typval_T **)funcargs.ga_data)[i],
- copyID, NULL, NULL);
- }
+ ABORTING(set_ref_in_func_args)(copyID);
// v: vars
ABORTING(set_ref_in_ht)(&vimvarht, copyID, NULL);
@@ -5325,23 +5034,8 @@ bool garbage_collect(bool testing)
did_free = free_unref_items(copyID);
// 3. Check if any funccal can be freed now.
- bool did_free_funccal = false;
- for (funccall_T **pfc = &previous_funccal; *pfc != NULL;) {
- if (can_free_funccal(*pfc, copyID)) {
- funccall_T *fc = *pfc;
- *pfc = fc->caller;
- free_funccal(fc, true);
- did_free = true;
- did_free_funccal = true;
- } else {
- pfc = &(*pfc)->caller;
- }
- }
- if (did_free_funccal) {
- // When a funccal was freed some more items might be garbage
- // collected, so run again.
- (void)garbage_collect(testing);
- }
+ // This may call us back recursively.
+ did_free = free_unref_funccal(copyID, testing) || did_free;
} else if (p_verbose > 0) {
verb_msg(_(
"Not enough memory to set references, garbage collection aborted!"));
@@ -5586,6 +5280,7 @@ bool set_ref_in_item(typval_T *tv, int copyID, ht_stack_T **ht_stack,
abort = set_ref_in_func(tv->vval.v_string, NULL, copyID);
break;
case VAR_UNKNOWN:
+ case VAR_BOOL:
case VAR_SPECIAL:
case VAR_FLOAT:
case VAR_NUMBER:
@@ -5596,27 +5291,6 @@ bool set_ref_in_item(typval_T *tv, int copyID, ht_stack_T **ht_stack,
return abort;
}
-/// Set "copyID" in all functions available by name.
-bool set_ref_in_functions(int copyID)
-{
- int todo;
- hashitem_T *hi = NULL;
- bool abort = false;
- ufunc_T *fp;
-
- todo = (int)func_hashtab.ht_used;
- for (hi = func_hashtab.ht_array; todo > 0 && !got_int; hi++) {
- if (!HASHITEM_EMPTY(hi)) {
- todo--;
- fp = HI2UF(hi);
- if (!func_name_refcount(fp->uf_name)) {
- abort = abort || set_ref_in_func(NULL, fp, copyID);
- }
- }
- }
- return abort;
-}
-
/// Mark all lists and dicts referenced in given mark
@@ -5665,19 +5339,6 @@ static inline bool set_ref_dict(dict_T *dict, int copyID)
return false;
}
-static bool set_ref_in_funccal(funccall_T *fc, int copyID)
-{
- bool abort = false;
-
- if (fc->fc_copyID != copyID) {
- fc->fc_copyID = copyID;
- abort = abort || set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID, NULL);
- abort = abort || set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID, NULL);
- abort = abort || set_ref_in_func(NULL, fc->func, copyID);
- }
- return abort;
-}
-
/*
* Allocate a variable for a Dictionary and fill it from "*arg".
* Return OK or FAIL. Returns NOTDONE for {expr}.
@@ -5700,10 +5361,12 @@ static int dict_get_tv(char_u **arg, typval_T *rettv, int evaluate)
* But {} is an empty Dictionary.
*/
if (*start != '}') {
- if (eval1(&start, &tv, FALSE) == FAIL) /* recursive! */
+ if (eval1(&start, &tv, false) == FAIL) { // recursive!
return FAIL;
- if (*start == '}')
+ }
+ if (*start == '}') {
return NOTDONE;
+ }
}
if (evaluate) {
@@ -5714,8 +5377,9 @@ static int dict_get_tv(char_u **arg, typval_T *rettv, int evaluate)
*arg = skipwhite(*arg + 1);
while (**arg != '}' && **arg != NUL) {
- if (eval1(arg, &tvkey, evaluate) == FAIL) /* recursive! */
+ if (eval1(arg, &tvkey, evaluate) == FAIL) { // recursive!
goto failret;
+ }
if (**arg != ':') {
EMSG2(_("E720: Missing colon in Dictionary: %s"), *arg);
tv_clear(&tvkey);
@@ -5746,13 +5410,13 @@ static int dict_get_tv(char_u **arg, typval_T *rettv, int evaluate)
goto failret;
}
item = tv_dict_item_alloc((const char *)key);
- tv_clear(&tvkey);
item->di_tv = tv;
item->di_tv.v_lock = 0;
if (tv_dict_add(d, item) == FAIL) {
tv_dict_item_free(item);
}
}
+ tv_clear(&tvkey);
if (**arg == '}')
break;
@@ -5766,7 +5430,7 @@ static int dict_get_tv(char_u **arg, typval_T *rettv, int evaluate)
if (**arg != '}') {
EMSG2(_("E723: Missing end of Dictionary '}': %s"), *arg);
failret:
- if (evaluate) {
+ if (d != NULL) {
tv_dict_free(d);
}
return FAIL;
@@ -5780,224 +5444,6 @@ failret:
return OK;
}
-/// Get function arguments.
-static int get_function_args(char_u **argp, char_u endchar, garray_T *newargs,
- int *varargs, bool skip)
-{
- bool mustend = false;
- char_u *arg = *argp;
- char_u *p = arg;
- int c;
- int i;
-
- if (newargs != NULL) {
- ga_init(newargs, (int)sizeof(char_u *), 3);
- }
-
- if (varargs != NULL) {
- *varargs = false;
- }
-
- // Isolate the arguments: "arg1, arg2, ...)"
- while (*p != endchar) {
- if (p[0] == '.' && p[1] == '.' && p[2] == '.') {
- if (varargs != NULL) {
- *varargs = true;
- }
- p += 3;
- mustend = true;
- } else {
- arg = p;
- while (ASCII_ISALNUM(*p) || *p == '_') {
- p++;
- }
- if (arg == p || isdigit(*arg)
- || (p - arg == 9 && STRNCMP(arg, "firstline", 9) == 0)
- || (p - arg == 8 && STRNCMP(arg, "lastline", 8) == 0)) {
- if (!skip) {
- EMSG2(_("E125: Illegal argument: %s"), arg);
- }
- break;
- }
- if (newargs != NULL) {
- ga_grow(newargs, 1);
- c = *p;
- *p = NUL;
- arg = vim_strsave(arg);
-
- // Check for duplicate argument name.
- for (i = 0; i < newargs->ga_len; i++) {
- if (STRCMP(((char_u **)(newargs->ga_data))[i], arg) == 0) {
- EMSG2(_("E853: Duplicate argument name: %s"), arg);
- xfree(arg);
- goto err_ret;
- }
- }
- ((char_u **)(newargs->ga_data))[newargs->ga_len] = arg;
- newargs->ga_len++;
-
- *p = c;
- }
- if (*p == ',') {
- p++;
- } else {
- mustend = true;
- }
- }
- p = skipwhite(p);
- if (mustend && *p != endchar) {
- if (!skip) {
- EMSG2(_(e_invarg2), *argp);
- }
- break;
- }
- }
- if (*p != endchar) {
- goto err_ret;
- }
- p++; // skip "endchar"
-
- *argp = p;
- return OK;
-
-err_ret:
- if (newargs != NULL) {
- ga_clear_strings(newargs);
- }
- return FAIL;
-}
-
-/// Register function "fp" as using "current_funccal" as its scope.
-static void register_closure(ufunc_T *fp)
-{
- if (fp->uf_scoped == current_funccal) {
- // no change
- return;
- }
- funccal_unref(fp->uf_scoped, fp, false);
- fp->uf_scoped = current_funccal;
- current_funccal->fc_refcount++;
- ga_grow(&current_funccal->fc_funcs, 1);
- ((ufunc_T **)current_funccal->fc_funcs.ga_data)
- [current_funccal->fc_funcs.ga_len++] = fp;
-}
-
-/// Parse a lambda expression and get a Funcref from "*arg".
-///
-/// @return OK or FAIL. Returns NOTDONE for dict or {expr}.
-static int get_lambda_tv(char_u **arg, typval_T *rettv, bool evaluate)
-{
- garray_T newargs = GA_EMPTY_INIT_VALUE;
- garray_T *pnewargs;
- ufunc_T *fp = NULL;
- int varargs;
- int ret;
- char_u *start = skipwhite(*arg + 1);
- char_u *s, *e;
- static int lambda_no = 0;
- int *old_eval_lavars = eval_lavars_used;
- int eval_lavars = false;
-
- // First, check if this is a lambda expression. "->" must exists.
- ret = get_function_args(&start, '-', NULL, NULL, true);
- if (ret == FAIL || *start != '>') {
- return NOTDONE;
- }
-
- // Parse the arguments again.
- if (evaluate) {
- pnewargs = &newargs;
- } else {
- pnewargs = NULL;
- }
- *arg = skipwhite(*arg + 1);
- ret = get_function_args(arg, '-', pnewargs, &varargs, false);
- if (ret == FAIL || **arg != '>') {
- goto errret;
- }
-
- // Set up a flag for checking local variables and arguments.
- if (evaluate) {
- eval_lavars_used = &eval_lavars;
- }
-
- // Get the start and the end of the expression.
- *arg = skipwhite(*arg + 1);
- s = *arg;
- ret = skip_expr(arg);
- if (ret == FAIL) {
- goto errret;
- }
- e = *arg;
- *arg = skipwhite(*arg);
- if (**arg != '}') {
- goto errret;
- }
- (*arg)++;
-
- if (evaluate) {
- int len, flags = 0;
- char_u *p;
- char_u name[20];
- partial_T *pt;
- garray_T newlines;
-
- lambda_no++;
- snprintf((char *)name, sizeof(name), "<lambda>%d", lambda_no);
-
- fp = xcalloc(1, offsetof(ufunc_T, uf_name) + STRLEN(name) + 1);
- pt = xcalloc(1, sizeof(partial_T));
-
- ga_init(&newlines, (int)sizeof(char_u *), 1);
- ga_grow(&newlines, 1);
-
- // Add "return " before the expression.
- len = 7 + e - s + 1;
- p = (char_u *)xmalloc(len);
- ((char_u **)(newlines.ga_data))[newlines.ga_len++] = p;
- STRCPY(p, "return ");
- STRLCPY(p + 7, s, e - s + 1);
-
- fp->uf_refcount = 1;
- STRCPY(fp->uf_name, name);
- hash_add(&func_hashtab, UF2HIKEY(fp));
- fp->uf_args = newargs;
- fp->uf_lines = newlines;
- if (current_funccal != NULL && eval_lavars) {
- flags |= FC_CLOSURE;
- register_closure(fp);
- } else {
- fp->uf_scoped = NULL;
- }
-
- if (prof_def_func()) {
- func_do_profile(fp);
- }
- if (sandbox) {
- flags |= FC_SANDBOX;
- }
- fp->uf_varargs = true;
- fp->uf_flags = flags;
- fp->uf_calls = 0;
- fp->uf_script_ctx = current_sctx;
- fp->uf_script_ctx.sc_lnum += sourcing_lnum - newlines.ga_len;
-
- pt->pt_func = fp;
- pt->pt_refcount = 1;
- rettv->vval.v_partial = pt;
- rettv->v_type = VAR_PARTIAL;
- }
-
- eval_lavars_used = old_eval_lavars;
- return OK;
-
-errret:
- ga_clear_strings(&newargs);
- xfree(fp);
- eval_lavars_used = old_eval_lavars;
- return FAIL;
-}
-
/// Convert the string to a floating point number
///
/// This uses strtod(). setlocale(LC_NUMERIC, "C") has been used earlier to
@@ -6072,722 +5518,9 @@ static int get_env_tv(char_u **arg, typval_T *rettv, int evaluate)
return OK;
}
-#ifdef INCLUDE_GENERATED_DECLARATIONS
-
-#ifdef _MSC_VER
-// This prevents MSVC from replacing the functions with intrinsics,
-// and causing errors when trying to get their addresses in funcs.generated.h
-#pragma function (ceil)
-#pragma function (floor)
-#endif
-
-PRAGMA_DIAG_PUSH_IGNORE_MISSING_PROTOTYPES
-# include "funcs.generated.h"
-PRAGMA_DIAG_POP
-#endif
-
-/*
- * Function given to ExpandGeneric() to obtain the list of internal
- * or user defined function names.
- */
-char_u *get_function_name(expand_T *xp, int idx)
-{
- static int intidx = -1;
- char_u *name;
-
- if (idx == 0)
- intidx = -1;
- if (intidx < 0) {
- name = get_user_func_name(xp, idx);
- if (name != NULL)
- return name;
- }
- while ( (size_t)++intidx < ARRAY_SIZE(functions)
- && functions[intidx].name[0] == '\0') {
- }
-
- if ((size_t)intidx >= ARRAY_SIZE(functions)) {
- return NULL;
- }
-
- const char *const key = functions[intidx].name;
- const size_t key_len = strlen(key);
- memcpy(IObuff, key, key_len);
- IObuff[key_len] = '(';
- if (functions[intidx].max_argc == 0) {
- IObuff[key_len + 1] = ')';
- IObuff[key_len + 2] = NUL;
- } else {
- IObuff[key_len + 1] = NUL;
- }
- return IObuff;
-}
-
-/*
- * Function given to ExpandGeneric() to obtain the list of internal or
- * user defined variable or function names.
- */
-char_u *get_expr_name(expand_T *xp, int idx)
-{
- static int intidx = -1;
- char_u *name;
-
- if (idx == 0)
- intidx = -1;
- if (intidx < 0) {
- name = get_function_name(xp, idx);
- if (name != NULL)
- return name;
- }
- return get_user_var_name(xp, ++intidx);
-}
-
-/// Find internal function in hash functions
-///
-/// @param[in] name Name of the function.
-///
-/// Returns pointer to the function definition or NULL if not found.
-static const VimLFuncDef *find_internal_func(const char *const name)
- FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE FUNC_ATTR_NONNULL_ALL
-{
- size_t len = strlen(name);
- return find_internal_func_gperf(name, len);
-}
-
-/// Return name of the function corresponding to `name`
-///
-/// If `name` points to variable that is either a function or partial then
-/// corresponding function name is returned. Otherwise it returns `name` itself.
-///
-/// @param[in] name Function name to check.
-/// @param[in,out] lenp Location where length of the returned name is stored.
-/// Must be set to the length of the `name` argument.
-/// @param[out] partialp Location where partial will be stored if found
-/// function appears to be a partial. May be NULL if this
-/// is not needed.
-/// @param[in] no_autoload If true, do not source autoload scripts if function
-/// was not found.
-///
-/// @return name of the function.
-static char_u *deref_func_name(const char *name, int *lenp,
- partial_T **const partialp, bool no_autoload)
- FUNC_ATTR_NONNULL_ARG(1, 2)
-{
- if (partialp != NULL) {
- *partialp = NULL;
- }
-
- dictitem_T *const v = find_var(name, (size_t)(*lenp), NULL, no_autoload);
- if (v != NULL && v->di_tv.v_type == VAR_FUNC) {
- if (v->di_tv.vval.v_string == NULL) { // just in case
- *lenp = 0;
- return (char_u *)"";
- }
- *lenp = (int)STRLEN(v->di_tv.vval.v_string);
- return v->di_tv.vval.v_string;
- }
-
- if (v != NULL && v->di_tv.v_type == VAR_PARTIAL) {
- partial_T *const pt = v->di_tv.vval.v_partial;
-
- if (pt == NULL) { // just in case
- *lenp = 0;
- return (char_u *)"";
- }
- if (partialp != NULL) {
- *partialp = pt;
- }
- char_u *s = partial_name(pt);
- *lenp = (int)STRLEN(s);
- return s;
- }
-
- return (char_u *)name;
-}
-
-/*
- * Allocate a variable for the result of a function.
- * Return OK or FAIL.
- */
-static int
-get_func_tv(
- char_u *name, // name of the function
- int len, // length of "name"
- typval_T *rettv,
- char_u **arg, // argument, pointing to the '('
- linenr_T firstline, // first line of range
- linenr_T lastline, // last line of range
- int *doesrange, // return: function handled range
- int evaluate,
- partial_T *partial, // for extra arguments
- dict_T *selfdict // Dictionary for "self"
-)
-{
- char_u *argp;
- int ret = OK;
- typval_T argvars[MAX_FUNC_ARGS + 1]; /* vars for arguments */
- int argcount = 0; /* number of arguments found */
-
- /*
- * Get the arguments.
- */
- argp = *arg;
- while (argcount < MAX_FUNC_ARGS - (partial == NULL ? 0 : partial->pt_argc)) {
- argp = skipwhite(argp + 1); // skip the '(' or ','
- if (*argp == ')' || *argp == ',' || *argp == NUL) {
- break;
- }
- if (eval1(&argp, &argvars[argcount], evaluate) == FAIL) {
- ret = FAIL;
- break;
- }
- ++argcount;
- if (*argp != ',')
- break;
- }
- if (*argp == ')')
- ++argp;
- else
- ret = FAIL;
-
- if (ret == OK) {
- int i = 0;
-
- if (get_vim_var_nr(VV_TESTING)) {
- // Prepare for calling garbagecollect_for_testing(), need to know
- // what variables are used on the call stack.
- if (funcargs.ga_itemsize == 0) {
- ga_init(&funcargs, (int)sizeof(typval_T *), 50);
- }
- for (i = 0; i < argcount; i++) {
- ga_grow(&funcargs, 1);
- ((typval_T **)funcargs.ga_data)[funcargs.ga_len++] = &argvars[i];
- }
- }
- ret = call_func(name, len, rettv, argcount, argvars, NULL,
- firstline, lastline, doesrange, evaluate,
- partial, selfdict);
-
- funcargs.ga_len -= i;
- } else if (!aborting()) {
- if (argcount == MAX_FUNC_ARGS) {
- emsg_funcname(N_("E740: Too many arguments for function %s"), name);
- } else {
- emsg_funcname(N_("E116: Invalid arguments for function %s"), name);
- }
- }
-
- while (--argcount >= 0) {
- tv_clear(&argvars[argcount]);
- }
-
- *arg = skipwhite(argp);
- return ret;
-}
-
-typedef enum {
- ERROR_UNKNOWN = 0,
- ERROR_TOOMANY,
- ERROR_TOOFEW,
- ERROR_SCRIPT,
- ERROR_DICT,
- ERROR_NONE,
- ERROR_OTHER,
- ERROR_BOTH,
- ERROR_DELETED,
-} FnameTransError;
-
-#define FLEN_FIXED 40
-
-/// In a script transform script-local names into actually used names
-///
-/// Transforms "<SID>" and "s:" prefixes to `K_SNR {N}` (e.g. K_SNR "123") and
-/// "<SNR>" prefix to `K_SNR`. Uses `fname_buf` buffer that is supposed to have
-/// #FLEN_FIXED + 1 length when it fits, otherwise it allocates memory.
-///
-/// @param[in] name Name to transform.
-/// @param fname_buf Buffer to save resulting function name to, if it fits.
-/// Must have at least #FLEN_FIXED + 1 length.
-/// @param[out] tofree Location where pointer to an allocated memory is saved
-/// in case result does not fit into fname_buf.
-/// @param[out] error Location where error type is saved, @see
-/// FnameTransError.
-///
-/// @return transformed name: either `fname_buf` or a pointer to an allocated
-/// memory.
-static char_u *fname_trans_sid(const char_u *const name,
- char_u *const fname_buf,
- char_u **const tofree, int *const error)
- FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
-{
- char_u *fname;
- const int llen = eval_fname_script((const char *)name);
- if (llen > 0) {
- fname_buf[0] = K_SPECIAL;
- fname_buf[1] = KS_EXTRA;
- fname_buf[2] = (int)KE_SNR;
- int i = 3;
- if (eval_fname_sid((const char *)name)) { // "<SID>" or "s:"
- if (current_sctx.sc_sid <= 0) {
- *error = ERROR_SCRIPT;
- } else {
- snprintf((char *)fname_buf + 3, FLEN_FIXED + 1, "%" PRId64 "_",
- (int64_t)current_sctx.sc_sid);
- i = (int)STRLEN(fname_buf);
- }
- }
- if (i + STRLEN(name + llen) < FLEN_FIXED) {
- STRCPY(fname_buf + i, name + llen);
- fname = fname_buf;
- } else {
- fname = xmalloc(i + STRLEN(name + llen) + 1);
- *tofree = fname;
- memmove(fname, fname_buf, (size_t)i);
- STRCPY(fname + i, name + llen);
- }
- } else {
- fname = (char_u *)name;
- }
-
- return fname;
-}
-
-/// Mark all lists and dicts referenced through function "name" with "copyID".
-/// "list_stack" is used to add lists to be marked. Can be NULL.
-/// "ht_stack" is used to add hashtabs to be marked. Can be NULL.
-///
-/// @return true if setting references failed somehow.
-bool set_ref_in_func(char_u *name, ufunc_T *fp_in, int copyID)
-{
- ufunc_T *fp = fp_in;
- funccall_T *fc;
- int error = ERROR_NONE;
- char_u fname_buf[FLEN_FIXED + 1];
- char_u *tofree = NULL;
- char_u *fname;
- bool abort = false;
- if (name == NULL && fp_in == NULL) {
- return false;
- }
-
- if (fp_in == NULL) {
- fname = fname_trans_sid(name, fname_buf, &tofree, &error);
- fp = find_func(fname);
- }
- if (fp != NULL) {
- for (fc = fp->uf_scoped; fc != NULL; fc = fc->func->uf_scoped) {
- abort = abort || set_ref_in_funccal(fc, copyID);
- }
- }
- xfree(tofree);
- return abort;
-}
-
-/// Call a function with its resolved parameters
-///
-/// "argv_func", when not NULL, can be used to fill in arguments only when the
-/// invoked function uses them. It is called like this:
-/// new_argcount = argv_func(current_argcount, argv, called_func_argcount)
-///
-/// @return FAIL if function cannot be called, else OK (even if an error
-/// occurred while executing the function! Set `msg_list` to capture
-/// the error, see do_cmdline()).
-int
-call_func(
- const char_u *funcname, // name of the function
- int len, // length of "name"
- typval_T *rettv, // [out] value goes here
- int argcount_in, // number of "argvars"
- typval_T *argvars_in, // vars for arguments, must have "argcount"
- // PLUS ONE elements!
- ArgvFunc argv_func, // function to fill in argvars
- linenr_T firstline, // first line of range
- linenr_T lastline, // last line of range
- int *doesrange, // [out] function handled range
- bool evaluate,
- partial_T *partial, // optional, can be NULL
- dict_T *selfdict_in // Dictionary for "self"
-)
- FUNC_ATTR_NONNULL_ARG(1, 3, 5, 9)
-{
- int ret = FAIL;
- int error = ERROR_NONE;
- ufunc_T *fp;
- char_u fname_buf[FLEN_FIXED + 1];
- char_u *tofree = NULL;
- char_u *fname;
- char_u *name;
- int argcount = argcount_in;
- typval_T *argvars = argvars_in;
- dict_T *selfdict = selfdict_in;
- typval_T argv[MAX_FUNC_ARGS + 1]; // used when "partial" is not NULL
- int argv_clear = 0;
-
- // Initialize rettv so that it is safe for caller to invoke clear_tv(rettv)
- // even when call_func() returns FAIL.
- rettv->v_type = VAR_UNKNOWN;
-
- // Make a copy of the name, if it comes from a funcref variable it could
- // be changed or deleted in the called function.
- name = vim_strnsave(funcname, len);
-
- fname = fname_trans_sid(name, fname_buf, &tofree, &error);
-
- *doesrange = false;
-
- if (partial != NULL) {
- // When the function has a partial with a dict and there is a dict
- // argument, use the dict argument. That is backwards compatible.
- // When the dict was bound explicitly use the one from the partial.
- if (partial->pt_dict != NULL
- && (selfdict_in == NULL || !partial->pt_auto)) {
- selfdict = partial->pt_dict;
- }
- if (error == ERROR_NONE && partial->pt_argc > 0) {
- for (argv_clear = 0; argv_clear < partial->pt_argc; argv_clear++) {
- tv_copy(&partial->pt_argv[argv_clear], &argv[argv_clear]);
- }
- for (int i = 0; i < argcount_in; i++) {
- argv[i + argv_clear] = argvars_in[i];
- }
- argvars = argv;
- argcount = partial->pt_argc + argcount_in;
- }
- }
-
- if (error == ERROR_NONE && evaluate) {
- char_u *rfname = fname;
-
- /* Ignore "g:" before a function name. */
- if (fname[0] == 'g' && fname[1] == ':') {
- rfname = fname + 2;
- }
-
- rettv->v_type = VAR_NUMBER; /* default rettv is number zero */
- rettv->vval.v_number = 0;
- error = ERROR_UNKNOWN;
-
- if (!builtin_function((const char *)rfname, -1)) {
- // User defined function.
- if (partial != NULL && partial->pt_func != NULL) {
- fp = partial->pt_func;
- } else {
- fp = find_func(rfname);
- }
-
- // Trigger FuncUndefined event, may load the function.
- if (fp == NULL
- && apply_autocmds(EVENT_FUNCUNDEFINED, rfname, rfname, TRUE, NULL)
- && !aborting()) {
- /* executed an autocommand, search for the function again */
- fp = find_func(rfname);
- }
- // Try loading a package.
- if (fp == NULL && script_autoload((const char *)rfname, STRLEN(rfname),
- true) && !aborting()) {
- // Loaded a package, search for the function again.
- fp = find_func(rfname);
- }
-
- if (fp != NULL && (fp->uf_flags & FC_DELETED)) {
- error = ERROR_DELETED;
- } else if (fp != NULL) {
- if (argv_func != NULL) {
- argcount = argv_func(argcount, argvars, fp->uf_args.ga_len);
- }
- if (fp->uf_flags & FC_RANGE) {
- *doesrange = true;
- }
- if (argcount < fp->uf_args.ga_len) {
- error = ERROR_TOOFEW;
- } else if (!fp->uf_varargs && argcount > fp->uf_args.ga_len) {
- error = ERROR_TOOMANY;
- } else if ((fp->uf_flags & FC_DICT) && selfdict == NULL) {
- error = ERROR_DICT;
- } else {
- // Call the user function.
- call_user_func(fp, argcount, argvars, rettv, firstline, lastline,
- (fp->uf_flags & FC_DICT) ? selfdict : NULL);
- error = ERROR_NONE;
- }
- }
- } else {
- // Find the function name in the table, call its implementation.
- const VimLFuncDef *const fdef = find_internal_func((const char *)fname);
- if (fdef != NULL) {
- if (argcount < fdef->min_argc) {
- error = ERROR_TOOFEW;
- } else if (argcount > fdef->max_argc) {
- error = ERROR_TOOMANY;
- } else {
- argvars[argcount].v_type = VAR_UNKNOWN;
- fdef->func(argvars, rettv, fdef->data);
- error = ERROR_NONE;
- }
- }
- }
- /*
- * The function call (or "FuncUndefined" autocommand sequence) might
- * have been aborted by an error, an interrupt, or an explicitly thrown
- * exception that has not been caught so far. This situation can be
- * tested for by calling aborting(). For an error in an internal
- * function or for the "E132" error in call_user_func(), however, the
- * throw point at which the "force_abort" flag (temporarily reset by
- * emsg()) is normally updated has not been reached yet. We need to
- * update that flag first to make aborting() reliable.
- */
- update_force_abort();
- }
- if (error == ERROR_NONE)
- ret = OK;
-
- /*
- * Report an error unless the argument evaluation or function call has been
- * cancelled due to an aborting error, an interrupt, or an exception.
- */
- if (!aborting()) {
- switch (error) {
- case ERROR_UNKNOWN:
- emsg_funcname(N_("E117: Unknown function: %s"), name);
- break;
- case ERROR_DELETED:
- emsg_funcname(N_("E933: Function was deleted: %s"), name);
- break;
- case ERROR_TOOMANY:
- emsg_funcname(e_toomanyarg, name);
- break;
- case ERROR_TOOFEW:
- emsg_funcname(N_("E119: Not enough arguments for function: %s"),
- name);
- break;
- case ERROR_SCRIPT:
- emsg_funcname(N_("E120: Using <SID> not in a script context: %s"),
- name);
- break;
- case ERROR_DICT:
- emsg_funcname(N_("E725: Calling dict function without Dictionary: %s"),
- name);
- break;
- }
- }
-
- while (argv_clear > 0) {
- tv_clear(&argv[--argv_clear]);
- }
- xfree(tofree);
- xfree(name);
-
- return ret;
-}
-
-/// Give an error message with a function name. Handle <SNR> things.
-///
-/// @param ermsg must be passed without translation (use N_() instead of _()).
-/// @param name function name
-static void emsg_funcname(char *ermsg, char_u *name)
-{
- char_u *p;
-
- if (*name == K_SPECIAL) {
- p = concat_str((char_u *)"<SNR>", name + 3);
- } else {
- p = name;
- }
-
- EMSG2(_(ermsg), p);
-
- if (p != name) {
- xfree(p);
- }
-}
-
-/*
- * Return TRUE for a non-zero Number and a non-empty String.
- */
-static int non_zero_arg(typval_T *argvars)
-{
- return ((argvars[0].v_type == VAR_NUMBER
- && argvars[0].vval.v_number != 0)
- || (argvars[0].v_type == VAR_SPECIAL
- && argvars[0].vval.v_special == kSpecialVarTrue)
- || (argvars[0].v_type == VAR_STRING
- && argvars[0].vval.v_string != NULL
- && *argvars[0].vval.v_string != NUL));
-}
-
-/*********************************************
- * Implementation of the built-in functions
- */
-
-
-// Apply a floating point C function on a typval with one float_T.
-//
-// Some versions of glibc on i386 have an optimization that makes it harder to
-// call math functions indirectly from inside an inlined function, causing
-// compile-time errors. Avoid `inline` in that case. #3072
-static void float_op_wrapper(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- float_T f;
- float_T (*function)(float_T) = (float_T (*)(float_T))fptr;
-
- rettv->v_type = VAR_FLOAT;
- if (tv_get_float_chk(argvars, &f)) {
- rettv->vval.v_float = function(f);
- } else {
- rettv->vval.v_float = 0.0;
- }
-}
-
-static void api_wrapper(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- if (check_restricted() || check_secure()) {
- return;
- }
-
- ApiDispatchWrapper fn = (ApiDispatchWrapper)fptr;
-
- Array args = ARRAY_DICT_INIT;
-
- for (typval_T *tv = argvars; tv->v_type != VAR_UNKNOWN; tv++) {
- ADD(args, vim_to_object(tv));
- }
-
- Error err = ERROR_INIT;
- Object result = fn(VIML_INTERNAL_CALL, args, &err);
-
- if (ERROR_SET(&err)) {
- emsgf_multiline((const char *)e_api_error, err.msg);
- goto end;
- }
-
- if (!object_to_vim(result, rettv, &err)) {
- EMSG2(_("Error converting the call result: %s"), err.msg);
- }
-
-end:
- api_free_array(args);
- api_free_object(result);
- api_clear_error(&err);
-}
-
-/*
- * "abs(expr)" function
- */
-static void f_abs(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- if (argvars[0].v_type == VAR_FLOAT) {
- float_op_wrapper(argvars, rettv, (FunPtr)&fabs);
- } else {
- varnumber_T n;
- bool error = false;
-
- n = tv_get_number_chk(&argvars[0], &error);
- if (error) {
- rettv->vval.v_number = -1;
- } else if (n > 0) {
- rettv->vval.v_number = n;
- } else {
- rettv->vval.v_number = -n;
- }
- }
-}
-
-/*
- * "add(list, item)" function
- */
-static void f_add(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = 1; // Default: failed.
- if (argvars[0].v_type == VAR_LIST) {
- list_T *const l = argvars[0].vval.v_list;
- if (!tv_check_lock(tv_list_locked(l), N_("add() argument"), TV_TRANSLATE)) {
- tv_list_append_tv(l, &argvars[1]);
- tv_copy(&argvars[0], rettv);
- }
- } else {
- EMSG(_(e_listreq));
- }
-}
-
-/*
- * "and(expr, expr)" function
- */
-static void f_and(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = tv_get_number_chk(&argvars[0], NULL)
- & tv_get_number_chk(&argvars[1], NULL);
-}
-
-
-/// "api_info()" function
-static void f_api_info(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- Dictionary metadata = api_metadata();
- (void)object_to_vim(DICTIONARY_OBJ(metadata), rettv, NULL);
- api_free_dictionary(metadata);
-}
-
-// "append(lnum, string/list)" function
-static void f_append(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const linenr_T lnum = tv_get_lnum(&argvars[0]);
-
- set_buffer_lines(curbuf, lnum, true, &argvars[1], rettv);
-}
-
-// "appendbufline(buf, lnum, string/list)" function
-static void f_appendbufline(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- buf_T *const buf = tv_get_buf(&argvars[0], false);
- if (buf == NULL) {
- rettv->vval.v_number = 1; // FAIL
- } else {
- const linenr_T lnum = tv_get_lnum_buf(&argvars[1], buf);
- set_buffer_lines(buf, lnum, true, &argvars[2], rettv);
- }
-}
-
-static void f_argc(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- if (argvars[0].v_type == VAR_UNKNOWN) {
- // use the current window
- rettv->vval.v_number = ARGCOUNT;
- } else if (argvars[0].v_type == VAR_NUMBER
- && tv_get_number(&argvars[0]) == -1) {
- // use the global argument list
- rettv->vval.v_number = GARGCOUNT;
- } else {
- // use the argument list of the specified window
- win_T *wp = find_win_by_nr_or_id(&argvars[0]);
- if (wp != NULL) {
- rettv->vval.v_number = WARGCOUNT(wp);
- } else {
- rettv->vval.v_number = -1;
- }
- }
-}
-
-/*
- * "argidx()" function
- */
-static void f_argidx(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = curwin->w_arg_idx;
-}
-
-/// "arglistid" function
-static void f_arglistid(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = -1;
- win_T *wp = find_tabwin(&argvars[0], &argvars[1]);
- if (wp != NULL) {
- rettv->vval.v_number = wp->w_alist->id;
- }
-}
-
/// Get the argument list for a given window
-static void get_arglist_as_rettv(aentry_T *arglist, int argcount,
- typval_T *rettv)
+void get_arglist_as_rettv(aentry_T *arglist, int argcount,
+ typval_T *rettv)
{
tv_list_alloc_ret(rettv, argcount);
if (arglist != NULL) {
@@ -6798,46 +5531,8 @@ static void get_arglist_as_rettv(aentry_T *arglist, int argcount,
}
}
-/*
- * "argv(nr)" function
- */
-static void f_argv(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- aentry_T *arglist = NULL;
- int argcount = -1;
-
- if (argvars[0].v_type != VAR_UNKNOWN) {
- if (argvars[1].v_type == VAR_UNKNOWN) {
- arglist = ARGLIST;
- argcount = ARGCOUNT;
- } else if (argvars[1].v_type == VAR_NUMBER
- && tv_get_number(&argvars[1]) == -1) {
- arglist = GARGLIST;
- argcount = GARGCOUNT;
- } else {
- win_T *wp = find_win_by_nr_or_id(&argvars[1]);
- if (wp != NULL) {
- // Use the argument list of the specified window
- arglist = WARGLIST(wp);
- argcount = WARGCOUNT(wp);
- }
- }
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = NULL;
- int idx = tv_get_number_chk(&argvars[0], NULL);
- if (arglist != NULL && idx >= 0 && idx < argcount) {
- rettv->vval.v_string = (char_u *)xstrdup(
- (const char *)alist_name(&arglist[idx]));
- } else if (idx == -1) {
- get_arglist_as_rettv(arglist, argcount, rettv);
- }
- } else {
- get_arglist_as_rettv(ARGLIST, ARGCOUNT, rettv);
- }
-}
-
// Prepare "gap" for an assert error and add the sourcing position.
-static void prepare_assert_error(garray_T *gap)
+void prepare_assert_error(garray_T *gap)
{
char buf[NUMBUFLEN];
@@ -6857,19 +5552,18 @@ static void prepare_assert_error(garray_T *gap)
}
}
-// Append "str" to "gap", escaping unprintable characters.
+// Append "p[clen]" to "gap", escaping unprintable characters.
// Changes NL to \n, CR to \r, etc.
-static void ga_concat_esc(garray_T *gap, char_u *str)
+static void ga_concat_esc(garray_T *gap, const char_u *p, int clen)
+ FUNC_ATTR_NONNULL_ALL
{
- char_u *p;
char_u buf[NUMBUFLEN];
- if (str == NULL) {
- ga_concat(gap, (char_u *)"NULL");
- return;
- }
-
- for (p = str; *p != NUL; p++) {
+ if (clen > 1) {
+ memmove(buf, p, clen);
+ buf[clen] = NUL;
+ ga_concat(gap, buf);
+ } else {
switch (*p) {
case BS: ga_concat(gap, (char_u *)"\\b"); break;
case ESC: ga_concat(gap, (char_u *)"\\e"); break;
@@ -6890,10 +5584,45 @@ static void ga_concat_esc(garray_T *gap, char_u *str)
}
}
+// Append "str" to "gap", escaping unprintable characters.
+// Changes NL to \n, CR to \r, etc.
+static void ga_concat_shorten_esc(garray_T *gap, const char_u *str)
+ FUNC_ATTR_NONNULL_ARG(1)
+{
+ char_u buf[NUMBUFLEN];
+
+ if (str == NULL) {
+ ga_concat(gap, (char_u *)"NULL");
+ return;
+ }
+
+ for (const char_u *p = str; *p != NUL; p++) {
+ int same_len = 1;
+ const char_u *s = p;
+ const int c = mb_ptr2char_adv(&s);
+ const int clen = s - p;
+ while (*s != NUL && c == utf_ptr2char(s)) {
+ same_len++;
+ s += clen;
+ }
+ if (same_len > 20) {
+ ga_concat(gap, (char_u *)"\\[");
+ ga_concat_esc(gap, p, clen);
+ ga_concat(gap, (char_u *)" occurs ");
+ vim_snprintf((char *)buf, NUMBUFLEN, "%d", same_len);
+ ga_concat(gap, buf);
+ ga_concat(gap, (char_u *)" times]");
+ p = s - 1;
+ } else {
+ ga_concat_esc(gap, p, clen);
+ }
+ }
+}
+
// Fill "gap" with information about an assert error.
-static void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv,
- char_u *exp_str, typval_T *exp_tv,
- typval_T *got_tv, assert_type_T atype)
+void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv,
+ char_u *exp_str, typval_T *exp_tv,
+ typval_T *got_tv, assert_type_T atype)
{
char_u *tofree;
@@ -6914,10 +5643,10 @@ static void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv,
if (exp_str == NULL) {
tofree = (char_u *)encode_tv2string(exp_tv, NULL);
- ga_concat_esc(gap, tofree);
+ ga_concat_shorten_esc(gap, tofree);
xfree(tofree);
} else {
- ga_concat_esc(gap, exp_str);
+ ga_concat_shorten_esc(gap, exp_str);
}
if (atype != ASSERT_NOTEQUAL) {
@@ -6929,13 +5658,13 @@ static void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv,
ga_concat(gap, (char_u *)" but got ");
}
tofree = (char_u *)encode_tv2string(got_tv, NULL);
- ga_concat_esc(gap, tofree);
+ ga_concat_shorten_esc(gap, tofree);
xfree(tofree);
}
}
// Add an assert error to v:errors.
-static void assert_error(garray_T *gap)
+void assert_error(garray_T *gap)
{
struct vimvar *vp = &vimvars[VV_ERRORS];
@@ -6947,7 +5676,8 @@ static void assert_error(garray_T *gap)
(const char *)gap->ga_data, (ptrdiff_t)gap->ga_len);
}
-static void assert_equal_common(typval_T *argvars, assert_type_T atype)
+int assert_equal_common(typval_T *argvars, assert_type_T atype)
+ FUNC_ATTR_NONNULL_ALL
{
garray_T ga;
@@ -6958,55 +5688,156 @@ static void assert_equal_common(typval_T *argvars, assert_type_T atype)
&argvars[0], &argvars[1], atype);
assert_error(&ga);
ga_clear(&ga);
+ return 1;
}
+ return 0;
}
-static void f_assert_beeps(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+int assert_equalfile(typval_T *argvars)
+ FUNC_ATTR_NONNULL_ALL
{
- const char *const cmd = tv_get_string_chk(&argvars[0]);
+ char buf1[NUMBUFLEN];
+ char buf2[NUMBUFLEN];
+ 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;
- called_vim_beep = false;
- suppress_errthrow = true;
- emsg_silent = false;
- do_cmdline_cmd(cmd);
- if (!called_vim_beep) {
+ if (fname1 == NULL || fname2 == NULL) {
+ return 0;
+ }
+
+ IObuff[0] = NUL;
+ FILE *const fd1 = os_fopen(fname1, READBIN);
+ char line1[200];
+ char line2[200];
+ ptrdiff_t lineidx = 0;
+ if (fd1 == NULL) {
+ snprintf((char *)IObuff, IOSIZE, (char *)e_notread, fname1);
+ } else {
+ FILE *const fd2 = os_fopen(fname2, READBIN);
+ if (fd2 == NULL) {
+ fclose(fd1);
+ snprintf((char *)IObuff, IOSIZE, (char *)e_notread, fname2);
+ } else {
+ int64_t linecount = 1;
+ for (int64_t count = 0; ; count++) {
+ const int c1 = fgetc(fd1);
+ const int c2 = fgetc(fd2);
+ if (c1 == EOF) {
+ if (c2 != EOF) {
+ STRCPY(IObuff, "first file is shorter");
+ }
+ break;
+ } else if (c2 == EOF) {
+ STRCPY(IObuff, "second file is shorter");
+ break;
+ } else {
+ line1[lineidx] = c1;
+ line2[lineidx] = c2;
+ lineidx++;
+ if (c1 != c2) {
+ snprintf((char *)IObuff, IOSIZE,
+ "difference at byte %" PRId64 ", line %" PRId64,
+ count, linecount);
+ break;
+ }
+ }
+ if (c1 == NL) {
+ linecount++;
+ lineidx = 0;
+ } else if (lineidx + 2 == (ptrdiff_t)sizeof(line1)) {
+ memmove(line1, line1 + 100, lineidx - 100);
+ memmove(line2, line2 + 100, lineidx - 100);
+ lineidx -= 100;
+ }
+ }
+ fclose(fd1);
+ fclose(fd2);
+ }
+ }
+ if (IObuff[0] != NUL) {
prepare_assert_error(&ga);
- ga_concat(&ga, (const char_u *)"command did not beep: ");
- ga_concat(&ga, (const char_u *)cmd);
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ char *const tofree = encode_tv2echo(&argvars[2], NULL);
+ ga_concat(&ga, (char_u *)tofree);
+ xfree(tofree);
+ ga_concat(&ga, (char_u *)": ");
+ }
+ ga_concat(&ga, IObuff);
+ if (lineidx > 0) {
+ line1[lineidx] = NUL;
+ line2[lineidx] = NUL;
+ ga_concat(&ga, (char_u *)" after \"");
+ ga_concat(&ga, (char_u *)line1);
+ if (STRCMP(line1, line2) != 0) {
+ ga_concat(&ga, (char_u *)"\" vs \"");
+ ga_concat(&ga, (char_u *)line2);
+ }
+ ga_concat(&ga, (char_u *)"\"");
+ }
assert_error(&ga);
ga_clear(&ga);
+ return 1;
}
-
- suppress_errthrow = false;
- emsg_on_display = false;
+ return 0;
}
-// "assert_equal(expected, actual[, msg])" function
-static void f_assert_equal(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+int assert_inrange(typval_T *argvars)
+ FUNC_ATTR_NONNULL_ALL
{
- assert_equal_common(argvars, ASSERT_EQUAL);
-}
+ bool error = false;
+ const varnumber_T lower = tv_get_number_chk(&argvars[0], &error);
+ const varnumber_T upper = tv_get_number_chk(&argvars[1], &error);
+ const varnumber_T actual = tv_get_number_chk(&argvars[2], &error);
-// "assert_notequal(expected, actual[, msg])" function
-static void f_assert_notequal(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- assert_equal_common(argvars, ASSERT_NOTEQUAL);
+ if (error) {
+ return 0;
+ }
+ if (actual < lower || actual > upper) {
+ garray_T ga;
+ prepare_assert_error(&ga);
+
+ char msg[55];
+ vim_snprintf(msg, sizeof(msg),
+ "range %" PRIdVARNUMBER " - %" PRIdVARNUMBER ",",
+ lower, upper);
+ fill_assert_error(&ga, &argvars[3], (char_u *)msg, NULL, &argvars[2],
+ ASSERT_INRANGE);
+ assert_error(&ga);
+ ga_clear(&ga);
+ return 1;
+ }
+ return 0;
}
-/// "assert_report(msg)
-static void f_assert_report(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+// Common for assert_true() and assert_false().
+int assert_bool(typval_T *argvars, bool is_true)
+ FUNC_ATTR_NONNULL_ALL
{
- garray_T ga;
+ bool error = false;
+ garray_T ga;
+ if ((argvars[0].v_type != VAR_NUMBER
+ || (tv_get_number_chk(&argvars[0], &error) == 0) == is_true
+ || error)
+ && (argvars[0].v_type != VAR_BOOL
+ || (argvars[0].vval.v_bool
+ != (BoolVarValue)(is_true
+ ? kBoolVarTrue
+ : kBoolVarFalse)))) {
prepare_assert_error(&ga);
- ga_concat(&ga, (const char_u *)tv_get_string(&argvars[0]));
+ fill_assert_error(&ga, &argvars[1],
+ (char_u *)(is_true ? "True" : "False"),
+ NULL, &argvars[0], ASSERT_OTHER);
assert_error(&ga);
ga_clear(&ga);
+ return 1;
+ }
+ return 0;
}
-/// "assert_exception(string[, msg])" function
-static void f_assert_exception(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+int assert_exception(typval_T *argvars)
+ FUNC_ATTR_NONNULL_ALL
{
garray_T ga;
@@ -7016,6 +5847,7 @@ static void f_assert_exception(typval_T *argvars, typval_T *rettv, FunPtr fptr)
ga_concat(&ga, (char_u *)"v:exception is not set");
assert_error(&ga);
ga_clear(&ga);
+ return 1;
} else if (error != NULL
&& strstr((char *)vimvars[VV_EXCEPTION].vv_str, error) == NULL) {
prepare_assert_error(&ga);
@@ -7023,14 +5855,17 @@ static void f_assert_exception(typval_T *argvars, typval_T *rettv, FunPtr fptr)
&vimvars[VV_EXCEPTION].vv_tv, ASSERT_OTHER);
assert_error(&ga);
ga_clear(&ga);
+ return 1;
}
+ return 0;
}
-/// "assert_fails(cmd [, error])" function
-static void f_assert_fails(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+int assert_fails(typval_T *argvars)
+ FUNC_ATTR_NONNULL_ALL
{
const char *const cmd = tv_get_string_chk(&argvars[0]);
garray_T ga;
+ int ret = 0;
int save_trylevel = trylevel;
// trylevel must be zero for a ":throw" command to be considered failed
@@ -7043,9 +5878,17 @@ static void f_assert_fails(typval_T *argvars, typval_T *rettv, FunPtr fptr)
if (!called_emsg) {
prepare_assert_error(&ga);
ga_concat(&ga, (const char_u *)"command did not fail: ");
- ga_concat(&ga, (const char_u *)cmd);
+ if (argvars[1].v_type != VAR_UNKNOWN
+ && argvars[2].v_type != VAR_UNKNOWN) {
+ char *const tofree = encode_tv2echo(&argvars[2], NULL);
+ ga_concat(&ga, (char_u *)tofree);
+ xfree(tofree);
+ } else {
+ ga_concat(&ga, (const char_u *)cmd);
+ }
assert_error(&ga);
ga_clear(&ga);
+ ret = 1;
} else if (argvars[1].v_type != VAR_UNKNOWN) {
char buf[NUMBUFLEN];
const char *const error = tv_get_string_buf_chk(&argvars[1], buf);
@@ -7057,6 +5900,7 @@ static void f_assert_fails(typval_T *argvars, typval_T *rettv, FunPtr fptr)
&vimvars[VV_ERRMSG].vv_tv, ASSERT_OTHER);
assert_error(&ga);
ga_clear(&ga);
+ ret = 1;
}
}
@@ -7066,63 +5910,11 @@ static void f_assert_fails(typval_T *argvars, typval_T *rettv, FunPtr fptr)
emsg_silent = false;
emsg_on_display = false;
set_vim_var_string(VV_ERRMSG, NULL, 0);
+ return ret;
}
-void assert_inrange(typval_T *argvars)
-{
- bool error = false;
- const varnumber_T lower = tv_get_number_chk(&argvars[0], &error);
- const varnumber_T upper = tv_get_number_chk(&argvars[1], &error);
- const varnumber_T actual = tv_get_number_chk(&argvars[2], &error);
-
- if (error) {
- return;
- }
- if (actual < lower || actual > upper) {
- garray_T ga;
- prepare_assert_error(&ga);
-
- char msg[55];
- vim_snprintf(msg, sizeof(msg),
- "range %" PRIdVARNUMBER " - %" PRIdVARNUMBER ",",
- lower, upper);
- fill_assert_error(&ga, &argvars[3], (char_u *)msg, NULL, &argvars[2],
- ASSERT_INRANGE);
- assert_error(&ga);
- ga_clear(&ga);
- }
-}
-
-// Common for assert_true() and assert_false().
-static void assert_bool(typval_T *argvars, bool is_true)
-{
- bool error = false;
- garray_T ga;
-
- if ((argvars[0].v_type != VAR_NUMBER
- || (tv_get_number_chk(&argvars[0], &error) == 0) == is_true
- || error)
- && (argvars[0].v_type != VAR_SPECIAL
- || (argvars[0].vval.v_special
- != (SpecialVarValue) (is_true
- ? kSpecialVarTrue
- : kSpecialVarFalse)))) {
- prepare_assert_error(&ga);
- fill_assert_error(&ga, &argvars[1],
- (char_u *)(is_true ? "True" : "False"),
- NULL, &argvars[0], ASSERT_OTHER);
- assert_error(&ga);
- ga_clear(&ga);
- }
-}
-
-// "assert_false(actual[, msg])" function
-static void f_assert_false(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- assert_bool(argvars, false);
-}
-
-static void assert_match_common(typval_T *argvars, assert_type_T atype)
+int assert_match_common(typval_T *argvars, assert_type_T atype)
+ FUNC_ATTR_NONNULL_ALL
{
char buf1[NUMBUFLEN];
char buf2[NUMBUFLEN];
@@ -7138,1517 +5930,9 @@ static void assert_match_common(typval_T *argvars, assert_type_T atype)
fill_assert_error(&ga, &argvars[2], NULL, &argvars[0], &argvars[1], atype);
assert_error(&ga);
ga_clear(&ga);
+ return 1;
}
-}
-
-/// "assert_inrange(lower, upper[, msg])" function
-static void f_assert_inrange(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- assert_inrange(argvars);
-}
-
-/// "assert_match(pattern, actual[, msg])" function
-static void f_assert_match(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- assert_match_common(argvars, ASSERT_MATCH);
-}
-
-/// "assert_notmatch(pattern, actual[, msg])" function
-static void f_assert_notmatch(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- assert_match_common(argvars, ASSERT_NOTMATCH);
-}
-
-// "assert_true(actual[, msg])" function
-static void f_assert_true(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- assert_bool(argvars, true);
-}
-
-/*
- * "atan2()" function
- */
-static void f_atan2(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- float_T fx;
- float_T fy;
-
- rettv->v_type = VAR_FLOAT;
- if (tv_get_float_chk(argvars, &fx) && tv_get_float_chk(&argvars[1], &fy)) {
- rettv->vval.v_float = atan2(fx, fy);
- } else {
- rettv->vval.v_float = 0.0;
- }
-}
-
-/*
- * "browse(save, title, initdir, default)" function
- */
-static void f_browse(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_string = NULL;
- rettv->v_type = VAR_STRING;
-}
-
-/*
- * "browsedir(title, initdir)" function
- */
-static void f_browsedir(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- f_browse(argvars, rettv, NULL);
-}
-
-
-/*
- * Find a buffer by number or exact name.
- */
-static buf_T *find_buffer(typval_T *avar)
-{
- buf_T *buf = NULL;
-
- if (avar->v_type == VAR_NUMBER)
- buf = buflist_findnr((int)avar->vval.v_number);
- else if (avar->v_type == VAR_STRING && avar->vval.v_string != NULL) {
- buf = buflist_findname_exp(avar->vval.v_string);
- if (buf == NULL) {
- /* No full path name match, try a match with a URL or a "nofile"
- * buffer, these don't use the full path. */
- FOR_ALL_BUFFERS(bp) {
- if (bp->b_fname != NULL
- && (path_with_url((char *)bp->b_fname)
- || bt_nofile(bp)
- )
- && STRCMP(bp->b_fname, avar->vval.v_string) == 0) {
- buf = bp;
- break;
- }
- }
- }
- }
- return buf;
-}
-
-// "bufadd(expr)" function
-static void f_bufadd(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- char_u *name = (char_u *)tv_get_string(&argvars[0]);
-
- rettv->vval.v_number = buflist_add(*name == NUL ? NULL : name, 0);
-}
-
-/*
- * "bufexists(expr)" function
- */
-static void f_bufexists(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = (find_buffer(&argvars[0]) != NULL);
-}
-
-/*
- * "buflisted(expr)" function
- */
-static void f_buflisted(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- buf_T *buf;
-
- buf = find_buffer(&argvars[0]);
- rettv->vval.v_number = (buf != NULL && buf->b_p_bl);
-}
-
-// "bufload(expr)" function
-static void f_bufload(typval_T *argvars, typval_T *unused, FunPtr fptr)
-{
- buf_T *buf = get_buf_arg(&argvars[0]);
-
- if (buf != NULL && buf->b_ml.ml_mfp == NULL) {
- aco_save_T aco;
-
- aucmd_prepbuf(&aco, buf);
- swap_exists_action = SEA_NONE;
- open_buffer(false, NULL, 0);
- aucmd_restbuf(&aco);
- }
-}
-
-/*
- * "bufloaded(expr)" function
- */
-static void f_bufloaded(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- buf_T *buf;
-
- buf = find_buffer(&argvars[0]);
- rettv->vval.v_number = (buf != NULL && buf->b_ml.ml_mfp != NULL);
-}
-
-
-/*
- * Get buffer by number or pattern.
- */
-static buf_T *tv_get_buf(typval_T *tv, int curtab_only)
-{
- char_u *name = tv->vval.v_string;
- int save_magic;
- char_u *save_cpo;
- buf_T *buf;
-
- if (tv->v_type == VAR_NUMBER)
- return buflist_findnr((int)tv->vval.v_number);
- if (tv->v_type != VAR_STRING)
- return NULL;
- if (name == NULL || *name == NUL)
- return curbuf;
- if (name[0] == '$' && name[1] == NUL)
- return lastbuf;
-
- // Ignore 'magic' and 'cpoptions' here to make scripts portable
- save_magic = p_magic;
- p_magic = TRUE;
- save_cpo = p_cpo;
- p_cpo = (char_u *)"";
-
- buf = buflist_findnr(buflist_findpat(name, name + STRLEN(name),
- TRUE, FALSE, curtab_only));
-
- p_magic = save_magic;
- p_cpo = save_cpo;
-
- // If not found, try expanding the name, like done for bufexists().
- if (buf == NULL) {
- buf = find_buffer(tv);
- }
-
- return buf;
-}
-
-/// Get the buffer from "arg" and give an error and return NULL if it is not
-/// valid.
-static buf_T * get_buf_arg(typval_T *arg)
-{
- buf_T *buf;
-
- emsg_off++;
- buf = tv_get_buf(arg, false);
- emsg_off--;
- if (buf == NULL) {
- EMSG2(_("E158: Invalid buffer name: %s"), tv_get_string(arg));
- }
- return buf;
-}
-
-/*
- * "bufname(expr)" function
- */
-static void f_bufname(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const buf_T *buf;
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = NULL;
- if (argvars[0].v_type == VAR_UNKNOWN) {
- buf = curbuf;
- } else {
- if (!tv_check_str_or_nr(&argvars[0])) {
- return;
- }
- emsg_off++;
- buf = tv_get_buf(&argvars[0], false);
- emsg_off--;
- }
- if (buf != NULL && buf->b_fname != NULL) {
- rettv->vval.v_string = (char_u *)xstrdup((char *)buf->b_fname);
- }
-}
-
-/*
- * "bufnr(expr)" function
- */
-static void f_bufnr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const buf_T *buf;
- bool error = false;
-
- rettv->vval.v_number = -1;
-
- if (argvars[0].v_type == VAR_UNKNOWN) {
- buf = curbuf;
- } else {
- if (!tv_check_str_or_nr(&argvars[0])) {
- return;
- }
- emsg_off++;
- buf = tv_get_buf(&argvars[0], false);
- emsg_off--;
- }
-
- // If the buffer isn't found and the second argument is not zero create a
- // new buffer.
- const char *name;
- if (buf == NULL
- && argvars[1].v_type != VAR_UNKNOWN
- && tv_get_number_chk(&argvars[1], &error) != 0
- && !error
- && (name = tv_get_string_chk(&argvars[0])) != NULL) {
- buf = buflist_new((char_u *)name, NULL, 1, 0);
- }
-
- if (buf != NULL) {
- rettv->vval.v_number = buf->b_fnum;
- }
-}
-
-static void buf_win_common(typval_T *argvars, typval_T *rettv, bool get_nr)
-{
- if (!tv_check_str_or_nr(&argvars[0])) {
- rettv->vval.v_number = -1;
- return;
- }
-
- emsg_off++;
- buf_T *buf = tv_get_buf(&argvars[0], true);
- if (buf == NULL) { // no need to search if buffer was not found
- rettv->vval.v_number = -1;
- goto end;
- }
-
- int winnr = 0;
- int winid;
- bool found_buf = false;
- FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
- winnr++;
- if (wp->w_buffer == buf) {
- found_buf = true;
- winid = wp->handle;
- break;
- }
- }
- rettv->vval.v_number = (found_buf ? (get_nr ? winnr : winid) : -1);
-end:
- emsg_off--;
-}
-
-/// "bufwinid(nr)" function
-static void f_bufwinid(typval_T *argvars, typval_T *rettv, FunPtr fptr) {
- buf_win_common(argvars, rettv, false);
-}
-
-/// "bufwinnr(nr)" function
-static void f_bufwinnr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- buf_win_common(argvars, rettv, true);
-}
-
-/*
- * "byte2line(byte)" function
- */
-static void f_byte2line(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- long boff = tv_get_number(&argvars[0]) - 1;
- if (boff < 0) {
- rettv->vval.v_number = -1;
- } else {
- rettv->vval.v_number = (varnumber_T)ml_find_line_or_offset(curbuf, 0,
- &boff, false);
- }
-}
-
-static void byteidx(typval_T *argvars, typval_T *rettv, int comp)
-{
- const char *const str = tv_get_string_chk(&argvars[0]);
- varnumber_T idx = tv_get_number_chk(&argvars[1], NULL);
- rettv->vval.v_number = -1;
- if (str == NULL || idx < 0) {
- return;
- }
-
- const char *t = str;
- for (; idx > 0; idx--) {
- if (*t == NUL) { // EOL reached.
- return;
- }
- if (enc_utf8 && comp) {
- t += utf_ptr2len((const char_u *)t);
- } else {
- t += (*mb_ptr2len)((const char_u *)t);
- }
- }
- rettv->vval.v_number = (varnumber_T)(t - str);
-}
-
-/*
- * "byteidx()" function
- */
-static void f_byteidx(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- byteidx(argvars, rettv, FALSE);
-}
-
-/*
- * "byteidxcomp()" function
- */
-static void f_byteidxcomp(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- byteidx(argvars, rettv, TRUE);
-}
-
-int func_call(char_u *name, typval_T *args, partial_T *partial,
- dict_T *selfdict, typval_T *rettv)
-{
- typval_T argv[MAX_FUNC_ARGS + 1];
- int argc = 0;
- int dummy;
- int r = 0;
-
- TV_LIST_ITER(args->vval.v_list, item, {
- if (argc == MAX_FUNC_ARGS - (partial == NULL ? 0 : partial->pt_argc)) {
- EMSG(_("E699: Too many arguments"));
- goto func_call_skip_call;
- }
- // Make a copy of each argument. This is needed to be able to set
- // v_lock to VAR_FIXED in the copy without changing the original list.
- tv_copy(TV_LIST_ITEM_TV(item), &argv[argc++]);
- });
-
- r = call_func(name, (int)STRLEN(name), rettv, argc, argv, NULL,
- curwin->w_cursor.lnum, curwin->w_cursor.lnum,
- &dummy, true, partial, selfdict);
-
-func_call_skip_call:
- // Free the arguments.
- while (argc > 0) {
- tv_clear(&argv[--argc]);
- }
-
- return r;
-}
-
-/// "call(func, arglist [, dict])" function
-static void f_call(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- if (argvars[1].v_type != VAR_LIST) {
- EMSG(_(e_listreq));
- return;
- }
- if (argvars[1].vval.v_list == NULL) {
- return;
- }
-
- char_u *func;
- partial_T *partial = NULL;
- dict_T *selfdict = NULL;
- if (argvars[0].v_type == VAR_FUNC) {
- func = argvars[0].vval.v_string;
- } else if (argvars[0].v_type == VAR_PARTIAL) {
- partial = argvars[0].vval.v_partial;
- func = partial_name(partial);
- } else {
- func = (char_u *)tv_get_string(&argvars[0]);
- }
- if (*func == NUL) {
- return; // type error or empty name
- }
-
- if (argvars[2].v_type != VAR_UNKNOWN) {
- if (argvars[2].v_type != VAR_DICT) {
- EMSG(_(e_dictreq));
- return;
- }
- selfdict = argvars[2].vval.v_dict;
- }
-
- func_call(func, &argvars[1], partial, selfdict, rettv);
-}
-
-/*
- * "changenr()" function
- */
-static void f_changenr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = curbuf->b_u_seq_cur;
-}
-
-// "chanclose(id[, stream])" function
-static void f_chanclose(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->v_type = VAR_NUMBER;
- rettv->vval.v_number = 0;
-
- if (check_restricted() || check_secure()) {
- return;
- }
-
- if (argvars[0].v_type != VAR_NUMBER || (argvars[1].v_type != VAR_STRING
- && argvars[1].v_type != VAR_UNKNOWN)) {
- EMSG(_(e_invarg));
- return;
- }
-
- ChannelPart part = kChannelPartAll;
- if (argvars[1].v_type == VAR_STRING) {
- char *stream = (char *)argvars[1].vval.v_string;
- if (!strcmp(stream, "stdin")) {
- part = kChannelPartStdin;
- } else if (!strcmp(stream, "stdout")) {
- part = kChannelPartStdout;
- } else if (!strcmp(stream, "stderr")) {
- part = kChannelPartStderr;
- } else if (!strcmp(stream, "rpc")) {
- part = kChannelPartRpc;
- } else {
- EMSG2(_("Invalid channel stream \"%s\""), stream);
- return;
- }
- }
- const char *error;
- rettv->vval.v_number = channel_close(argvars[0].vval.v_number, part, &error);
- if (!rettv->vval.v_number) {
- EMSG(error);
- }
-}
-
-// "chansend(id, data)" function
-static void f_chansend(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->v_type = VAR_NUMBER;
- rettv->vval.v_number = 0;
-
- if (check_restricted() || check_secure()) {
- return;
- }
-
- if (argvars[0].v_type != VAR_NUMBER || argvars[1].v_type == VAR_UNKNOWN) {
- // First argument is the channel id and second is the data to write
- EMSG(_(e_invarg));
- return;
- }
-
- ptrdiff_t input_len = 0;
- char *input = save_tv_as_string(&argvars[1], &input_len, false);
- if (!input) {
- // Either the error has been handled by save_tv_as_string(),
- // or there is no input to send.
- return;
- }
- uint64_t id = argvars[0].vval.v_number;
- const char *error = NULL;
- rettv->vval.v_number = channel_send(id, input, input_len, &error);
- if (error) {
- EMSG(error);
- }
-}
-
-/*
- * "char2nr(string)" function
- */
-static void f_char2nr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- if (argvars[1].v_type != VAR_UNKNOWN) {
- if (!tv_check_num(&argvars[1])) {
- return;
- }
- }
-
- rettv->vval.v_number = utf_ptr2char(
- (const char_u *)tv_get_string(&argvars[0]));
-}
-
-/*
- * "cindent(lnum)" function
- */
-static void f_cindent(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- pos_T pos;
- linenr_T lnum;
-
- pos = curwin->w_cursor;
- lnum = tv_get_lnum(argvars);
- if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count) {
- curwin->w_cursor.lnum = lnum;
- rettv->vval.v_number = get_c_indent();
- curwin->w_cursor = pos;
- } else
- rettv->vval.v_number = -1;
-}
-
-/*
- * "clearmatches()" function
- */
-static void f_clearmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- clear_matches(curwin);
-}
-
-/*
- * "col(string)" function
- */
-static void f_col(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- colnr_T col = 0;
- pos_T *fp;
- int fnum = curbuf->b_fnum;
-
- fp = var2fpos(&argvars[0], FALSE, &fnum);
- if (fp != NULL && fnum == curbuf->b_fnum) {
- if (fp->col == MAXCOL) {
- /* '> can be MAXCOL, get the length of the line then */
- if (fp->lnum <= curbuf->b_ml.ml_line_count)
- col = (colnr_T)STRLEN(ml_get(fp->lnum)) + 1;
- else
- col = MAXCOL;
- } else {
- col = fp->col + 1;
- /* col(".") when the cursor is on the NUL at the end of the line
- * because of "coladd" can be seen as an extra column. */
- if (virtual_active() && fp == &curwin->w_cursor) {
- char_u *p = get_cursor_pos_ptr();
-
- if (curwin->w_cursor.coladd >= (colnr_T)chartabsize(p,
- curwin->w_virtcol - curwin->w_cursor.coladd)) {
- int l;
-
- if (*p != NUL && p[(l = (*mb_ptr2len)(p))] == NUL)
- col += l;
- }
- }
- }
- }
- rettv->vval.v_number = col;
-}
-
-/*
- * "complete()" function
- */
-static void f_complete(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- if ((State & INSERT) == 0) {
- EMSG(_("E785: complete() can only be used in Insert mode"));
- return;
- }
-
- /* Check for undo allowed here, because if something was already inserted
- * the line was already saved for undo and this check isn't done. */
- if (!undo_allowed())
- return;
-
- if (argvars[1].v_type != VAR_LIST) {
- EMSG(_(e_invarg));
- return;
- }
-
- const colnr_T startcol = tv_get_number_chk(&argvars[0], NULL);
- if (startcol <= 0) {
- return;
- }
-
- set_completion(startcol - 1, argvars[1].vval.v_list);
-}
-
-/*
- * "complete_add()" function
- */
-static void f_complete_add(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = ins_compl_add_tv(&argvars[0], 0);
-}
-
-/*
- * "complete_check()" function
- */
-static void f_complete_check(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- int saved = RedrawingDisabled;
-
- RedrawingDisabled = 0;
- ins_compl_check_keys(0, true);
- rettv->vval.v_number = compl_interrupted;
- RedrawingDisabled = saved;
-}
-
-// "complete_info()" function
-static void f_complete_info(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- tv_dict_alloc_ret(rettv);
-
- list_T *what_list = NULL;
-
- if (argvars[0].v_type != VAR_UNKNOWN) {
- if (argvars[0].v_type != VAR_LIST) {
- EMSG(_(e_listreq));
- return;
- }
- what_list = argvars[0].vval.v_list;
- }
- get_complete_info(what_list, rettv->vval.v_dict);
-}
-
-/*
- * "confirm(message, buttons[, default [, type]])" function
- */
-static void f_confirm(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- char buf[NUMBUFLEN];
- char buf2[NUMBUFLEN];
- const char *message;
- const char *buttons = NULL;
- int def = 1;
- int type = VIM_GENERIC;
- const char *typestr;
- bool error = false;
-
- message = tv_get_string_chk(&argvars[0]);
- if (message == NULL) {
- error = true;
- }
- if (argvars[1].v_type != VAR_UNKNOWN) {
- buttons = tv_get_string_buf_chk(&argvars[1], buf);
- if (buttons == NULL) {
- error = true;
- }
- if (argvars[2].v_type != VAR_UNKNOWN) {
- def = tv_get_number_chk(&argvars[2], &error);
- if (argvars[3].v_type != VAR_UNKNOWN) {
- typestr = tv_get_string_buf_chk(&argvars[3], buf2);
- if (typestr == NULL) {
- error = true;
- } else {
- switch (TOUPPER_ASC(*typestr)) {
- case 'E': type = VIM_ERROR; break;
- case 'Q': type = VIM_QUESTION; break;
- case 'I': type = VIM_INFO; break;
- case 'W': type = VIM_WARNING; break;
- case 'G': type = VIM_GENERIC; break;
- }
- }
- }
- }
- }
-
- if (buttons == NULL || *buttons == NUL) {
- buttons = _("&Ok");
- }
-
- if (!error) {
- rettv->vval.v_number = do_dialog(
- type, NULL, (char_u *)message, (char_u *)buttons, def, NULL, false);
- }
-}
-
-/*
- * "copy()" function
- */
-static void f_copy(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- var_item_copy(NULL, &argvars[0], rettv, false, 0);
-}
-
-/*
- * "count()" function
- */
-static void f_count(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- long n = 0;
- int ic = 0;
- bool error = false;
-
- if (argvars[2].v_type != VAR_UNKNOWN) {
- ic = tv_get_number_chk(&argvars[2], &error);
- }
-
- if (argvars[0].v_type == VAR_STRING) {
- const char_u *expr = (char_u *)tv_get_string_chk(&argvars[1]);
- const char_u *p = argvars[0].vval.v_string;
-
- if (!error && expr != NULL && *expr != NUL && p != NULL) {
- if (ic) {
- const size_t len = STRLEN(expr);
-
- while (*p != NUL) {
- if (mb_strnicmp(p, expr, len) == 0) {
- n++;
- p += len;
- } else {
- MB_PTR_ADV(p);
- }
- }
- } else {
- char_u *next;
- while ((next = (char_u *)strstr((char *)p, (char *)expr)) != NULL) {
- n++;
- p = next + STRLEN(expr);
- }
- }
- }
- } else if (argvars[0].v_type == VAR_LIST) {
- listitem_T *li;
- list_T *l;
- long idx;
-
- if ((l = argvars[0].vval.v_list) != NULL) {
- li = tv_list_first(l);
- if (argvars[2].v_type != VAR_UNKNOWN) {
- if (argvars[3].v_type != VAR_UNKNOWN) {
- idx = tv_get_number_chk(&argvars[3], &error);
- if (!error) {
- li = tv_list_find(l, idx);
- if (li == NULL) {
- EMSGN(_(e_listidx), idx);
- }
- }
- }
- if (error)
- li = NULL;
- }
-
- for (; li != NULL; li = TV_LIST_ITEM_NEXT(l, li)) {
- if (tv_equal(TV_LIST_ITEM_TV(li), &argvars[1], ic, false)) {
- n++;
- }
- }
- }
- } else if (argvars[0].v_type == VAR_DICT) {
- int todo;
- dict_T *d;
- hashitem_T *hi;
-
- if ((d = argvars[0].vval.v_dict) != NULL) {
- if (argvars[2].v_type != VAR_UNKNOWN) {
- if (argvars[3].v_type != VAR_UNKNOWN) {
- EMSG(_(e_invarg));
- }
- }
-
- todo = error ? 0 : (int)d->dv_hashtab.ht_used;
- for (hi = d->dv_hashtab.ht_array; todo > 0; ++hi) {
- if (!HASHITEM_EMPTY(hi)) {
- todo--;
- if (tv_equal(&TV_DICT_HI2DI(hi)->di_tv, &argvars[1], ic, false)) {
- n++;
- }
- }
- }
- }
- } else {
- EMSG2(_(e_listdictarg), "count()");
- }
- rettv->vval.v_number = n;
-}
-
-/*
- * "cscope_connection([{num} , {dbpath} [, {prepend}]])" function
- *
- * Checks the existence of a cscope connection.
- */
-static void f_cscope_connection(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- int num = 0;
- const char *dbpath = NULL;
- const char *prepend = NULL;
- char buf[NUMBUFLEN];
-
- if (argvars[0].v_type != VAR_UNKNOWN
- && argvars[1].v_type != VAR_UNKNOWN) {
- num = (int)tv_get_number(&argvars[0]);
- dbpath = tv_get_string(&argvars[1]);
- if (argvars[2].v_type != VAR_UNKNOWN) {
- prepend = tv_get_string_buf(&argvars[2], buf);
- }
- }
-
- rettv->vval.v_number = cs_connection(num, (char_u *)dbpath,
- (char_u *)prepend);
-}
-
-/// "ctxget([{index}])" function
-static void f_ctxget(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- size_t index = 0;
- if (argvars[0].v_type == VAR_NUMBER) {
- index = argvars[0].vval.v_number;
- } else if (argvars[0].v_type != VAR_UNKNOWN) {
- EMSG2(_(e_invarg2), "expected nothing or a Number as an argument");
- return;
- }
-
- Context *ctx = ctx_get(index);
- if (ctx == NULL) {
- EMSG3(_(e_invargNval), "index", "out of bounds");
- return;
- }
-
- Dictionary ctx_dict = ctx_to_dict(ctx);
- Error err = ERROR_INIT;
- object_to_vim(DICTIONARY_OBJ(ctx_dict), rettv, &err);
- api_free_dictionary(ctx_dict);
- api_clear_error(&err);
-}
-
-/// "ctxpop()" function
-static void f_ctxpop(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- if (!ctx_restore(NULL, kCtxAll)) {
- EMSG(_("Context stack is empty"));
- }
-}
-
-/// "ctxpush([{types}])" function
-static void f_ctxpush(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- int types = kCtxAll;
- if (argvars[0].v_type == VAR_LIST) {
- types = 0;
- TV_LIST_ITER(argvars[0].vval.v_list, li, {
- typval_T *tv_li = TV_LIST_ITEM_TV(li);
- if (tv_li->v_type == VAR_STRING) {
- if (strequal((char *)tv_li->vval.v_string, "regs")) {
- types |= kCtxRegs;
- } else if (strequal((char *)tv_li->vval.v_string, "jumps")) {
- types |= kCtxJumps;
- } else if (strequal((char *)tv_li->vval.v_string, "bufs")) {
- types |= kCtxBufs;
- } else if (strequal((char *)tv_li->vval.v_string, "gvars")) {
- types |= kCtxGVars;
- } else if (strequal((char *)tv_li->vval.v_string, "sfuncs")) {
- types |= kCtxSFuncs;
- } else if (strequal((char *)tv_li->vval.v_string, "funcs")) {
- types |= kCtxFuncs;
- }
- }
- });
- } else if (argvars[0].v_type != VAR_UNKNOWN) {
- EMSG2(_(e_invarg2), "expected nothing or a List as an argument");
- return;
- }
- ctx_save(NULL, types);
-}
-
-/// "ctxset({context}[, {index}])" function
-static void f_ctxset(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- if (argvars[0].v_type != VAR_DICT) {
- EMSG2(_(e_invarg2), "expected dictionary as first argument");
- return;
- }
-
- size_t index = 0;
- if (argvars[1].v_type == VAR_NUMBER) {
- index = argvars[1].vval.v_number;
- } else if (argvars[1].v_type != VAR_UNKNOWN) {
- EMSG2(_(e_invarg2), "expected nothing or a Number as second argument");
- return;
- }
-
- Context *ctx = ctx_get(index);
- if (ctx == NULL) {
- EMSG3(_(e_invargNval), "index", "out of bounds");
- return;
- }
-
- int save_did_emsg = did_emsg;
- did_emsg = false;
-
- Dictionary dict = vim_to_object(&argvars[0]).data.dictionary;
- Context tmp = CONTEXT_INIT;
- ctx_from_dict(dict, &tmp);
-
- if (did_emsg) {
- ctx_free(&tmp);
- } else {
- ctx_free(ctx);
- *ctx = tmp;
- }
-
- api_free_dictionary(dict);
- did_emsg = save_did_emsg;
-}
-
-/// "ctxsize()" function
-static void f_ctxsize(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->v_type = VAR_NUMBER;
- rettv->vval.v_number = ctx_size();
-}
-
-/// "cursor(lnum, col)" function, or
-/// "cursor(list)"
-///
-/// Moves the cursor to the specified line and column.
-///
-/// @returns 0 when the position could be set, -1 otherwise.
-static void f_cursor(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- long line, col;
- long coladd = 0;
- bool set_curswant = true;
-
- rettv->vval.v_number = -1;
- if (argvars[1].v_type == VAR_UNKNOWN) {
- pos_T pos;
- colnr_T curswant = -1;
-
- if (list2fpos(argvars, &pos, NULL, &curswant) == FAIL) {
- EMSG(_(e_invarg));
- return;
- }
-
- line = pos.lnum;
- col = pos.col;
- coladd = pos.coladd;
- if (curswant >= 0) {
- curwin->w_curswant = curswant - 1;
- set_curswant = false;
- }
- } else {
- line = tv_get_lnum(argvars);
- col = (long)tv_get_number_chk(&argvars[1], NULL);
- if (argvars[2].v_type != VAR_UNKNOWN) {
- coladd = (long)tv_get_number_chk(&argvars[2], NULL);
- }
- }
- if (line < 0 || col < 0
- || coladd < 0) {
- return; // type error; errmsg already given
- }
- if (line > 0) {
- curwin->w_cursor.lnum = line;
- }
- if (col > 0) {
- curwin->w_cursor.col = col - 1;
- }
- curwin->w_cursor.coladd = coladd;
-
- // Make sure the cursor is in a valid position.
- check_cursor();
- // Correct cursor for multi-byte character.
- if (has_mbyte) {
- mb_adjust_cursor();
- }
-
- curwin->w_set_curswant = set_curswant;
- rettv->vval.v_number = 0;
-}
-
-/*
- * "deepcopy()" function
- */
-static void f_deepcopy(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- int noref = 0;
-
- if (argvars[1].v_type != VAR_UNKNOWN) {
- noref = tv_get_number_chk(&argvars[1], NULL);
- }
- if (noref < 0 || noref > 1) {
- EMSG(_(e_invarg));
- } else {
- var_item_copy(NULL, &argvars[0], rettv, true, (noref == 0
- ? get_copyID()
- : 0));
- }
-}
-
-// "delete()" function
-static void f_delete(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = -1;
- if (check_restricted() || check_secure()) {
- return;
- }
-
- const char *const name = tv_get_string(&argvars[0]);
- if (*name == NUL) {
- EMSG(_(e_invarg));
- return;
- }
-
- char nbuf[NUMBUFLEN];
- const char *flags;
- if (argvars[1].v_type != VAR_UNKNOWN) {
- flags = tv_get_string_buf(&argvars[1], nbuf);
- } else {
- flags = "";
- }
-
- if (*flags == NUL) {
- // delete a file
- rettv->vval.v_number = os_remove(name) == 0 ? 0 : -1;
- } else if (strcmp(flags, "d") == 0) {
- // delete an empty directory
- rettv->vval.v_number = os_rmdir(name) == 0 ? 0 : -1;
- } else if (strcmp(flags, "rf") == 0) {
- // delete a directory recursively
- rettv->vval.v_number = delete_recursive(name);
- } else {
- emsgf(_(e_invexpr2), flags);
- }
-}
-
-// dictwatcheradd(dict, key, funcref) function
-static void f_dictwatcheradd(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- if (check_restricted() || check_secure()) {
- return;
- }
-
- if (argvars[0].v_type != VAR_DICT) {
- emsgf(_(e_invarg2), "dict");
- return;
- } else if (argvars[0].vval.v_dict == NULL) {
- const char *const arg_errmsg = _("dictwatcheradd() argument");
- const size_t arg_errmsg_len = strlen(arg_errmsg);
- emsgf(_(e_readonlyvar), (int)arg_errmsg_len, arg_errmsg);
- return;
- }
-
- if (argvars[1].v_type != VAR_STRING && argvars[1].v_type != VAR_NUMBER) {
- emsgf(_(e_invarg2), "key");
- return;
- }
-
- const char *const key_pattern = tv_get_string_chk(argvars + 1);
- if (key_pattern == NULL) {
- return;
- }
- const size_t key_pattern_len = strlen(key_pattern);
-
- Callback callback;
- if (!callback_from_typval(&callback, &argvars[2])) {
- emsgf(_(e_invarg2), "funcref");
- return;
- }
-
- tv_dict_watcher_add(argvars[0].vval.v_dict, key_pattern, key_pattern_len,
- callback);
-}
-
-// dictwatcherdel(dict, key, funcref) function
-static void f_dictwatcherdel(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- if (check_restricted() || check_secure()) {
- return;
- }
-
- if (argvars[0].v_type != VAR_DICT) {
- emsgf(_(e_invarg2), "dict");
- return;
- }
-
- if (argvars[2].v_type != VAR_FUNC && argvars[2].v_type != VAR_STRING) {
- emsgf(_(e_invarg2), "funcref");
- return;
- }
-
- const char *const key_pattern = tv_get_string_chk(argvars + 1);
- if (key_pattern == NULL) {
- return;
- }
-
- Callback callback;
- if (!callback_from_typval(&callback, &argvars[2])) {
- return;
- }
-
- if (!tv_dict_watcher_remove(argvars[0].vval.v_dict, key_pattern,
- strlen(key_pattern), callback)) {
- EMSG("Couldn't find a watcher matching key and callback");
- }
-
- callback_free(&callback);
-}
-
-/// "deletebufline()" function
-static void f_deletebufline(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- linenr_T last;
- buf_T *curbuf_save = NULL;
- win_T *curwin_save = NULL;
-
- buf_T *const buf = tv_get_buf(&argvars[0], false);
- if (buf == NULL) {
- rettv->vval.v_number = 1; // FAIL
- return;
- }
- const bool is_curbuf = buf == curbuf;
-
- const linenr_T first = tv_get_lnum_buf(&argvars[1], buf);
- if (argvars[2].v_type != VAR_UNKNOWN) {
- last = tv_get_lnum_buf(&argvars[2], buf);
- } else {
- last = first;
- }
-
- if (buf->b_ml.ml_mfp == NULL || first < 1
- || first > buf->b_ml.ml_line_count || last < first) {
- rettv->vval.v_number = 1; // FAIL
- return;
- }
-
- if (!is_curbuf) {
- curbuf_save = curbuf;
- curwin_save = curwin;
- curbuf = buf;
- find_win_for_curbuf();
- }
- if (last > curbuf->b_ml.ml_line_count) {
- last = curbuf->b_ml.ml_line_count;
- }
- const long count = last - first + 1;
-
- // When coming here from Insert mode, sync undo, so that this can be
- // undone separately from what was previously inserted.
- if (u_sync_once == 2) {
- u_sync_once = 1; // notify that u_sync() was called
- u_sync(true);
- }
-
- if (u_save(first - 1, last + 1) == FAIL) {
- rettv->vval.v_number = 1; // FAIL
- return;
- }
-
- for (linenr_T lnum = first; lnum <= last; lnum++) {
- ml_delete(first, true);
- }
-
- FOR_ALL_TAB_WINDOWS(tp, wp) {
- if (wp->w_buffer == buf) {
- if (wp->w_cursor.lnum > last) {
- wp->w_cursor.lnum -= count;
- } else if (wp->w_cursor.lnum> first) {
- wp->w_cursor.lnum = first;
- }
- if (wp->w_cursor.lnum > wp->w_buffer->b_ml.ml_line_count) {
- wp->w_cursor.lnum = wp->w_buffer->b_ml.ml_line_count;
- }
- }
- }
- check_cursor_col();
- deleted_lines_mark(first, count);
-
- if (!is_curbuf) {
- curbuf = curbuf_save;
- curwin = curwin_save;
- }
-}
-
-/*
- * "did_filetype()" function
- */
-static void f_did_filetype(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = did_filetype;
-}
-
-/*
- * "diff_filler()" function
- */
-static void f_diff_filler(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = diff_check_fill(curwin, tv_get_lnum(argvars));
-}
-
-/*
- * "diff_hlID()" function
- */
-static void f_diff_hlID(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- linenr_T lnum = tv_get_lnum(argvars);
- static linenr_T prev_lnum = 0;
- static int changedtick = 0;
- static int fnum = 0;
- static int change_start = 0;
- static int change_end = 0;
- static hlf_T hlID = (hlf_T)0;
- int filler_lines;
- int col;
-
- if (lnum < 0) /* ignore type error in {lnum} arg */
- lnum = 0;
- if (lnum != prev_lnum
- || changedtick != buf_get_changedtick(curbuf)
- || fnum != curbuf->b_fnum) {
- /* New line, buffer, change: need to get the values. */
- filler_lines = diff_check(curwin, lnum);
- if (filler_lines < 0) {
- if (filler_lines == -1) {
- change_start = MAXCOL;
- change_end = -1;
- if (diff_find_change(curwin, lnum, &change_start, &change_end))
- hlID = HLF_ADD; /* added line */
- else
- hlID = HLF_CHD; /* changed line */
- } else
- hlID = HLF_ADD; /* added line */
- } else
- hlID = (hlf_T)0;
- prev_lnum = lnum;
- changedtick = buf_get_changedtick(curbuf);
- fnum = curbuf->b_fnum;
- }
-
- if (hlID == HLF_CHD || hlID == HLF_TXD) {
- col = tv_get_number(&argvars[1]) - 1; // Ignore type error in {col}.
- if (col >= change_start && col <= change_end) {
- hlID = HLF_TXD; // Changed text.
- } else {
- hlID = HLF_CHD; // Changed line.
- }
- }
- rettv->vval.v_number = hlID == (hlf_T)0 ? 0 : (int)(hlID + 1);
-}
-
-/*
- * "empty({expr})" function
- */
-static void f_empty(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- bool n = true;
-
- switch (argvars[0].v_type) {
- case VAR_STRING:
- case VAR_FUNC: {
- n = argvars[0].vval.v_string == NULL
- || *argvars[0].vval.v_string == NUL;
- break;
- }
- case VAR_PARTIAL: {
- n = false;
- break;
- }
- case VAR_NUMBER: {
- n = argvars[0].vval.v_number == 0;
- break;
- }
- case VAR_FLOAT: {
- n = argvars[0].vval.v_float == 0.0;
- break;
- }
- case VAR_LIST: {
- n = (tv_list_len(argvars[0].vval.v_list) == 0);
- break;
- }
- case VAR_DICT: {
- n = (tv_dict_len(argvars[0].vval.v_dict) == 0);
- break;
- }
- case VAR_SPECIAL: {
- // Using switch to get warning if SpecialVarValue receives more values.
- switch (argvars[0].vval.v_special) {
- case kSpecialVarTrue: {
- n = false;
- break;
- }
- case kSpecialVarFalse:
- case kSpecialVarNull: {
- n = true;
- break;
- }
- }
- break;
- }
- case VAR_UNKNOWN: {
- internal_error("f_empty(UNKNOWN)");
- break;
- }
- }
-
- rettv->vval.v_number = n;
-}
-
-/// "environ()" function
-static void f_environ(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- tv_dict_alloc_ret(rettv);
-
- for (int i = 0; ; i++) {
- // TODO(justinmk): use os_copyfullenv from #7202 ?
- char *envname = os_getenvname_at_index((size_t)i);
- if (envname == NULL) {
- break;
- }
- const char *value = os_getenv(envname);
- tv_dict_add_str(rettv->vval.v_dict,
- (char *)envname, STRLEN((char *)envname),
- value == NULL ? "" : value);
- xfree(envname);
- }
-}
-
-/*
- * "escape({string}, {chars})" function
- */
-static void f_escape(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- char buf[NUMBUFLEN];
-
- rettv->vval.v_string = vim_strsave_escaped(
- (const char_u *)tv_get_string(&argvars[0]),
- (const char_u *)tv_get_string_buf(&argvars[1], buf));
- rettv->v_type = VAR_STRING;
-}
-
-/// "getenv()" function
-static void f_getenv(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- char_u *p = (char_u *)vim_getenv(tv_get_string(&argvars[0]));
-
- if (p == NULL) {
- rettv->v_type = VAR_SPECIAL;
- rettv->vval.v_number = kSpecialVarNull;
- return;
- }
- rettv->vval.v_string = p;
- rettv->v_type = VAR_STRING;
-}
-
-/*
- * "eval()" function
- */
-static void f_eval(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const char *s = tv_get_string_chk(&argvars[0]);
- if (s != NULL) {
- s = (const char *)skipwhite((const char_u *)s);
- }
-
- const char *const expr_start = s;
- if (s == NULL || eval1((char_u **)&s, rettv, true) == FAIL) {
- if (expr_start != NULL && !aborting()) {
- EMSG2(_(e_invexpr2), expr_start);
- }
- need_clr_eos = FALSE;
- rettv->v_type = VAR_NUMBER;
- rettv->vval.v_number = 0;
- } else if (*s != NUL) {
- EMSG(_(e_trailing));
- }
-}
-
-/*
- * "eventhandler()" function
- */
-static void f_eventhandler(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = vgetc_busy;
-}
-
-/*
- * "executable()" function
- */
-static void f_executable(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const char *name = tv_get_string(&argvars[0]);
-
- // Check in $PATH and also check directly if there is a directory name
- rettv->vval.v_number = os_can_exe(name, NULL, true);
-}
-
-typedef struct {
- const list_T *const l;
- const listitem_T *li;
-} GetListLineCookie;
-
-static char_u *get_list_line(int c, void *cookie, int indent)
-{
- GetListLineCookie *const p = (GetListLineCookie *)cookie;
-
- const listitem_T *const item = p->li;
- if (item == NULL) {
- return NULL;
- }
- char buf[NUMBUFLEN];
- const char *const s = tv_get_string_buf_chk(TV_LIST_ITEM_TV(item), buf);
- p->li = TV_LIST_ITEM_NEXT(p->l, item);
- return (char_u *)(s == NULL ? NULL : xstrdup(s));
-}
-
-// "execute(command)" function
-static void f_execute(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const int save_msg_silent = msg_silent;
- const int save_emsg_silent = emsg_silent;
- const bool save_emsg_noredir = emsg_noredir;
- const bool save_redir_off = redir_off;
- garray_T *const save_capture_ga = capture_ga;
- const int save_msg_col = msg_col;
- bool echo_output = false;
-
- if (check_secure()) {
- return;
- }
-
- if (argvars[1].v_type != VAR_UNKNOWN) {
- char buf[NUMBUFLEN];
- const char *const s = tv_get_string_buf_chk(&argvars[1], buf);
-
- if (s == NULL) {
- return;
- }
- if (*s == NUL) {
- echo_output = true;
- }
- if (strncmp(s, "silent", 6) == 0) {
- msg_silent++;
- }
- if (strcmp(s, "silent!") == 0) {
- emsg_silent = true;
- emsg_noredir = true;
- }
- } else {
- msg_silent++;
- }
-
- garray_T capture_local;
- ga_init(&capture_local, (int)sizeof(char), 80);
- capture_ga = &capture_local;
- redir_off = false;
- if (!echo_output) {
- msg_col = 0; // prevent leading spaces
- }
-
- if (argvars[0].v_type != VAR_LIST) {
- do_cmdline_cmd(tv_get_string(&argvars[0]));
- } else if (argvars[0].vval.v_list != NULL) {
- list_T *const list = argvars[0].vval.v_list;
- tv_list_ref(list);
- GetListLineCookie cookie = {
- .l = list,
- .li = tv_list_first(list),
- };
- do_cmdline(NULL, get_list_line, (void *)&cookie,
- DOCMD_NOWAIT|DOCMD_VERBOSE|DOCMD_REPEAT|DOCMD_KEYTYPED);
- tv_list_unref(list);
- }
- msg_silent = save_msg_silent;
- emsg_silent = save_emsg_silent;
- emsg_noredir = save_emsg_noredir;
- redir_off = save_redir_off;
- // "silent reg" or "silent echo x" leaves msg_col somewhere in the line.
- if (echo_output) {
- // When not working silently: put it in column zero. A following
- // "echon" will overwrite the message, unavoidably.
- msg_col = 0;
- } else {
- // When working silently: Put it back where it was, since nothing
- // should have been written.
- msg_col = save_msg_col;
- }
-
- ga_append(capture_ga, NUL);
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = capture_ga->ga_data;
-
- capture_ga = save_capture_ga;
-}
-
-/// "exepath()" function
-static void f_exepath(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const char *arg = tv_get_string(&argvars[0]);
- char *path = NULL;
-
- (void)os_can_exe(arg, &path, true);
-
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = (char_u *)path;
+ return 0;
}
/// Find a window: When using a Window ID in any tab page, when using a number
@@ -8665,329 +5949,9 @@ win_T * find_win_by_nr_or_id(typval_T *vp)
}
/*
- * "exists()" function
- */
-static void f_exists(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- int n = false;
- int len = 0;
-
- const char *p = tv_get_string(&argvars[0]);
- if (*p == '$') { // Environment variable.
- // First try "normal" environment variables (fast).
- if (os_env_exists(p + 1)) {
- n = true;
- } else {
- // Try expanding things like $VIM and ${HOME}.
- char_u *const exp = expand_env_save((char_u *)p);
- if (exp != NULL && *exp != '$') {
- n = true;
- }
- xfree(exp);
- }
- } else if (*p == '&' || *p == '+') { // Option.
- n = (get_option_tv(&p, NULL, true) == OK);
- if (*skipwhite((const char_u *)p) != NUL) {
- n = false; // Trailing garbage.
- }
- } else if (*p == '*') { // Internal or user defined function.
- n = function_exists(p + 1, false);
- } else if (*p == ':') {
- n = cmd_exists(p + 1);
- } else if (*p == '#') {
- if (p[1] == '#') {
- n = autocmd_supported(p + 2);
- } else {
- n = au_exists(p + 1);
- }
- } else { // Internal variable.
- typval_T tv;
-
- // get_name_len() takes care of expanding curly braces
- const char *name = p;
- char *tofree;
- len = get_name_len((const char **)&p, &tofree, true, false);
- if (len > 0) {
- if (tofree != NULL) {
- name = tofree;
- }
- n = (get_var_tv(name, len, &tv, NULL, false, true) == OK);
- if (n) {
- // Handle d.key, l[idx], f(expr).
- n = (handle_subscript(&p, &tv, true, false) == OK);
- if (n) {
- tv_clear(&tv);
- }
- }
- }
- if (*p != NUL)
- n = FALSE;
-
- xfree(tofree);
- }
-
- rettv->vval.v_number = n;
-}
-
-/*
- * "expand()" function
- */
-static void f_expand(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- size_t len;
- char_u *errormsg;
- int options = WILD_SILENT|WILD_USE_NL|WILD_LIST_NOTFOUND;
- expand_T xpc;
- bool error = false;
- char_u *result;
-
- rettv->v_type = VAR_STRING;
- if (argvars[1].v_type != VAR_UNKNOWN
- && argvars[2].v_type != VAR_UNKNOWN
- && tv_get_number_chk(&argvars[2], &error)
- && !error) {
- tv_list_set_ret(rettv, NULL);
- }
-
- const char *s = tv_get_string(&argvars[0]);
- if (*s == '%' || *s == '#' || *s == '<') {
- emsg_off++;
- result = eval_vars((char_u *)s, (char_u *)s, &len, NULL, &errormsg, NULL);
- emsg_off--;
- if (rettv->v_type == VAR_LIST) {
- tv_list_alloc_ret(rettv, (result != NULL));
- if (result != NULL) {
- tv_list_append_string(rettv->vval.v_list, (const char *)result, -1);
- }
- } else
- rettv->vval.v_string = result;
- } else {
- /* When the optional second argument is non-zero, don't remove matches
- * for 'wildignore' and don't put matches for 'suffixes' at the end. */
- if (argvars[1].v_type != VAR_UNKNOWN
- && tv_get_number_chk(&argvars[1], &error)) {
- options |= WILD_KEEP_ALL;
- }
- if (!error) {
- ExpandInit(&xpc);
- xpc.xp_context = EXPAND_FILES;
- if (p_wic) {
- options += WILD_ICASE;
- }
- if (rettv->v_type == VAR_STRING) {
- rettv->vval.v_string = ExpandOne(&xpc, (char_u *)s, NULL, options,
- WILD_ALL);
- } else {
- ExpandOne(&xpc, (char_u *)s, NULL, options, WILD_ALL_KEEP);
- tv_list_alloc_ret(rettv, xpc.xp_numfiles);
- for (int i = 0; i < xpc.xp_numfiles; i++) {
- tv_list_append_string(rettv->vval.v_list,
- (const char *)xpc.xp_files[i], -1);
- }
- ExpandCleanup(&xpc);
- }
- } else {
- rettv->vval.v_string = NULL;
- }
- }
-}
-
-
-/// "menu_get(path [, modes])" function
-static void f_menu_get(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- tv_list_alloc_ret(rettv, kListLenMayKnow);
- int modes = MENU_ALL_MODES;
- if (argvars[1].v_type == VAR_STRING) {
- const char_u *const strmodes = (char_u *)tv_get_string(&argvars[1]);
- modes = get_menu_cmd_modes(strmodes, false, NULL, NULL);
- }
- menu_get((char_u *)tv_get_string(&argvars[0]), modes, rettv->vval.v_list);
-}
-
-/*
- * "extend(list, list [, idx])" function
- * "extend(dict, dict [, action])" function
- */
-static void f_extend(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const char *const arg_errmsg = N_("extend() argument");
-
- if (argvars[0].v_type == VAR_LIST && argvars[1].v_type == VAR_LIST) {
- long before;
- bool error = false;
-
- list_T *const l1 = argvars[0].vval.v_list;
- list_T *const l2 = argvars[1].vval.v_list;
- if (!tv_check_lock(tv_list_locked(l1), arg_errmsg, TV_TRANSLATE)) {
- listitem_T *item;
- if (argvars[2].v_type != VAR_UNKNOWN) {
- before = (long)tv_get_number_chk(&argvars[2], &error);
- if (error) {
- return; // Type error; errmsg already given.
- }
-
- if (before == tv_list_len(l1)) {
- item = NULL;
- } else {
- item = tv_list_find(l1, before);
- if (item == NULL) {
- EMSGN(_(e_listidx), before);
- return;
- }
- }
- } else {
- item = NULL;
- }
- tv_list_extend(l1, l2, item);
-
- tv_copy(&argvars[0], rettv);
- }
- } else if (argvars[0].v_type == VAR_DICT && argvars[1].v_type ==
- VAR_DICT) {
- dict_T *const d1 = argvars[0].vval.v_dict;
- dict_T *const d2 = argvars[1].vval.v_dict;
- if (d1 == NULL) {
- const bool locked = tv_check_lock(VAR_FIXED, arg_errmsg, TV_TRANSLATE);
- (void)locked;
- assert(locked == true);
- } else if (d2 == NULL) {
- // Do nothing
- tv_copy(&argvars[0], rettv);
- } else if (!tv_check_lock(d1->dv_lock, arg_errmsg, TV_TRANSLATE)) {
- const char *action = "force";
- // Check the third argument.
- if (argvars[2].v_type != VAR_UNKNOWN) {
- const char *const av[] = { "keep", "force", "error" };
-
- action = tv_get_string_chk(&argvars[2]);
- if (action == NULL) {
- return; // Type error; error message already given.
- }
- size_t i;
- for (i = 0; i < ARRAY_SIZE(av); i++) {
- if (strcmp(action, av[i]) == 0) {
- break;
- }
- }
- if (i == 3) {
- EMSG2(_(e_invarg2), action);
- return;
- }
- }
-
- tv_dict_extend(d1, d2, action);
-
- tv_copy(&argvars[0], rettv);
- }
- } else {
- EMSG2(_(e_listdictarg), "extend()");
- }
-}
-
-/*
- * "feedkeys()" function
- */
-static void f_feedkeys(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- // This is not allowed in the sandbox. If the commands would still be
- // executed in the sandbox it would be OK, but it probably happens later,
- // when "sandbox" is no longer set.
- if (check_secure()) {
- return;
- }
-
- const char *const keys = tv_get_string(&argvars[0]);
- char nbuf[NUMBUFLEN];
- const char *flags = NULL;
- if (argvars[1].v_type != VAR_UNKNOWN) {
- flags = tv_get_string_buf(&argvars[1], nbuf);
- }
-
- nvim_feedkeys(cstr_as_string((char *)keys),
- cstr_as_string((char *)flags), true);
-}
-
-/// "filereadable()" function
-static void f_filereadable(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const char *const p = tv_get_string(&argvars[0]);
- rettv->vval.v_number =
- (*p && !os_isdir((const char_u *)p) && os_file_is_readable(p));
-}
-
-/*
- * Return 0 for not writable, 1 for writable file, 2 for a dir which we have
- * rights to write into.
- */
-static void f_filewritable(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const char *filename = tv_get_string(&argvars[0]);
- rettv->vval.v_number = os_file_is_writable(filename);
-}
-
-
-static void findfilendir(typval_T *argvars, typval_T *rettv, int find_what)
-{
- char_u *fresult = NULL;
- char_u *path = *curbuf->b_p_path == NUL ? p_path : curbuf->b_p_path;
- int count = 1;
- bool first = true;
- bool error = false;
-
- rettv->vval.v_string = NULL;
- rettv->v_type = VAR_STRING;
-
- const char *fname = tv_get_string(&argvars[0]);
-
- char pathbuf[NUMBUFLEN];
- if (argvars[1].v_type != VAR_UNKNOWN) {
- const char *p = tv_get_string_buf_chk(&argvars[1], pathbuf);
- if (p == NULL) {
- error = true;
- } else {
- if (*p != NUL) {
- path = (char_u *)p;
- }
-
- if (argvars[2].v_type != VAR_UNKNOWN) {
- count = tv_get_number_chk(&argvars[2], &error);
- }
- }
- }
-
- if (count < 0) {
- tv_list_alloc_ret(rettv, kListLenUnknown);
- }
-
- if (*fname != NUL && !error) {
- do {
- if (rettv->v_type == VAR_STRING || rettv->v_type == VAR_LIST)
- xfree(fresult);
- fresult = find_file_in_path_option(first ? (char_u *)fname : NULL,
- first ? strlen(fname) : 0,
- 0, first, path,
- find_what, curbuf->b_ffname,
- (find_what == FINDFILE_DIR
- ? (char_u *)""
- : curbuf->b_p_sua));
- first = false;
-
- if (fresult != NULL && rettv->v_type == VAR_LIST) {
- tv_list_append_string(rettv->vval.v_list, (const char *)fresult, -1);
- }
- } while ((rettv->v_type == VAR_LIST || --count > 0) && fresult != NULL);
- }
-
- if (rettv->v_type == VAR_STRING)
- rettv->vval.v_string = fresult;
-}
-
-
-/*
* Implementation of map() and filter().
*/
-static void filter_map(typval_T *argvars, typval_T *rettv, int map)
+void filter_map(typval_T *argvars, typval_T *rettv, int map)
{
typval_T *expr;
list_T *l = NULL;
@@ -9137,248 +6101,8 @@ theend:
return retval;
}
-/*
- * "filter()" function
- */
-static void f_filter(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- filter_map(argvars, rettv, FALSE);
-}
-
-/*
- * "finddir({fname}[, {path}[, {count}]])" function
- */
-static void f_finddir(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- findfilendir(argvars, rettv, FINDFILE_DIR);
-}
-
-/*
- * "findfile({fname}[, {path}[, {count}]])" function
- */
-static void f_findfile(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- findfilendir(argvars, rettv, FINDFILE_FILE);
-}
-
-/*
- * "float2nr({float})" function
- */
-static void f_float2nr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- float_T f;
-
- if (tv_get_float_chk(argvars, &f)) {
- if (f <= -VARNUMBER_MAX + DBL_EPSILON) {
- rettv->vval.v_number = -VARNUMBER_MAX;
- } else if (f >= VARNUMBER_MAX - DBL_EPSILON) {
- rettv->vval.v_number = VARNUMBER_MAX;
- } else {
- rettv->vval.v_number = (varnumber_T)f;
- }
- }
-}
-
-/*
- * "fmod()" function
- */
-static void f_fmod(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- float_T fx;
- float_T fy;
-
- rettv->v_type = VAR_FLOAT;
- if (tv_get_float_chk(argvars, &fx) && tv_get_float_chk(&argvars[1], &fy)) {
- rettv->vval.v_float = fmod(fx, fy);
- } else {
- rettv->vval.v_float = 0.0;
- }
-}
-
-/*
- * "fnameescape({string})" function
- */
-static void f_fnameescape(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_string = (char_u *)vim_strsave_fnameescape(
- tv_get_string(&argvars[0]), false);
- rettv->v_type = VAR_STRING;
-}
-
-/*
- * "fnamemodify({fname}, {mods})" function
- */
-static void f_fnamemodify(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- char_u *fbuf = NULL;
- size_t len;
- char buf[NUMBUFLEN];
- const char *fname = tv_get_string_chk(&argvars[0]);
- const char *const mods = tv_get_string_buf_chk(&argvars[1], buf);
- if (fname == NULL || mods == NULL) {
- fname = NULL;
- } else {
- len = strlen(fname);
- size_t usedlen = 0;
- (void)modify_fname((char_u *)mods, false, &usedlen,
- (char_u **)&fname, &fbuf, &len);
- }
-
- rettv->v_type = VAR_STRING;
- if (fname == NULL) {
- rettv->vval.v_string = NULL;
- } else {
- rettv->vval.v_string = (char_u *)xmemdupz(fname, len);
- }
- xfree(fbuf);
-}
-
-
-/*
- * "foldclosed()" function
- */
-static void foldclosed_both(typval_T *argvars, typval_T *rettv, int end)
-{
- const linenr_T lnum = tv_get_lnum(argvars);
- if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count) {
- linenr_T first;
- linenr_T last;
- if (hasFoldingWin(curwin, lnum, &first, &last, false, NULL)) {
- if (end) {
- rettv->vval.v_number = (varnumber_T)last;
- } else {
- rettv->vval.v_number = (varnumber_T)first;
- }
- return;
- }
- }
- rettv->vval.v_number = -1;
-}
-
-/*
- * "foldclosed()" function
- */
-static void f_foldclosed(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- foldclosed_both(argvars, rettv, FALSE);
-}
-
-/*
- * "foldclosedend()" function
- */
-static void f_foldclosedend(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- foldclosed_both(argvars, rettv, TRUE);
-}
-
-/*
- * "foldlevel()" function
- */
-static void f_foldlevel(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const linenr_T lnum = tv_get_lnum(argvars);
- if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count) {
- rettv->vval.v_number = foldLevel(lnum);
- }
-}
-
-/*
- * "foldtext()" function
- */
-static void f_foldtext(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- linenr_T foldstart;
- linenr_T foldend;
- char_u *dashes;
- linenr_T lnum;
- char_u *s;
- char_u *r;
- int len;
- char *txt;
-
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = NULL;
-
- foldstart = (linenr_T)get_vim_var_nr(VV_FOLDSTART);
- foldend = (linenr_T)get_vim_var_nr(VV_FOLDEND);
- dashes = get_vim_var_str(VV_FOLDDASHES);
- if (foldstart > 0 && foldend <= curbuf->b_ml.ml_line_count) {
- // Find first non-empty line in the fold.
- for (lnum = foldstart; lnum < foldend; lnum++) {
- if (!linewhite(lnum)) {
- break;
- }
- }
-
- /* Find interesting text in this line. */
- s = skipwhite(ml_get(lnum));
- /* skip C comment-start */
- if (s[0] == '/' && (s[1] == '*' || s[1] == '/')) {
- s = skipwhite(s + 2);
- if (*skipwhite(s) == NUL && lnum + 1 < foldend) {
- s = skipwhite(ml_get(lnum + 1));
- if (*s == '*')
- s = skipwhite(s + 1);
- }
- }
- unsigned long count = (unsigned long)(foldend - foldstart + 1);
- txt = NGETTEXT("+-%s%3ld line: ", "+-%s%3ld lines: ", count);
- r = xmalloc(STRLEN(txt)
- + STRLEN(dashes) // for %s
- + 20 // for %3ld
- + STRLEN(s)); // concatenated
- sprintf((char *)r, txt, dashes, count);
- len = (int)STRLEN(r);
- STRCAT(r, s);
- /* remove 'foldmarker' and 'commentstring' */
- foldtext_cleanup(r + len);
- rettv->vval.v_string = r;
- }
-}
-
-/*
- * "foldtextresult(lnum)" function
- */
-static void f_foldtextresult(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- char_u *text;
- char_u buf[FOLD_TEXT_LEN];
- foldinfo_T foldinfo;
- int fold_count;
- static bool entered = false;
-
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = NULL;
- if (entered) {
- return; // reject recursive use
- }
- entered = true;
- linenr_T lnum = tv_get_lnum(argvars);
- // Treat illegal types and illegal string values for {lnum} the same.
- if (lnum < 0) {
- lnum = 0;
- }
- fold_count = foldedCount(curwin, lnum, &foldinfo);
- if (fold_count > 0) {
- text = get_foldtext(curwin, lnum, lnum + fold_count - 1, &foldinfo, buf);
- if (text == buf) {
- text = vim_strsave(text);
- }
- rettv->vval.v_string = text;
- }
-
- entered = false;
-}
-
-/*
- * "foreground()" function
- */
-static void f_foreground(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
-}
-
-static void common_function(typval_T *argvars, typval_T *rettv,
- bool is_funcref, FunPtr fptr)
+void common_function(typval_T *argvars, typval_T *rettv,
+ bool is_funcref, FunPtr fptr)
{
char_u *s;
char_u *name;
@@ -9472,6 +6196,10 @@ static void common_function(typval_T *argvars, typval_T *rettv,
list = argvars[arg_idx].vval.v_list;
if (tv_list_len(list) == 0) {
arg_idx = 0;
+ } else if (tv_list_len(list) > MAX_FUNC_ARGS) {
+ emsg_funcname((char *)e_toomanyarg, s);
+ xfree(name);
+ goto theend;
}
}
}
@@ -9539,116 +6267,8 @@ theend:
xfree(trans_name);
}
-static void f_funcref(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- common_function(argvars, rettv, true, fptr);
-}
-
-static void f_function(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- common_function(argvars, rettv, false, fptr);
-}
-
-/// "garbagecollect()" function
-static void f_garbagecollect(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- // This is postponed until we are back at the toplevel, because we may be
- // using Lists and Dicts internally. E.g.: ":echo [garbagecollect()]".
- want_garbage_collect = true;
-
- if (argvars[0].v_type != VAR_UNKNOWN && tv_get_number(&argvars[0]) == 1) {
- garbage_collect_at_exit = true;
- }
-}
-
-/*
- * "get()" function
- */
-static void f_get(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- listitem_T *li;
- list_T *l;
- dictitem_T *di;
- dict_T *d;
- typval_T *tv = NULL;
- bool what_is_dict = false;
-
- if (argvars[0].v_type == VAR_LIST) {
- if ((l = argvars[0].vval.v_list) != NULL) {
- bool error = false;
-
- li = tv_list_find(l, tv_get_number_chk(&argvars[1], &error));
- if (!error && li != NULL) {
- tv = TV_LIST_ITEM_TV(li);
- }
- }
- } else if (argvars[0].v_type == VAR_DICT) {
- if ((d = argvars[0].vval.v_dict) != NULL) {
- di = tv_dict_find(d, tv_get_string(&argvars[1]), -1);
- if (di != NULL) {
- tv = &di->di_tv;
- }
- }
- } else if (tv_is_func(argvars[0])) {
- partial_T *pt;
- partial_T fref_pt;
-
- if (argvars[0].v_type == VAR_PARTIAL) {
- pt = argvars[0].vval.v_partial;
- } else {
- memset(&fref_pt, 0, sizeof(fref_pt));
- fref_pt.pt_name = argvars[0].vval.v_string;
- pt = &fref_pt;
- }
-
- if (pt != NULL) {
- const char *const what = tv_get_string(&argvars[1]);
-
- if (strcmp(what, "func") == 0 || strcmp(what, "name") == 0) {
- rettv->v_type = (*what == 'f' ? VAR_FUNC : VAR_STRING);
- const char *const n = (const char *)partial_name(pt);
- assert(n != NULL);
- rettv->vval.v_string = (char_u *)xstrdup(n);
- if (rettv->v_type == VAR_FUNC) {
- func_ref(rettv->vval.v_string);
- }
- } else if (strcmp(what, "dict") == 0) {
- what_is_dict = true;
- if (pt->pt_dict != NULL) {
- tv_dict_set_ret(rettv, pt->pt_dict);
- }
- } else if (strcmp(what, "args") == 0) {
- rettv->v_type = VAR_LIST;
- if (tv_list_alloc_ret(rettv, pt->pt_argc) != NULL) {
- for (int i = 0; i < pt->pt_argc; i++) {
- tv_list_append_tv(rettv->vval.v_list, &pt->pt_argv[i]);
- }
- }
- } else {
- EMSG2(_(e_invarg2), what);
- }
-
- // When {what} == "dict" and pt->pt_dict == NULL, evaluate the
- // third argument
- if (!what_is_dict) {
- return;
- }
- }
- } else {
- EMSG2(_(e_listdictarg), "get()");
- }
-
- if (tv == NULL) {
- if (argvars[2].v_type != VAR_UNKNOWN) {
- tv_copy(&argvars[2], rettv);
- }
- } else {
- tv_copy(tv, rettv);
- }
-}
-
/// Returns buffer options, variables and other attributes in a dictionary.
-static dict_T *get_buffer_info(buf_T *buf)
+dict_T *get_buffer_info(buf_T *buf)
{
dict_T *const dict = tv_dict_alloc();
@@ -9657,6 +6277,7 @@ static dict_T *get_buffer_info(buf_T *buf)
buf->b_ffname != NULL ? (const char *)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);
tv_dict_add_nr(dict, S_LEN("loaded"), buf->b_ml.ml_mfp != NULL);
tv_dict_add_nr(dict, S_LEN("listed"), buf->b_p_bl);
tv_dict_add_nr(dict, S_LEN("changed"), bufIsChanged(buf));
@@ -9681,110 +6302,9 @@ static dict_T *get_buffer_info(buf_T *buf)
tv_dict_add_list(dict, S_LEN("signs"), get_buffer_signs(buf));
}
- return dict;
-}
-
-/// "getbufinfo()" function
-static void f_getbufinfo(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- buf_T *argbuf = NULL;
- bool filtered = false;
- bool sel_buflisted = false;
- bool sel_bufloaded = false;
- bool sel_bufmodified = false;
-
- tv_list_alloc_ret(rettv, kListLenMayKnow);
-
- // List of all the buffers or selected buffers
- if (argvars[0].v_type == VAR_DICT) {
- dict_T *sel_d = argvars[0].vval.v_dict;
-
- if (sel_d != NULL) {
- dictitem_T *di;
-
- filtered = true;
+ tv_dict_add_nr(dict, S_LEN("lastused"), buf->b_last_used);
- di = tv_dict_find(sel_d, S_LEN("buflisted"));
- if (di != NULL && tv_get_number(&di->di_tv)) {
- sel_buflisted = true;
- }
-
- di = tv_dict_find(sel_d, S_LEN("bufloaded"));
- if (di != NULL && tv_get_number(&di->di_tv)) {
- sel_bufloaded = true;
- }
- di = tv_dict_find(sel_d, S_LEN("bufmodified"));
- if (di != NULL && tv_get_number(&di->di_tv)) {
- sel_bufmodified = true;
- }
- }
- } else if (argvars[0].v_type != VAR_UNKNOWN) {
- // Information about one buffer. Argument specifies the buffer
- if (tv_check_num(&argvars[0])) { // issue errmsg if type error
- emsg_off++;
- argbuf = tv_get_buf(&argvars[0], false);
- emsg_off--;
- if (argbuf == NULL) {
- return;
- }
- }
- }
-
- // Return information about all the buffers or a specified buffer
- FOR_ALL_BUFFERS(buf) {
- if (argbuf != NULL && argbuf != buf) {
- continue;
- }
- if (filtered && ((sel_bufloaded && buf->b_ml.ml_mfp == NULL)
- || (sel_buflisted && !buf->b_p_bl)
- || (sel_bufmodified && !buf->b_changed))) {
- continue;
- }
-
- dict_T *const d = get_buffer_info(buf);
- tv_list_append_dict(rettv->vval.v_list, d);
- if (argbuf != NULL) {
- return;
- }
- }
-}
-
-/*
- * Get line or list of lines from buffer "buf" into "rettv".
- * Return a range (from start to end) of lines in rettv from the specified
- * buffer.
- * If 'retlist' is TRUE, then the lines are returned as a Vim List.
- */
-static void get_buffer_lines(buf_T *buf, linenr_T start, linenr_T end, int retlist, typval_T *rettv)
-{
- rettv->v_type = (retlist ? VAR_LIST : VAR_STRING);
- rettv->vval.v_string = NULL;
-
- if (buf == NULL || buf->b_ml.ml_mfp == NULL || start < 0 || end < start) {
- if (retlist) {
- tv_list_alloc_ret(rettv, 0);
- }
- return;
- }
-
- if (retlist) {
- if (start < 1) {
- start = 1;
- }
- if (end > buf->b_ml.ml_line_count) {
- end = buf->b_ml.ml_line_count;
- }
- tv_list_alloc_ret(rettv, end - start + 1);
- while (start <= end) {
- tv_list_append_string(rettv->vval.v_list,
- (const char *)ml_get_buf(buf, start++, false), -1);
- }
- } else {
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = ((start >= 1 && start <= buf->b_ml.ml_line_count)
- ? vim_strsave(ml_get_buf(buf, start, false))
- : NULL);
- }
+ return dict;
}
/// Get the line number from VimL object
@@ -9797,8 +6317,8 @@ static void get_buffer_lines(buf_T *buf, linenr_T start, linenr_T end, int retli
/// be NULL, in this case "$" results in zero return.
///
/// @return Line number or 0 in case of error.
-static linenr_T tv_get_lnum_buf(const typval_T *const tv,
- const buf_T *const buf)
+linenr_T tv_get_lnum_buf(const typval_T *const tv,
+ const buf_T *const buf)
FUNC_ATTR_NONNULL_ARG(1) FUNC_ATTR_WARN_UNUSED_RESULT
{
if (tv->v_type == VAR_STRING
@@ -9810,675 +6330,8 @@ static linenr_T tv_get_lnum_buf(const typval_T *const tv,
return tv_get_number_chk(tv, NULL);
}
-/*
- * "getbufline()" function
- */
-static void f_getbufline(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- buf_T *buf = NULL;
-
- if (tv_check_str_or_nr(&argvars[0])) {
- emsg_off++;
- buf = tv_get_buf(&argvars[0], false);
- emsg_off--;
- }
-
- const linenr_T lnum = tv_get_lnum_buf(&argvars[1], buf);
- const linenr_T end = (argvars[2].v_type == VAR_UNKNOWN
- ? lnum
- : tv_get_lnum_buf(&argvars[2], buf));
-
- get_buffer_lines(buf, lnum, end, true, rettv);
-}
-
-/*
- * "getbufvar()" function
- */
-static void f_getbufvar(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- bool done = false;
-
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = NULL;
-
- if (!tv_check_str_or_nr(&argvars[0])) {
- goto f_getbufvar_end;
- }
-
- const char *varname = tv_get_string_chk(&argvars[1]);
- emsg_off++;
- buf_T *const buf = tv_get_buf(&argvars[0], false);
-
- if (buf != NULL && varname != NULL) {
- // set curbuf to be our buf, temporarily
- buf_T *const save_curbuf = curbuf;
- curbuf = buf;
-
- if (*varname == '&') { // buffer-local-option
- if (varname[1] == NUL) {
- // get all buffer-local options in a dict
- dict_T *opts = get_winbuf_options(true);
-
- if (opts != NULL) {
- tv_dict_set_ret(rettv, opts);
- done = true;
- }
- } else if (get_option_tv(&varname, rettv, true) == OK) {
- // buffer-local-option
- done = true;
- }
- } else {
- // Look up the variable.
- // Let getbufvar({nr}, "") return the "b:" dictionary.
- dictitem_T *const v = find_var_in_ht(&curbuf->b_vars->dv_hashtab, 'b',
- varname, strlen(varname), false);
- if (v != NULL) {
- tv_copy(&v->di_tv, rettv);
- done = true;
- }
- }
-
- // restore previous notion of curbuf
- curbuf = save_curbuf;
- }
- emsg_off--;
-
-f_getbufvar_end:
- if (!done && argvars[2].v_type != VAR_UNKNOWN) {
- // use the default value
- tv_copy(&argvars[2], rettv);
- }
-}
-
-// "getchangelist()" function
-static void f_getchangelist(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- tv_list_alloc_ret(rettv, 2);
- vim_ignored = tv_get_number(&argvars[0]); // issue errmsg if type error
- emsg_off++;
- const buf_T *const buf = tv_get_buf(&argvars[0], false);
- emsg_off--;
- if (buf == NULL) {
- return;
- }
-
- list_T *const l = tv_list_alloc(buf->b_changelistlen);
- tv_list_append_list(rettv->vval.v_list, l);
- // The current window change list index tracks only the position in the
- // current buffer change list. For other buffers, use the change list
- // length as the current index.
- tv_list_append_number(rettv->vval.v_list,
- (buf == curwin->w_buffer)
- ? curwin->w_changelistidx
- : buf->b_changelistlen);
-
- for (int i = 0; i < buf->b_changelistlen; i++) {
- if (buf->b_changelist[i].mark.lnum == 0) {
- continue;
- }
- dict_T *const d = tv_dict_alloc();
- tv_list_append_dict(l, d);
- tv_dict_add_nr(d, S_LEN("lnum"), buf->b_changelist[i].mark.lnum);
- tv_dict_add_nr(d, S_LEN("col"), buf->b_changelist[i].mark.col);
- tv_dict_add_nr(d, S_LEN("coladd"), buf->b_changelist[i].mark.coladd);
- }
-}
-
-/*
- * "getchar()" function
- */
-static void f_getchar(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- varnumber_T n;
- bool error = false;
-
- no_mapping++;
- for (;; ) {
- // Position the cursor. Needed after a message that ends in a space,
- // or if event processing caused a redraw.
- ui_cursor_goto(msg_row, msg_col);
-
- if (argvars[0].v_type == VAR_UNKNOWN) {
- // getchar(): blocking wait.
- if (!(char_avail() || using_script() || input_available())) {
- (void)os_inchar(NULL, 0, -1, 0, main_loop.events);
- if (!multiqueue_empty(main_loop.events)) {
- multiqueue_process_events(main_loop.events);
- continue;
- }
- }
- n = safe_vgetc();
- } else if (tv_get_number_chk(&argvars[0], &error) == 1) {
- // getchar(1): only check if char avail
- n = vpeekc_any();
- } else if (error || vpeekc_any() == NUL) {
- // illegal argument or getchar(0) and no char avail: return zero
- n = 0;
- } else {
- // getchar(0) and char avail: return char
- n = safe_vgetc();
- }
-
- if (n == K_IGNORE) {
- continue;
- }
- break;
- }
- no_mapping--;
-
- vimvars[VV_MOUSE_WIN].vv_nr = 0;
- vimvars[VV_MOUSE_WINID].vv_nr = 0;
- vimvars[VV_MOUSE_LNUM].vv_nr = 0;
- vimvars[VV_MOUSE_COL].vv_nr = 0;
-
- rettv->vval.v_number = n;
- if (IS_SPECIAL(n) || mod_mask != 0) {
- char_u temp[10]; /* modifier: 3, mbyte-char: 6, NUL: 1 */
- int i = 0;
-
- /* Turn a special key into three bytes, plus modifier. */
- if (mod_mask != 0) {
- temp[i++] = K_SPECIAL;
- temp[i++] = KS_MODIFIER;
- temp[i++] = mod_mask;
- }
- if (IS_SPECIAL(n)) {
- temp[i++] = K_SPECIAL;
- temp[i++] = K_SECOND(n);
- temp[i++] = K_THIRD(n);
- } else {
- i += utf_char2bytes(n, temp + i);
- }
- temp[i++] = NUL;
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = vim_strsave(temp);
-
- if (is_mouse_key(n)) {
- int row = mouse_row;
- int col = mouse_col;
- int grid = mouse_grid;
- win_T *win;
- linenr_T lnum;
- win_T *wp;
- int winnr = 1;
-
- if (row >= 0 && col >= 0) {
- /* Find the window at the mouse coordinates and compute the
- * text position. */
- win = mouse_find_win(&grid, &row, &col);
- if (win == NULL) {
- return;
- }
- (void)mouse_comp_pos(win, &row, &col, &lnum);
- for (wp = firstwin; wp != win; wp = wp->w_next)
- ++winnr;
- vimvars[VV_MOUSE_WIN].vv_nr = winnr;
- vimvars[VV_MOUSE_WINID].vv_nr = wp->handle;
- vimvars[VV_MOUSE_LNUM].vv_nr = lnum;
- vimvars[VV_MOUSE_COL].vv_nr = col + 1;
- }
- }
- }
-}
-
-/*
- * "getcharmod()" function
- */
-static void f_getcharmod(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = mod_mask;
-}
-
-/*
- * "getcharsearch()" function
- */
-static void f_getcharsearch(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- tv_dict_alloc_ret(rettv);
-
- dict_T *dict = rettv->vval.v_dict;
-
- tv_dict_add_str(dict, S_LEN("char"), last_csearch());
- tv_dict_add_nr(dict, S_LEN("forward"), last_csearch_forward());
- tv_dict_add_nr(dict, S_LEN("until"), last_csearch_until());
-}
-
-/*
- * "getcmdline()" function
- */
-static void f_getcmdline(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = get_cmdline_str();
-}
-
-/*
- * "getcmdpos()" function
- */
-static void f_getcmdpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = get_cmdline_pos() + 1;
-}
-
-/*
- * "getcmdtype()" function
- */
-static void f_getcmdtype(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = xmallocz(1);
- rettv->vval.v_string[0] = get_cmdline_type();
-}
-
-/*
- * "getcmdwintype()" function
- */
-static void f_getcmdwintype(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = NULL;
- rettv->vval.v_string = xmallocz(1);
- rettv->vval.v_string[0] = cmdwin_type;
-}
-
-// "getcompletion()" function
-static void f_getcompletion(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- char_u *pat;
- expand_T xpc;
- bool filtered = false;
- int options = WILD_SILENT | WILD_USE_NL | WILD_ADD_SLASH
- | WILD_NO_BEEP;
-
- if (argvars[2].v_type != VAR_UNKNOWN) {
- filtered = (bool)tv_get_number_chk(&argvars[2], NULL);
- }
-
- if (p_wic) {
- options |= WILD_ICASE;
- }
-
- // For filtered results, 'wildignore' is used
- if (!filtered) {
- options |= WILD_KEEP_ALL;
- }
-
- if (argvars[0].v_type != VAR_STRING || argvars[1].v_type != VAR_STRING) {
- EMSG(_(e_invarg));
- return;
- }
-
- if (strcmp(tv_get_string(&argvars[1]), "cmdline") == 0) {
- set_one_cmd_context(&xpc, tv_get_string(&argvars[0]));
- xpc.xp_pattern_len = STRLEN(xpc.xp_pattern);
- goto theend;
- }
-
- ExpandInit(&xpc);
- xpc.xp_pattern = (char_u *)tv_get_string(&argvars[0]);
- xpc.xp_pattern_len = STRLEN(xpc.xp_pattern);
- xpc.xp_context = cmdcomplete_str_to_type(
- (char_u *)tv_get_string(&argvars[1]));
- if (xpc.xp_context == EXPAND_NOTHING) {
- EMSG2(_(e_invarg2), argvars[1].vval.v_string);
- return;
- }
-
- if (xpc.xp_context == EXPAND_MENUS) {
- set_context_in_menu_cmd(&xpc, (char_u *)"menu", xpc.xp_pattern, false);
- xpc.xp_pattern_len = STRLEN(xpc.xp_pattern);
- }
-
- if (xpc.xp_context == EXPAND_CSCOPE) {
- set_context_in_cscope_cmd(&xpc, (const char *)xpc.xp_pattern, CMD_cscope);
- xpc.xp_pattern_len = STRLEN(xpc.xp_pattern);
- }
-
- if (xpc.xp_context == EXPAND_SIGN) {
- set_context_in_sign_cmd(&xpc, xpc.xp_pattern);
- xpc.xp_pattern_len = STRLEN(xpc.xp_pattern);
- }
-
-theend:
- pat = addstar(xpc.xp_pattern, xpc.xp_pattern_len, xpc.xp_context);
- ExpandOne(&xpc, pat, NULL, options, WILD_ALL_KEEP);
- tv_list_alloc_ret(rettv, xpc.xp_numfiles);
-
- for (int i = 0; i < xpc.xp_numfiles; i++) {
- tv_list_append_string(rettv->vval.v_list, (const char *)xpc.xp_files[i],
- -1);
- }
- xfree(pat);
- ExpandCleanup(&xpc);
-}
-
-/// `getcwd([{win}[, {tab}]])` function
-///
-/// Every scope not specified implies the currently selected scope object.
-///
-/// @pre The arguments must be of type number.
-/// @pre There may not be more than two arguments.
-/// @pre An argument may not be -1 if preceding arguments are not all -1.
-///
-/// @post The return value will be a string.
-static void f_getcwd(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- // Possible scope of working directory to return.
- CdScope scope = kCdScopeInvalid;
-
- // Numbers of the scope objects (window, tab) we want the working directory
- // of. A `-1` means to skip this scope, a `0` means the current object.
- int scope_number[] = {
- [kCdScopeWindow] = 0, // Number of window to look at.
- [kCdScopeTab ] = 0, // Number of tab to look at.
- };
-
- char_u *cwd = NULL; // Current working directory to print
- char_u *from = NULL; // The original string to copy
-
- tabpage_T *tp = curtab; // The tabpage to look at.
- win_T *win = curwin; // The window to look at.
-
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = NULL;
-
- // Pre-conditions and scope extraction together
- for (int i = MIN_CD_SCOPE; i < MAX_CD_SCOPE; i++) {
- // If there is no argument there are no more scopes after it, break out.
- if (argvars[i].v_type == VAR_UNKNOWN) {
- break;
- }
- if (argvars[i].v_type != VAR_NUMBER) {
- EMSG(_(e_invarg));
- return;
- }
- scope_number[i] = argvars[i].vval.v_number;
- // It is an error for the scope number to be less than `-1`.
- if (scope_number[i] < -1) {
- EMSG(_(e_invarg));
- return;
- }
- // Use the narrowest scope the user requested
- if (scope_number[i] >= 0 && scope == kCdScopeInvalid) {
- // The scope is the current iteration step.
- scope = i;
- } else if (scope_number[i] < 0) {
- scope = i + 1;
- }
- }
-
- // If the user didn't specify anything, default to window scope
- if (scope == kCdScopeInvalid) {
- scope = MIN_CD_SCOPE;
- }
-
- // Find the tabpage by number
- if (scope_number[kCdScopeTab] > 0) {
- tp = find_tabpage(scope_number[kCdScopeTab]);
- if (!tp) {
- EMSG(_("E5000: Cannot find tab number."));
- return;
- }
- }
-
- // Find the window in `tp` by number, `NULL` if none.
- if (scope_number[kCdScopeWindow] >= 0) {
- if (scope_number[kCdScopeTab] < 0) {
- EMSG(_("E5001: Higher scope cannot be -1 if lower scope is >= 0."));
- return;
- }
-
- if (scope_number[kCdScopeWindow] > 0) {
- win = find_win_by_nr(&argvars[0], tp);
- if (!win) {
- EMSG(_("E5002: Cannot find window number."));
- return;
- }
- }
- }
-
- cwd = xmalloc(MAXPATHL);
-
- switch (scope) {
- case kCdScopeWindow:
- assert(win);
- from = win->w_localdir;
- if (from) {
- break;
- }
- FALLTHROUGH;
- case kCdScopeTab:
- assert(tp);
- from = tp->tp_localdir;
- if (from) {
- break;
- }
- FALLTHROUGH;
- case kCdScopeGlobal:
- if (globaldir) { // `globaldir` is not always set.
- from = globaldir;
- } else if (os_dirname(cwd, MAXPATHL) == FAIL) { // Get the OS CWD.
- from = (char_u *)""; // Return empty string on failure.
- }
- break;
- case kCdScopeInvalid: // We should never get here
- assert(false);
- }
-
- if (from) {
- xstrlcpy((char *)cwd, (char *)from, MAXPATHL);
- }
-
- rettv->vval.v_string = vim_strsave(cwd);
-#ifdef BACKSLASH_IN_FILENAME
- slash_adjust(rettv->vval.v_string);
-#endif
-
- xfree(cwd);
-}
-
-/*
- * "getfontname()" function
- */
-static void f_getfontname(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = NULL;
-}
-
-/*
- * "getfperm({fname})" function
- */
-static void f_getfperm(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- char *perm = NULL;
- char_u flags[] = "rwx";
-
- const char *filename = tv_get_string(&argvars[0]);
- int32_t file_perm = os_getperm(filename);
- if (file_perm >= 0) {
- perm = xstrdup("---------");
- for (int i = 0; i < 9; i++) {
- if (file_perm & (1 << (8 - i))) {
- perm[i] = flags[i % 3];
- }
- }
- }
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = (char_u *)perm;
-}
-
-/*
- * "getfsize({fname})" function
- */
-static void f_getfsize(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const char *fname = tv_get_string(&argvars[0]);
-
- rettv->v_type = VAR_NUMBER;
-
- FileInfo file_info;
- if (os_fileinfo(fname, &file_info)) {
- uint64_t filesize = os_fileinfo_size(&file_info);
- if (os_isdir((const char_u *)fname)) {
- rettv->vval.v_number = 0;
- } else {
- rettv->vval.v_number = (varnumber_T)filesize;
-
- /* non-perfect check for overflow */
- if ((uint64_t)rettv->vval.v_number != filesize) {
- rettv->vval.v_number = -2;
- }
- }
- } else {
- rettv->vval.v_number = -1;
- }
-}
-
-/*
- * "getftime({fname})" function
- */
-static void f_getftime(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const char *fname = tv_get_string(&argvars[0]);
-
- FileInfo file_info;
- if (os_fileinfo(fname, &file_info)) {
- rettv->vval.v_number = (varnumber_T)file_info.stat.st_mtim.tv_sec;
- } else {
- rettv->vval.v_number = -1;
- }
-}
-
-/*
- * "getftype({fname})" function
- */
-static void f_getftype(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- char_u *type = NULL;
- char *t;
-
- const char *fname = tv_get_string(&argvars[0]);
-
- rettv->v_type = VAR_STRING;
- FileInfo file_info;
- if (os_fileinfo_link(fname, &file_info)) {
- uint64_t mode = file_info.stat.st_mode;
-#ifdef S_ISREG
- if (S_ISREG(mode))
- t = "file";
- else if (S_ISDIR(mode))
- t = "dir";
-# ifdef S_ISLNK
- else if (S_ISLNK(mode))
- t = "link";
-# endif
-# ifdef S_ISBLK
- else if (S_ISBLK(mode))
- t = "bdev";
-# endif
-# ifdef S_ISCHR
- else if (S_ISCHR(mode))
- t = "cdev";
-# endif
-# ifdef S_ISFIFO
- else if (S_ISFIFO(mode))
- t = "fifo";
-# endif
-# ifdef S_ISSOCK
- else if (S_ISSOCK(mode))
- t = "socket";
-# endif
- else
- t = "other";
-#else
-# ifdef S_IFMT
- switch (mode & S_IFMT) {
- case S_IFREG: t = "file"; break;
- case S_IFDIR: t = "dir"; break;
-# ifdef S_IFLNK
- case S_IFLNK: t = "link"; break;
-# endif
-# ifdef S_IFBLK
- case S_IFBLK: t = "bdev"; break;
-# endif
-# ifdef S_IFCHR
- case S_IFCHR: t = "cdev"; break;
-# endif
-# ifdef S_IFIFO
- case S_IFIFO: t = "fifo"; break;
-# endif
-# ifdef S_IFSOCK
- case S_IFSOCK: t = "socket"; break;
-# endif
- default: t = "other";
- }
-# else
- if (os_isdir((const char_u *)fname)) {
- t = "dir";
- } else {
- t = "file";
- }
-# endif
-#endif
- type = vim_strsave((char_u *)t);
- }
- rettv->vval.v_string = type;
-}
-
-// "getjumplist()" function
-static void f_getjumplist(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- tv_list_alloc_ret(rettv, kListLenMayKnow);
- win_T *const wp = find_tabwin(&argvars[0], &argvars[1]);
- if (wp == NULL) {
- return;
- }
-
- cleanup_jumplist(wp, true);
-
- list_T *const l = tv_list_alloc(wp->w_jumplistlen);
- tv_list_append_list(rettv->vval.v_list, l);
- tv_list_append_number(rettv->vval.v_list, wp->w_jumplistidx);
-
- for (int i = 0; i < wp->w_jumplistlen; i++) {
- if (wp->w_jumplist[i].fmark.mark.lnum == 0) {
- continue;
- }
- dict_T *const d = tv_dict_alloc();
- tv_list_append_dict(l, d);
- tv_dict_add_nr(d, S_LEN("lnum"), wp->w_jumplist[i].fmark.mark.lnum);
- tv_dict_add_nr(d, S_LEN("col"), wp->w_jumplist[i].fmark.mark.col);
- tv_dict_add_nr(d, S_LEN("coladd"), wp->w_jumplist[i].fmark.mark.coladd);
- tv_dict_add_nr(d, S_LEN("bufnr"), wp->w_jumplist[i].fmark.fnum);
- if (wp->w_jumplist[i].fname != NULL) {
- tv_dict_add_str(d, S_LEN("filename"), (char *)wp->w_jumplist[i].fname);
- }
- }
-}
-
-/*
- * "getline(lnum, [end])" function
- */
-static void f_getline(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- linenr_T end;
- bool retlist;
-
- const linenr_T lnum = tv_get_lnum(argvars);
- if (argvars[1].v_type == VAR_UNKNOWN) {
- end = lnum;
- retlist = false;
- } else {
- end = tv_get_lnum(&argvars[1]);
- retlist = true;
- }
-
- get_buffer_lines(curbuf, lnum, end, retlist, rettv);
-}
-
-static void get_qf_loc_list(int is_qf, win_T *wp, typval_T *what_arg,
- typval_T *rettv)
+void get_qf_loc_list(int is_qf, win_T *wp, typval_T *what_arg,
+ typval_T *rettv)
{
if (what_arg->v_type == VAR_UNKNOWN) {
tv_list_alloc_ret(rettv, kListLenMayKnow);
@@ -10501,217 +6354,9 @@ static void get_qf_loc_list(int is_qf, win_T *wp, typval_T *what_arg,
}
}
-/// "getloclist()" function
-static void f_getloclist(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- win_T *wp = find_win_by_nr_or_id(&argvars[0]);
- get_qf_loc_list(false, wp, &argvars[1], rettv);
-}
-
-/*
- * "getmatches()" function
- */
-static void f_getmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- matchitem_T *cur = curwin->w_match_head;
- int i;
-
- tv_list_alloc_ret(rettv, kListLenMayKnow);
- while (cur != NULL) {
- dict_T *dict = tv_dict_alloc();
- if (cur->match.regprog == NULL) {
- // match added with matchaddpos()
- for (i = 0; i < MAXPOSMATCH; i++) {
- llpos_T *llpos;
- char buf[30]; // use 30 to avoid compiler warning
-
- llpos = &cur->pos.pos[i];
- if (llpos->lnum == 0) {
- break;
- }
- list_T *const l = tv_list_alloc(1 + (llpos->col > 0 ? 2 : 0));
- tv_list_append_number(l, (varnumber_T)llpos->lnum);
- if (llpos->col > 0) {
- tv_list_append_number(l, (varnumber_T)llpos->col);
- tv_list_append_number(l, (varnumber_T)llpos->len);
- }
- int len = snprintf(buf, sizeof(buf), "pos%d", i + 1);
- assert((size_t)len < sizeof(buf));
- tv_dict_add_list(dict, buf, (size_t)len, l);
- }
- } else {
- tv_dict_add_str(dict, S_LEN("pattern"), (const char *)cur->pattern);
- }
- tv_dict_add_str(dict, S_LEN("group"),
- (const char *)syn_id2name(cur->hlg_id));
- tv_dict_add_nr(dict, S_LEN("priority"), (varnumber_T)cur->priority);
- tv_dict_add_nr(dict, S_LEN("id"), (varnumber_T)cur->id);
-
- if (cur->conceal_char) {
- char buf[MB_MAXBYTES + 1];
-
- buf[utf_char2bytes((int)cur->conceal_char, (char_u *)buf)] = NUL;
- tv_dict_add_str(dict, S_LEN("conceal"), buf);
- }
-
- tv_list_append_dict(rettv->vval.v_list, dict);
- cur = cur->next;
- }
-}
-
-/*
- * "getpid()" function
- */
-static void f_getpid(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = os_get_pid();
-}
-
-static void getpos_both(typval_T *argvars, typval_T *rettv, bool getcurpos)
-{
- pos_T *fp;
- int fnum = -1;
-
- if (getcurpos) {
- fp = &curwin->w_cursor;
- } else {
- fp = var2fpos(&argvars[0], true, &fnum);
- }
-
- list_T *const l = tv_list_alloc_ret(rettv, 4 + (!!getcurpos));
- tv_list_append_number(l, (fnum != -1) ? (varnumber_T)fnum : (varnumber_T)0);
- tv_list_append_number(l, ((fp != NULL)
- ? (varnumber_T)fp->lnum
- : (varnumber_T)0));
- tv_list_append_number(
- l, ((fp != NULL)
- ? (varnumber_T)(fp->col == MAXCOL ? MAXCOL : fp->col + 1)
- : (varnumber_T)0));
- tv_list_append_number(
- l, (fp != NULL) ? (varnumber_T)fp->coladd : (varnumber_T)0);
- if (getcurpos) {
- const int save_set_curswant = curwin->w_set_curswant;
- const colnr_T save_curswant = curwin->w_curswant;
- const colnr_T save_virtcol = curwin->w_virtcol;
-
- update_curswant();
- tv_list_append_number(l, (curwin->w_curswant == MAXCOL
- ? (varnumber_T)MAXCOL
- : (varnumber_T)curwin->w_curswant + 1));
-
- // Do not change "curswant", as it is unexpected that a get
- // function has a side effect.
- if (save_set_curswant) {
- curwin->w_set_curswant = save_set_curswant;
- curwin->w_curswant = save_curswant;
- curwin->w_virtcol = save_virtcol;
- curwin->w_valid &= ~VALID_VIRTCOL;
- }
- }
-}
-
-/*
- * "getcurpos(string)" function
- */
-static void f_getcurpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- getpos_both(argvars, rettv, true);
-}
-
-/*
- * "getpos(string)" function
- */
-static void f_getpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- getpos_both(argvars, rettv, false);
-}
-
-/// "getqflist()" functions
-static void f_getqflist(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- get_qf_loc_list(true, NULL, &argvars[0], rettv);
-}
-
-/// "getreg()" function
-static void f_getreg(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const char *strregname;
- int arg2 = false;
- bool return_list = false;
- bool error = false;
-
- if (argvars[0].v_type != VAR_UNKNOWN) {
- strregname = tv_get_string_chk(&argvars[0]);
- error = strregname == NULL;
- if (argvars[1].v_type != VAR_UNKNOWN) {
- arg2 = tv_get_number_chk(&argvars[1], &error);
- if (!error && argvars[2].v_type != VAR_UNKNOWN) {
- return_list = tv_get_number_chk(&argvars[2], &error);
- }
- }
- } else {
- strregname = (const char *)vimvars[VV_REG].vv_str;
- }
-
- if (error) {
- return;
- }
-
- int regname = (uint8_t)(strregname == NULL ? '"' : *strregname);
- if (regname == 0) {
- regname = '"';
- }
-
- if (return_list) {
- rettv->v_type = VAR_LIST;
- rettv->vval.v_list =
- get_reg_contents(regname, (arg2 ? kGRegExprSrc : 0) | kGRegList);
- if (rettv->vval.v_list == NULL) {
- rettv->vval.v_list = tv_list_alloc(0);
- }
- tv_list_ref(rettv->vval.v_list);
- } else {
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = get_reg_contents(regname, arg2 ? kGRegExprSrc : 0);
- }
-}
-
-/*
- * "getregtype()" function
- */
-static void f_getregtype(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const char *strregname;
-
- if (argvars[0].v_type != VAR_UNKNOWN) {
- strregname = tv_get_string_chk(&argvars[0]);
- if (strregname == NULL) { // Type error; errmsg already given.
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = NULL;
- return;
- }
- } else {
- // Default to v:register.
- strregname = (const char *)vimvars[VV_REG].vv_str;
- }
-
- int regname = (uint8_t)(strregname == NULL ? '"' : *strregname);
- if (regname == 0) {
- regname = '"';
- }
-
- colnr_T reglen = 0;
- char buf[NUMBUFLEN + 2];
- MotionType reg_type = get_reg_type(regname, &reglen);
- format_reg_type(reg_type, reglen, buf, ARRAY_SIZE(buf));
-
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = (char_u *)xstrdup(buf);
-}
-
/// Returns information (variables, options, etc.) about a tab page
/// as a dictionary.
-static dict_T *get_tabpage_info(tabpage_T *tp, int tp_idx)
+dict_T *get_tabpage_info(tabpage_T *tp, int tp_idx)
{
dict_T *const dict = tv_dict_alloc();
@@ -10729,106 +6374,8 @@ static dict_T *get_tabpage_info(tabpage_T *tp, int tp_idx)
return dict;
}
-/// "gettabinfo()" function
-static void f_gettabinfo(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- tabpage_T *tparg = NULL;
-
- tv_list_alloc_ret(rettv, (argvars[0].v_type == VAR_UNKNOWN
- ? 1
- : kListLenMayKnow));
-
- if (argvars[0].v_type != VAR_UNKNOWN) {
- // Information about one tab page
- tparg = find_tabpage((int)tv_get_number_chk(&argvars[0], NULL));
- if (tparg == NULL) {
- return;
- }
- }
-
- // Get information about a specific tab page or all tab pages
- int tpnr = 0;
- FOR_ALL_TABS(tp) {
- tpnr++;
- if (tparg != NULL && tp != tparg) {
- continue;
- }
- dict_T *const d = get_tabpage_info(tp, tpnr);
- tv_list_append_dict(rettv->vval.v_list, d);
- if (tparg != NULL) {
- return;
- }
- }
-}
-
-/*
- * "gettabvar()" function
- */
-static void f_gettabvar(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- win_T *oldcurwin;
- tabpage_T *oldtabpage;
- bool done = false;
-
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = NULL;
-
- const char *const varname = tv_get_string_chk(&argvars[1]);
- tabpage_T *const tp = find_tabpage((int)tv_get_number_chk(&argvars[0], NULL));
- if (tp != NULL && varname != NULL) {
- // Set tp to be our tabpage, temporarily. Also set the window to the
- // first window in the tabpage, otherwise the window is not valid.
- win_T *const window = tp == curtab || tp->tp_firstwin == NULL
- ? firstwin
- : tp->tp_firstwin;
- if (switch_win(&oldcurwin, &oldtabpage, window, tp, true) == OK) {
- // look up the variable
- // Let gettabvar({nr}, "") return the "t:" dictionary.
- const dictitem_T *const v = find_var_in_ht(&tp->tp_vars->dv_hashtab, 't',
- varname, strlen(varname),
- false);
- if (v != NULL) {
- tv_copy(&v->di_tv, rettv);
- done = true;
- }
- }
-
- // restore previous notion of curwin
- restore_win(oldcurwin, oldtabpage, true);
- }
-
- if (!done && argvars[2].v_type != VAR_UNKNOWN) {
- tv_copy(&argvars[2], rettv);
- }
-}
-
-/*
- * "gettabwinvar()" function
- */
-static void f_gettabwinvar(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- getwinvar(argvars, rettv, 1);
-}
-
-// "gettagstack()" function
-static void f_gettagstack(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- win_T *wp = curwin; // default is current window
-
- tv_dict_alloc_ret(rettv);
-
- if (argvars[0].v_type != VAR_UNKNOWN) {
- wp = find_win_by_nr_or_id(&argvars[0]);
- if (wp == NULL) {
- return;
- }
- }
-
- get_tagstack(wp, rettv->vval.v_dict);
-}
-
/// Returns information about a window as a dictionary.
-static dict_T *get_win_info(win_T *wp, int16_t tpnr, int16_t winnr)
+dict_T *get_win_info(win_T *wp, int16_t tpnr, int16_t winnr)
{
dict_T *const dict = tv_dict_alloc();
@@ -10839,6 +6386,7 @@ static dict_T *get_win_info(win_T *wp, int16_t tpnr, int16_t winnr)
tv_dict_add_nr(dict, S_LEN("winrow"), wp->w_winrow + 1);
tv_dict_add_nr(dict, S_LEN("topline"), wp->w_topline);
tv_dict_add_nr(dict, S_LEN("botline"), wp->w_botline - 1);
+ tv_dict_add_nr(dict, S_LEN("winbar"), wp->w_winbar_height);
tv_dict_add_nr(dict, S_LEN("width"), wp->w_width);
tv_dict_add_nr(dict, S_LEN("bufnr"), wp->w_buffer->b_fnum);
tv_dict_add_nr(dict, S_LEN("wincol"), wp->w_wincol + 1);
@@ -10854,149 +6402,11 @@ static dict_T *get_win_info(win_T *wp, int16_t tpnr, int16_t winnr)
return dict;
}
-/// "getwininfo()" function
-static void f_getwininfo(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- win_T *wparg = NULL;
-
- tv_list_alloc_ret(rettv, kListLenMayKnow);
-
- if (argvars[0].v_type != VAR_UNKNOWN) {
- wparg = win_id2wp(argvars);
- if (wparg == NULL) {
- return;
- }
- }
-
- // Collect information about either all the windows across all the tab
- // pages or one particular window.
- int16_t tabnr = 0;
- FOR_ALL_TABS(tp) {
- tabnr++;
- int16_t winnr = 0;
- FOR_ALL_WINDOWS_IN_TAB(wp, tp) {
- winnr++;
- if (wparg != NULL && wp != wparg) {
- continue;
- }
- dict_T *const d = get_win_info(wp, tabnr, winnr);
- tv_list_append_dict(rettv->vval.v_list, d);
- if (wparg != NULL) {
- // found information about a specific window
- return;
- }
- }
- }
-}
-
-// Dummy timer callback. Used by f_wait().
-static void dummy_timer_due_cb(TimeWatcher *tw, void *data)
-{
-}
-
-// Dummy timer close callback. Used by f_wait().
-static void dummy_timer_close_cb(TimeWatcher *tw, void *data)
-{
- xfree(tw);
-}
-
-/// "wait(timeout, condition[, interval])" function
-static void f_wait(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->v_type = VAR_NUMBER;
- rettv->vval.v_number = -1;
-
- if (argvars[0].v_type != VAR_NUMBER) {
- EMSG2(_(e_invargval), "1");
- return;
- }
- if ((argvars[2].v_type != VAR_NUMBER && argvars[2].v_type != VAR_UNKNOWN)
- || (argvars[2].v_type == VAR_NUMBER && argvars[2].vval.v_number <= 0)) {
- EMSG2(_(e_invargval), "3");
- return;
- }
-
- int timeout = argvars[0].vval.v_number;
- typval_T expr = argvars[1];
- int interval = argvars[2].v_type == VAR_NUMBER
- ? argvars[2].vval.v_number
- : 200; // Default.
- TimeWatcher *tw = xmalloc(sizeof(TimeWatcher));
-
- // Start dummy timer.
- time_watcher_init(&main_loop, tw, NULL);
- tw->events = main_loop.events;
- tw->blockable = true;
- time_watcher_start(tw, dummy_timer_due_cb, interval, interval);
-
- typval_T argv = TV_INITIAL_VALUE;
- typval_T exprval = TV_INITIAL_VALUE;
- bool error = false;
- int save_called_emsg = called_emsg;
- called_emsg = false;
-
- LOOP_PROCESS_EVENTS_UNTIL(&main_loop, main_loop.events, timeout,
- eval_expr_typval(&expr, &argv, 0, &exprval) != OK
- || tv_get_number_chk(&exprval, &error)
- || called_emsg || error || got_int);
-
- if (called_emsg || error) {
- rettv->vval.v_number = -3;
- } else if (got_int) {
- got_int = false;
- vgetc();
- rettv->vval.v_number = -2;
- } else if (tv_get_number_chk(&exprval, &error)) {
- rettv->vval.v_number = 0;
- }
-
- called_emsg = save_called_emsg;
-
- // Stop dummy timer
- time_watcher_stop(tw);
- time_watcher_close(tw, dummy_timer_close_cb);
-}
-
-// "win_screenpos()" function
-static void f_win_screenpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- tv_list_alloc_ret(rettv, 2);
- const win_T *const wp = find_win_by_nr_or_id(&argvars[0]);
- tv_list_append_number(rettv->vval.v_list, wp == NULL ? 0 : wp->w_winrow + 1);
- tv_list_append_number(rettv->vval.v_list, wp == NULL ? 0 : wp->w_wincol + 1);
-}
-
-// "getwinpos({timeout})" function
-static void f_getwinpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- tv_list_alloc_ret(rettv, 2);
- tv_list_append_number(rettv->vval.v_list, -1);
- tv_list_append_number(rettv->vval.v_list, -1);
-}
-
-/*
- * "getwinposx()" function
- */
-static void f_getwinposx(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = -1;
-}
-
-/*
- * "getwinposy()" function
- */
-static void f_getwinposy(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = -1;
-}
-
-/*
- * Find window specified by "vp" in tabpage "tp".
- */
-static win_T *
-find_win_by_nr (
+// Find window specified by "vp" in tabpage "tp".
+win_T *
+find_win_by_nr(
typval_T *vp,
- tabpage_T *tp /* NULL for current tab page */
+ tabpage_T *tp // NULL for current tab page
)
{
int nr = (int)tv_get_number_chk(vp, NULL);
@@ -11027,7 +6437,7 @@ find_win_by_nr (
}
/// Find window specified by "wvp" in tabpage "tvp".
-static win_T *find_tabwin(typval_T *wvp, typval_T *tvp)
+win_T *find_tabwin(typval_T *wvp, typval_T *tvp)
{
win_T *wp = NULL;
tabpage_T *tp = NULL;
@@ -11052,20 +6462,14 @@ static win_T *find_tabwin(typval_T *wvp, typval_T *tvp)
return wp;
}
-/// "getwinvar()" function
-static void f_getwinvar(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- getwinvar(argvars, rettv, 0);
-}
-
/*
* getwinvar() and gettabwinvar()
*/
-static void
+void
getwinvar(
typval_T *argvars,
typval_T *rettv,
- int off /* 1 for gettabwinvar() */
+ int off // 1 for gettabwinvar()
)
{
win_T *win, *oldcurwin;
@@ -11132,657 +6536,15 @@ getwinvar(
}
/*
- * "glob()" function
- */
-static void f_glob(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- int options = WILD_SILENT|WILD_USE_NL;
- expand_T xpc;
- bool error = false;
-
- /* When the optional second argument is non-zero, don't remove matches
- * for 'wildignore' and don't put matches for 'suffixes' at the end. */
- rettv->v_type = VAR_STRING;
- if (argvars[1].v_type != VAR_UNKNOWN) {
- if (tv_get_number_chk(&argvars[1], &error)) {
- options |= WILD_KEEP_ALL;
- }
- if (argvars[2].v_type != VAR_UNKNOWN) {
- if (tv_get_number_chk(&argvars[2], &error)) {
- tv_list_set_ret(rettv, NULL);
- }
- if (argvars[3].v_type != VAR_UNKNOWN
- && tv_get_number_chk(&argvars[3], &error)) {
- options |= WILD_ALLLINKS;
- }
- }
- }
- if (!error) {
- ExpandInit(&xpc);
- xpc.xp_context = EXPAND_FILES;
- if (p_wic)
- options += WILD_ICASE;
- if (rettv->v_type == VAR_STRING) {
- rettv->vval.v_string = ExpandOne(
- &xpc, (char_u *)tv_get_string(&argvars[0]), NULL, options, WILD_ALL);
- } else {
- ExpandOne(&xpc, (char_u *)tv_get_string(&argvars[0]), NULL, options,
- WILD_ALL_KEEP);
- tv_list_alloc_ret(rettv, xpc.xp_numfiles);
- for (int i = 0; i < xpc.xp_numfiles; i++) {
- tv_list_append_string(rettv->vval.v_list, (const char *)xpc.xp_files[i],
- -1);
- }
- ExpandCleanup(&xpc);
- }
- } else
- rettv->vval.v_string = NULL;
-}
-
-/// "globpath()" function
-static void f_globpath(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- int flags = 0; // Flags for globpath.
- bool error = false;
-
- // Return a string, or a list if the optional third argument is non-zero.
- rettv->v_type = VAR_STRING;
-
- if (argvars[2].v_type != VAR_UNKNOWN) {
- // When the optional second argument is non-zero, don't remove matches
- // for 'wildignore' and don't put matches for 'suffixes' at the end.
- if (tv_get_number_chk(&argvars[2], &error)) {
- flags |= WILD_KEEP_ALL;
- }
-
- if (argvars[3].v_type != VAR_UNKNOWN) {
- if (tv_get_number_chk(&argvars[3], &error)) {
- tv_list_set_ret(rettv, NULL);
- }
- if (argvars[4].v_type != VAR_UNKNOWN
- && tv_get_number_chk(&argvars[4], &error)) {
- flags |= WILD_ALLLINKS;
- }
- }
- }
-
- char buf1[NUMBUFLEN];
- const char *const file = tv_get_string_buf_chk(&argvars[1], buf1);
- if (file != NULL && !error) {
- garray_T ga;
- ga_init(&ga, (int)sizeof(char_u *), 10);
- globpath((char_u *)tv_get_string(&argvars[0]), (char_u *)file, &ga, flags);
-
- if (rettv->v_type == VAR_STRING) {
- rettv->vval.v_string = ga_concat_strings_sep(&ga, "\n");
- } else {
- tv_list_alloc_ret(rettv, ga.ga_len);
- for (int i = 0; i < ga.ga_len; i++) {
- tv_list_append_string(rettv->vval.v_list,
- ((const char **)(ga.ga_data))[i], -1);
- }
- }
-
- ga_clear_strings(&ga);
- } else {
- rettv->vval.v_string = NULL;
- }
-}
-
-// "glob2regpat()" function
-static void f_glob2regpat(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const char *const pat = tv_get_string_chk(&argvars[0]); // NULL on type error
-
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = ((pat == NULL)
- ? NULL
- : file_pat_to_reg_pat((char_u *)pat, NULL, NULL,
- false));
-}
-
-/// "has()" function
-static void f_has(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- static const char *const has_list[] = {
-#ifdef UNIX
- "unix",
-#endif
-#if defined(WIN32)
- "win32",
-#endif
-#if defined(WIN64) || defined(_WIN64)
- "win64",
-#endif
- "fname_case",
-#ifdef HAVE_ACL
- "acl",
-#endif
- "autochdir",
- "arabic",
- "autocmd",
- "browsefilter",
- "byte_offset",
- "cindent",
- "cmdline_compl",
- "cmdline_hist",
- "comments",
- "conceal",
- "cscope",
- "cursorbind",
- "cursorshape",
-#ifdef DEBUG
- "debug",
-#endif
- "dialog_con",
- "diff",
- "digraphs",
- "eval", /* always present, of course! */
- "ex_extra",
- "extra_search",
- "file_in_path",
- "filterpipe",
- "find_in_path",
- "float",
- "folding",
-#if defined(UNIX)
- "fork",
-#endif
- "gettext",
-#if defined(HAVE_ICONV)
- "iconv",
-#endif
- "insert_expand",
- "jumplist",
- "keymap",
- "lambda",
- "langmap",
- "libcall",
- "linebreak",
- "lispindent",
- "listcmds",
- "localmap",
-#ifdef __APPLE__
- "mac",
- "macunix",
- "osx",
- "osxdarwin",
-#endif
- "menu",
- "mksession",
- "modify_fname",
- "mouse",
- "multi_byte",
- "multi_lang",
- "num64",
- "packages",
- "path_extra",
- "persistent_undo",
- "postscript",
- "printer",
- "profile",
- "pythonx",
- "reltime",
- "quickfix",
- "rightleft",
- "scrollbind",
- "showcmd",
- "cmdline_info",
- "shada",
- "signs",
- "smartindent",
- "startuptime",
- "statusline",
- "spell",
- "syntax",
-#if !defined(UNIX)
- "system", // TODO(SplinterOfChaos): This IS defined for UNIX!
-#endif
- "tablineat",
- "tag_binary",
- "termguicolors",
- "termresponse",
- "textobjects",
- "timers",
- "title",
- "user-commands", /* was accidentally included in 5.4 */
- "user_commands",
- "vertsplit",
- "virtualedit",
- "visual",
- "visualextra",
- "vreplace",
- "wildignore",
- "wildmenu",
- "windows",
- "winaltkeys",
- "writebackup",
-#if defined(HAVE_WSL)
- "wsl",
-#endif
- "nvim",
- };
-
- bool n = false;
- const char *const name = tv_get_string(&argvars[0]);
- for (size_t i = 0; i < ARRAY_SIZE(has_list); i++) {
- if (STRICMP(name, has_list[i]) == 0) {
- n = true;
- break;
- }
- }
-
- if (!n) {
- if (STRNICMP(name, "patch", 5) == 0) {
- if (name[5] == '-'
- && strlen(name) >= 11
- && ascii_isdigit(name[6])
- && ascii_isdigit(name[8])
- && ascii_isdigit(name[10])) {
- int major = atoi(name + 6);
- int minor = atoi(name + 8);
-
- // Expect "patch-9.9.01234".
- n = (major < VIM_VERSION_MAJOR
- || (major == VIM_VERSION_MAJOR
- && (minor < VIM_VERSION_MINOR
- || (minor == VIM_VERSION_MINOR
- && has_vim_patch(atoi(name + 10))))));
- } else {
- n = has_vim_patch(atoi(name + 5));
- }
- } else if (STRNICMP(name, "nvim-", 5) == 0) {
- // Expect "nvim-x.y.z"
- n = has_nvim_version(name + 5);
- } else if (STRICMP(name, "vim_starting") == 0) {
- n = (starting != 0);
- } else if (STRICMP(name, "ttyin") == 0) {
- n = stdin_isatty;
- } else if (STRICMP(name, "ttyout") == 0) {
- n = stdout_isatty;
- } else if (STRICMP(name, "multi_byte_encoding") == 0) {
- n = has_mbyte != 0;
- } else if (STRICMP(name, "syntax_items") == 0) {
- n = syntax_present(curwin);
-#ifdef UNIX
- } else if (STRICMP(name, "unnamedplus") == 0) {
- n = eval_has_provider("clipboard");
-#endif
- }
- }
-
- if (!n && eval_has_provider(name)) {
- n = true;
- }
-
- rettv->vval.v_number = n;
-}
-
-/*
- * "has_key()" function
- */
-static void f_has_key(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- if (argvars[0].v_type != VAR_DICT) {
- EMSG(_(e_dictreq));
- return;
- }
- if (argvars[0].vval.v_dict == NULL)
- return;
-
- rettv->vval.v_number = tv_dict_find(argvars[0].vval.v_dict,
- tv_get_string(&argvars[1]),
- -1) != NULL;
-}
-
-/// `haslocaldir([{win}[, {tab}]])` function
-///
-/// Returns `1` if the scope object has a local directory, `0` otherwise. If a
-/// scope object is not specified the current one is implied. This function
-/// share a lot of code with `f_getcwd`.
-///
-/// @pre The arguments must be of type number.
-/// @pre There may not be more than two arguments.
-/// @pre An argument may not be -1 if preceding arguments are not all -1.
-///
-/// @post The return value will be either the number `1` or `0`.
-static void f_haslocaldir(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- // Possible scope of working directory to return.
- CdScope scope = kCdScopeInvalid;
-
- // Numbers of the scope objects (window, tab) we want the working directory
- // of. A `-1` means to skip this scope, a `0` means the current object.
- int scope_number[] = {
- [kCdScopeWindow] = 0, // Number of window to look at.
- [kCdScopeTab ] = 0, // Number of tab to look at.
- };
-
- tabpage_T *tp = curtab; // The tabpage to look at.
- win_T *win = curwin; // The window to look at.
-
- rettv->v_type = VAR_NUMBER;
- rettv->vval.v_number = 0;
-
- // Pre-conditions and scope extraction together
- for (int i = MIN_CD_SCOPE; i < MAX_CD_SCOPE; i++) {
- if (argvars[i].v_type == VAR_UNKNOWN) {
- break;
- }
- if (argvars[i].v_type != VAR_NUMBER) {
- EMSG(_(e_invarg));
- return;
- }
- scope_number[i] = argvars[i].vval.v_number;
- if (scope_number[i] < -1) {
- EMSG(_(e_invarg));
- return;
- }
- // Use the narrowest scope the user requested
- if (scope_number[i] >= 0 && scope == kCdScopeInvalid) {
- // The scope is the current iteration step.
- scope = i;
- } else if (scope_number[i] < 0) {
- scope = i + 1;
- }
- }
-
- // If the user didn't specify anything, default to window scope
- if (scope == kCdScopeInvalid) {
- scope = MIN_CD_SCOPE;
- }
-
- // Find the tabpage by number
- if (scope_number[kCdScopeTab] > 0) {
- tp = find_tabpage(scope_number[kCdScopeTab]);
- if (!tp) {
- EMSG(_("E5000: Cannot find tab number."));
- return;
- }
- }
-
- // Find the window in `tp` by number, `NULL` if none.
- if (scope_number[kCdScopeWindow] >= 0) {
- if (scope_number[kCdScopeTab] < 0) {
- EMSG(_("E5001: Higher scope cannot be -1 if lower scope is >= 0."));
- return;
- }
-
- if (scope_number[kCdScopeWindow] > 0) {
- win = find_win_by_nr(&argvars[0], tp);
- if (!win) {
- EMSG(_("E5002: Cannot find window number."));
- return;
- }
- }
- }
-
- switch (scope) {
- case kCdScopeWindow:
- assert(win);
- rettv->vval.v_number = win->w_localdir ? 1 : 0;
- break;
- case kCdScopeTab:
- assert(tp);
- rettv->vval.v_number = tp->tp_localdir ? 1 : 0;
- break;
- case kCdScopeGlobal:
- // The global scope never has a local directory
- rettv->vval.v_number = 0;
- break;
- case kCdScopeInvalid:
- // We should never get here
- assert(false);
- }
-}
-
-/*
- * "hasmapto()" function
- */
-static void f_hasmapto(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const char *mode;
- const char *const name = tv_get_string(&argvars[0]);
- bool abbr = false;
- char buf[NUMBUFLEN];
- if (argvars[1].v_type == VAR_UNKNOWN) {
- mode = "nvo";
- } else {
- mode = tv_get_string_buf(&argvars[1], buf);
- if (argvars[2].v_type != VAR_UNKNOWN) {
- abbr = tv_get_number(&argvars[2]);
- }
- }
-
- if (map_to_exists(name, mode, abbr)) {
- rettv->vval.v_number = true;
- } else {
- rettv->vval.v_number = false;
- }
-}
-
-/*
- * "histadd()" function
- */
-static void f_histadd(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- HistoryType histype;
-
- rettv->vval.v_number = false;
- if (check_restricted() || check_secure()) {
- return;
- }
- const char *str = tv_get_string_chk(&argvars[0]); // NULL on type error
- histype = str != NULL ? get_histtype(str, strlen(str), false) : HIST_INVALID;
- if (histype != HIST_INVALID) {
- char buf[NUMBUFLEN];
- str = tv_get_string_buf(&argvars[1], buf);
- if (*str != NUL) {
- init_history();
- add_to_history(histype, (char_u *)str, false, NUL);
- rettv->vval.v_number = true;
- return;
- }
- }
-}
-
-/*
- * "histdel()" function
- */
-static void f_histdel(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- int n;
- const char *const str = tv_get_string_chk(&argvars[0]); // NULL on type error
- if (str == NULL) {
- n = 0;
- } else if (argvars[1].v_type == VAR_UNKNOWN) {
- // only one argument: clear entire history
- n = clr_history(get_histtype(str, strlen(str), false));
- } else if (argvars[1].v_type == VAR_NUMBER) {
- // index given: remove that entry
- n = del_history_idx(get_histtype(str, strlen(str), false),
- (int)tv_get_number(&argvars[1]));
- } else {
- // string given: remove all matching entries
- char buf[NUMBUFLEN];
- n = del_history_entry(get_histtype(str, strlen(str), false),
- (char_u *)tv_get_string_buf(&argvars[1], buf));
- }
- rettv->vval.v_number = n;
-}
-
-/*
- * "histget()" function
- */
-static void f_histget(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- HistoryType type;
- int idx;
-
- const char *const str = tv_get_string_chk(&argvars[0]); // NULL on type error
- if (str == NULL) {
- rettv->vval.v_string = NULL;
- } else {
- type = get_histtype(str, strlen(str), false);
- if (argvars[1].v_type == VAR_UNKNOWN) {
- idx = get_history_idx(type);
- } else {
- idx = (int)tv_get_number_chk(&argvars[1], NULL);
- }
- // -1 on type error
- rettv->vval.v_string = vim_strsave(get_history_entry(type, idx));
- }
- rettv->v_type = VAR_STRING;
-}
-
-/*
- * "histnr()" function
- */
-static void f_histnr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- int i;
-
- const char *const history = tv_get_string_chk(&argvars[0]);
-
- i = history == NULL ? HIST_CMD - 1 : get_histtype(history, strlen(history),
- false);
- if (i != HIST_INVALID) {
- i = get_history_idx(i);
- } else {
- i = -1;
- }
- rettv->vval.v_number = i;
-}
-
-/*
- * "highlightID(name)" function
- */
-static void f_hlID(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = syn_name2id(
- (const char_u *)tv_get_string(&argvars[0]));
-}
-
-/*
- * "highlight_exists()" function
- */
-static void f_hlexists(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = highlight_exists(
- (const char_u *)tv_get_string(&argvars[0]));
-}
-
-/*
- * "hostname()" function
- */
-static void f_hostname(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- char hostname[256];
-
- os_get_hostname(hostname, 256);
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = vim_strsave((char_u *)hostname);
-}
-
-/*
- * iconv() function
- */
-static void f_iconv(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- vimconv_T vimconv;
-
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = NULL;
-
- const char *const str = tv_get_string(&argvars[0]);
- char buf1[NUMBUFLEN];
- char_u *const from = enc_canonize(enc_skip(
- (char_u *)tv_get_string_buf(&argvars[1], buf1)));
- char buf2[NUMBUFLEN];
- char_u *const to = enc_canonize(enc_skip(
- (char_u *)tv_get_string_buf(&argvars[2], buf2)));
- vimconv.vc_type = CONV_NONE;
- convert_setup(&vimconv, from, to);
-
- // If the encodings are equal, no conversion needed.
- if (vimconv.vc_type == CONV_NONE) {
- rettv->vval.v_string = (char_u *)xstrdup(str);
- } else {
- rettv->vval.v_string = string_convert(&vimconv, (char_u *)str, NULL);
- }
-
- convert_setup(&vimconv, NULL, NULL);
- xfree(from);
- xfree(to);
-}
-
-/*
- * "indent()" function
- */
-static void f_indent(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const linenr_T lnum = tv_get_lnum(argvars);
- if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count) {
- rettv->vval.v_number = get_indent_lnum(lnum);
- } else {
- rettv->vval.v_number = -1;
- }
-}
-
-/*
- * "index()" function
- */
-static void f_index(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- long idx = 0;
- bool ic = false;
-
- rettv->vval.v_number = -1;
- if (argvars[0].v_type != VAR_LIST) {
- EMSG(_(e_listreq));
- return;
- }
- list_T *const l = argvars[0].vval.v_list;
- if (l != NULL) {
- listitem_T *item = tv_list_first(l);
- if (argvars[2].v_type != VAR_UNKNOWN) {
- bool error = false;
-
- // Start at specified item.
- idx = tv_list_uidx(l, tv_get_number_chk(&argvars[2], &error));
- if (error || idx == -1) {
- item = NULL;
- } else {
- item = tv_list_find(l, idx);
- assert(item != NULL);
- }
- if (argvars[3].v_type != VAR_UNKNOWN) {
- ic = !!tv_get_number_chk(&argvars[3], &error);
- if (error) {
- item = NULL;
- }
- }
- }
-
- for (; item != NULL; item = TV_LIST_ITEM_NEXT(l, item), idx++) {
- if (tv_equal(TV_LIST_ITEM_TV(item), &argvars[1], ic, false)) {
- rettv->vval.v_number = idx;
- break;
- }
- }
- }
-}
-
-static int inputsecret_flag = 0;
-
-/*
* This function is used by f_input() and f_inputdialog() functions. The third
* argument to f_input() specifies the type of completion to use at the
* prompt. The third argument to f_inputdialog() specifies the value to return
* when the user cancels the prompt.
*/
void get_user_input(const typval_T *const argvars,
- typval_T *const rettv, const bool inputdialog)
+ typval_T *const rettv,
+ const bool inputdialog,
+ const bool secret)
FUNC_ATTR_NONNULL_ALL
{
rettv->v_type = VAR_STRING;
@@ -11893,7 +6655,7 @@ void get_user_input(const typval_T *const argvars,
const int save_ex_normal_busy = ex_normal_busy;
ex_normal_busy = 0;
rettv->vval.v_string =
- (char_u *)getcmdline_prompt(inputsecret_flag ? NUL : '@', p, echo_attr,
+ (char_u *)getcmdline_prompt(secret ? NUL : '@', p, echo_attr,
xp_type, xp_arg, input_callback);
ex_normal_busy = save_ex_normal_busy;
callback_free(&input_callback);
@@ -11910,212 +6672,14 @@ void get_user_input(const typval_T *const argvars,
cmd_silent = cmd_silent_save;
}
-/*
- * "input()" function
- * Also handles inputsecret() when inputsecret is set.
- */
-static void f_input(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- get_user_input(argvars, rettv, FALSE);
-}
-
-/*
- * "inputdialog()" function
- */
-static void f_inputdialog(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- get_user_input(argvars, rettv, TRUE);
-}
-
-/*
- * "inputlist()" function
- */
-static void f_inputlist(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- int selected;
- int mouse_used;
-
- if (argvars[0].v_type != VAR_LIST) {
- EMSG2(_(e_listarg), "inputlist()");
- return;
- }
-
- msg_start();
- msg_row = Rows - 1; /* for when 'cmdheight' > 1 */
- lines_left = Rows; /* avoid more prompt */
- msg_scroll = TRUE;
- msg_clr_eos();
-
- TV_LIST_ITER_CONST(argvars[0].vval.v_list, li, {
- msg_puts(tv_get_string(TV_LIST_ITEM_TV(li)));
- msg_putchar('\n');
- });
-
- // Ask for choice.
- selected = prompt_for_number(&mouse_used);
- if (mouse_used) {
- selected -= lines_left;
- }
-
- rettv->vval.v_number = selected;
-}
-
-
-static garray_T ga_userinput = {0, 0, sizeof(tasave_T), 4, NULL};
-
-/// "inputrestore()" function
-static void f_inputrestore(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- if (!GA_EMPTY(&ga_userinput)) {
- ga_userinput.ga_len--;
- restore_typeahead((tasave_T *)(ga_userinput.ga_data)
- + ga_userinput.ga_len);
- // default return is zero == OK
- } else if (p_verbose > 1) {
- verb_msg(_("called inputrestore() more often than inputsave()"));
- rettv->vval.v_number = 1; // Failed
- }
-}
-
-/// "inputsave()" function
-static void f_inputsave(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- // Add an entry to the stack of typeahead storage.
- tasave_T *p = GA_APPEND_VIA_PTR(tasave_T, &ga_userinput);
- save_typeahead(p);
-}
-
-/// "inputsecret()" function
-static void f_inputsecret(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- cmdline_star++;
- inputsecret_flag++;
- f_input(argvars, rettv, NULL);
- cmdline_star--;
- inputsecret_flag--;
-}
-
-/*
- * "insert()" function
- */
-static void f_insert(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- list_T *l;
- bool error = false;
-
- if (argvars[0].v_type != VAR_LIST) {
- EMSG2(_(e_listarg), "insert()");
- } else if (!tv_check_lock(tv_list_locked((l = argvars[0].vval.v_list)),
- N_("insert() argument"), TV_TRANSLATE)) {
- long before = 0;
- if (argvars[2].v_type != VAR_UNKNOWN) {
- before = tv_get_number_chk(&argvars[2], &error);
- }
- if (error) {
- // type error; errmsg already given
- return;
- }
-
- listitem_T *item = NULL;
- if (before != tv_list_len(l)) {
- item = tv_list_find(l, before);
- if (item == NULL) {
- EMSGN(_(e_listidx), before);
- l = NULL;
- }
- }
- if (l != NULL) {
- tv_list_insert_tv(l, &argvars[1], item);
- tv_copy(&argvars[0], rettv);
- }
- }
-}
-
-/*
- * "invert(expr)" function
- */
-static void f_invert(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = ~tv_get_number_chk(&argvars[0], NULL);
-}
-
-/*
- * "isdirectory()" function
- */
-static void f_isdirectory(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = os_isdir((const char_u *)tv_get_string(&argvars[0]));
-}
-
-/*
- * "islocked()" function
- */
-static void f_islocked(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- lval_T lv;
- dictitem_T *di;
-
- rettv->vval.v_number = -1;
- const char_u *const end = get_lval((char_u *)tv_get_string(&argvars[0]),
- NULL,
- &lv, false, false,
- GLV_NO_AUTOLOAD|GLV_READ_ONLY,
- FNE_CHECK_START);
- if (end != NULL && lv.ll_name != NULL) {
- if (*end != NUL) {
- EMSG(_(e_trailing));
- } else {
- if (lv.ll_tv == NULL) {
- di = find_var((const char *)lv.ll_name, lv.ll_name_len, NULL, true);
- if (di != NULL) {
- // Consider a variable locked when:
- // 1. the variable itself is locked
- // 2. the value of the variable is locked.
- // 3. the List or Dict value is locked.
- rettv->vval.v_number = ((di->di_flags & DI_FLAGS_LOCK)
- || tv_islocked(&di->di_tv));
- }
- } else if (lv.ll_range) {
- EMSG(_("E786: Range not allowed"));
- } else if (lv.ll_newkey != NULL) {
- EMSG2(_(e_dictkey), lv.ll_newkey);
- } else if (lv.ll_list != NULL) {
- // List item.
- rettv->vval.v_number = tv_islocked(TV_LIST_ITEM_TV(lv.ll_li));
- } else {
- // Dictionary item.
- rettv->vval.v_number = tv_islocked(&lv.ll_di->di_tv);
- }
- }
- }
-
- clear_lval(&lv);
-}
-
-// "isinf()" function
-static void f_isinf(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- if (argvars[0].v_type == VAR_FLOAT
- && xisinf(argvars[0].vval.v_float)) {
- rettv->vval.v_number = argvars[0].vval.v_float > 0.0 ? 1 : -1;
- }
-}
-
-// "isnan()" function
-static void f_isnan(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = argvars[0].v_type == VAR_FLOAT
- && xisnan(argvars[0].vval.v_float);
-}
-
/// Turn a dictionary into a list
///
/// @param[in] tv Dictionary to convert. Is checked for actually being
/// a dictionary, will give an error if not.
/// @param[out] rettv Location where result will be saved.
/// @param[in] what What to save in rettv.
-static void dict_list(typval_T *const tv, typval_T *const rettv,
- const DictListType what)
+void dict_list(typval_T *const tv, typval_T *const rettv,
+ const DictListType what)
{
if (tv->v_type != VAR_DICT) {
EMSG(_(e_dictreq));
@@ -12163,82 +6727,6 @@ static void dict_list(typval_T *const tv, typval_T *const rettv,
});
}
-/// "id()" function
-static void f_id(typval_T *argvars, typval_T *rettv, FunPtr fptr)
- FUNC_ATTR_NONNULL_ALL
-{
- const int len = vim_vsnprintf_typval(NULL, 0, "%p", dummy_ap, argvars);
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = xmalloc(len + 1);
- vim_vsnprintf_typval((char *)rettv->vval.v_string, len + 1, "%p",
- dummy_ap, argvars);
-}
-
-/*
- * "items(dict)" function
- */
-static void f_items(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- dict_list(argvars, rettv, 2);
-}
-
-// "jobpid(id)" function
-static void f_jobpid(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->v_type = VAR_NUMBER;
- rettv->vval.v_number = 0;
-
- if (check_restricted() || check_secure()) {
- return;
- }
-
- if (argvars[0].v_type != VAR_NUMBER) {
- EMSG(_(e_invarg));
- return;
- }
-
- Channel *data = find_job(argvars[0].vval.v_number, true);
- if (!data) {
- return;
- }
-
- Process *proc = (Process *)&data->stream.proc;
- rettv->vval.v_number = proc->pid;
-}
-
-// "jobresize(job, width, height)" function
-static void f_jobresize(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->v_type = VAR_NUMBER;
- rettv->vval.v_number = 0;
-
- if (check_restricted() || check_secure()) {
- return;
- }
-
- if (argvars[0].v_type != VAR_NUMBER || argvars[1].v_type != VAR_NUMBER
- || argvars[2].v_type != VAR_NUMBER) {
- // job id, width, height
- EMSG(_(e_invarg));
- return;
- }
-
-
- Channel *data = find_job(argvars[0].vval.v_number, true);
- if (!data) {
- return;
- }
-
- if (data->stream.proc.type != kProcessTypePty) {
- EMSG(_(e_channotpty));
- return;
- }
-
- pty_process_resize(&data->stream.pty, argvars[1].vval.v_number,
- argvars[2].vval.v_number);
- rettv->vval.v_number = 1;
-}
-
/// Builds a process argument vector from a VimL object (typval_T).
///
/// @param[in] cmd_tv VimL object
@@ -12248,7 +6736,7 @@ static void f_jobresize(typval_T *argvars, typval_T *rettv, FunPtr fptr)
/// @returns Result of `shell_build_argv()` if `cmd_tv` is a String.
/// Else, string values of `cmd_tv` copied to a (char **) list with
/// argv[0] resolved to full path ($PATHEXT-resolved on Windows).
-static char **tv_to_argv(typval_T *cmd_tv, const char **cmd, bool *executable)
+char **tv_to_argv(typval_T *cmd_tv, const char **cmd, bool *executable)
{
if (cmd_tv->v_type == VAR_STRING) { // String => "shell semantics".
const char *cmd_str = tv_get_string(cmd_tv);
@@ -12274,6 +6762,9 @@ static char **tv_to_argv(typval_T *cmd_tv, const char **cmd, bool *executable)
char *exe_resolved = NULL;
if (!arg0 || !os_can_exe(arg0, &exe_resolved, true)) {
if (arg0 && executable) {
+ char buf[IOSIZE];
+ snprintf(buf, sizeof(buf), "'%s' is not executable", arg0);
+ EMSG3(_(e_invargNval), "cmd", buf);
*executable = false;
}
return NULL;
@@ -12304,526 +6795,6 @@ static char **tv_to_argv(typval_T *cmd_tv, const char **cmd, bool *executable)
return argv;
}
-// "jobstart()" function
-static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->v_type = VAR_NUMBER;
- rettv->vval.v_number = 0;
-
- if (check_restricted() || check_secure()) {
- return;
- }
-
- bool executable = true;
- char **argv = tv_to_argv(&argvars[0], NULL, &executable);
- if (!argv) {
- rettv->vval.v_number = executable ? 0 : -1;
- return; // Did error message in tv_to_argv.
- }
-
- if (argvars[1].v_type != VAR_DICT && argvars[1].v_type != VAR_UNKNOWN) {
- // Wrong argument types
- EMSG2(_(e_invarg2), "expected dictionary");
- shell_free_argv(argv);
- return;
- }
-
-
- dict_T *job_opts = NULL;
- bool detach = false;
- bool rpc = false;
- bool pty = false;
- CallbackReader on_stdout = CALLBACK_READER_INIT,
- on_stderr = CALLBACK_READER_INIT;
- Callback on_exit = CALLBACK_NONE;
- char *cwd = NULL;
- if (argvars[1].v_type == VAR_DICT) {
- job_opts = argvars[1].vval.v_dict;
-
- detach = tv_dict_get_number(job_opts, "detach") != 0;
- rpc = tv_dict_get_number(job_opts, "rpc") != 0;
- pty = tv_dict_get_number(job_opts, "pty") != 0;
- if (pty && rpc) {
- EMSG2(_(e_invarg2), "job cannot have both 'pty' and 'rpc' options set");
- shell_free_argv(argv);
- return;
- }
-
- char *new_cwd = tv_dict_get_string(job_opts, "cwd", false);
- if (new_cwd && strlen(new_cwd) > 0) {
- cwd = new_cwd;
- // The new cwd must be a directory.
- if (!os_isdir_executable((const char *)cwd)) {
- EMSG2(_(e_invarg2), "expected valid directory");
- shell_free_argv(argv);
- return;
- }
- }
-
- if (!common_job_callbacks(job_opts, &on_stdout, &on_stderr, &on_exit)) {
- shell_free_argv(argv);
- return;
- }
- }
-
- uint16_t width = 0, height = 0;
- char *term_name = NULL;
-
- if (pty) {
- width = (uint16_t)tv_dict_get_number(job_opts, "width");
- height = (uint16_t)tv_dict_get_number(job_opts, "height");
- term_name = tv_dict_get_string(job_opts, "TERM", true);
- }
-
- Channel *chan = channel_job_start(argv, on_stdout, on_stderr, on_exit, pty,
- rpc, detach, cwd, width, height, term_name,
- &rettv->vval.v_number);
- if (chan) {
- channel_create_event(chan, NULL);
- }
-}
-
-// "jobstop()" function
-static void f_jobstop(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->v_type = VAR_NUMBER;
- rettv->vval.v_number = 0;
-
- if (check_restricted() || check_secure()) {
- return;
- }
-
- if (argvars[0].v_type != VAR_NUMBER) {
- // Only argument is the job id
- EMSG(_(e_invarg));
- return;
- }
-
- Channel *data = find_job(argvars[0].vval.v_number, true);
- if (!data) {
- return;
- }
-
- const char *error = NULL;
- if (data->is_rpc) {
- // Ignore return code, but show error later.
- (void)channel_close(data->id, kChannelPartRpc, &error);
- }
- process_stop((Process *)&data->stream.proc);
- rettv->vval.v_number = 1;
- if (error) {
- EMSG(error);
- }
-}
-
-// "jobwait(ids[, timeout])" function
-static void f_jobwait(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->v_type = VAR_NUMBER;
- rettv->vval.v_number = 0;
-
- if (check_restricted() || check_secure()) {
- return;
- }
- if (argvars[0].v_type != VAR_LIST || (argvars[1].v_type != VAR_NUMBER
- && argvars[1].v_type != VAR_UNKNOWN)) {
- EMSG(_(e_invarg));
- return;
- }
-
- ui_busy_start();
- list_T *args = argvars[0].vval.v_list;
- Channel **jobs = xcalloc(tv_list_len(args), sizeof(*jobs));
- MultiQueue *waiting_jobs = multiqueue_new_parent(loop_on_put, &main_loop);
-
- // Validate, prepare jobs for waiting.
- int i = 0;
- TV_LIST_ITER_CONST(args, arg, {
- Channel *chan = NULL;
- if (TV_LIST_ITEM_TV(arg)->v_type != VAR_NUMBER
- || !(chan = find_job(TV_LIST_ITEM_TV(arg)->vval.v_number, false))) {
- jobs[i] = NULL; // Invalid job.
- } else {
- jobs[i] = chan;
- channel_incref(chan);
- if (chan->stream.proc.status < 0) {
- // Process any pending events on the job's queue before temporarily
- // replacing it.
- multiqueue_process_events(chan->events);
- multiqueue_replace_parent(chan->events, waiting_jobs);
- }
- }
- i++;
- });
-
- int remaining = -1;
- uint64_t before = 0;
- if (argvars[1].v_type == VAR_NUMBER && argvars[1].vval.v_number >= 0) {
- remaining = argvars[1].vval.v_number;
- before = os_hrtime();
- }
-
- for (i = 0; i < tv_list_len(args); i++) {
- if (remaining == 0) {
- break; // Timeout.
- }
- if (jobs[i] == NULL) {
- continue; // Invalid job, will assign status=-3 below.
- }
- int status = process_wait(&jobs[i]->stream.proc, remaining,
- waiting_jobs);
- if (status < 0) {
- break; // Interrupted (CTRL-C) or timeout, skip remaining jobs.
- }
- if (remaining > 0) {
- uint64_t now = os_hrtime();
- remaining = MIN(0, remaining - (int)((now - before) / 1000000));
- before = now;
- }
- }
-
- list_T *const rv = tv_list_alloc(tv_list_len(args));
-
- // For each job:
- // * Restore its parent queue if the job is still alive.
- // * Append its status to the output list, or:
- // -3 for "invalid job id"
- // -2 for "interrupted" (user hit CTRL-C)
- // -1 for jobs that were skipped or timed out
- for (i = 0; i < tv_list_len(args); i++) {
- if (jobs[i] == NULL) {
- tv_list_append_number(rv, -3);
- continue;
- }
- multiqueue_process_events(jobs[i]->events);
- multiqueue_replace_parent(jobs[i]->events, main_loop.events);
-
- tv_list_append_number(rv, jobs[i]->stream.proc.status);
- channel_decref(jobs[i]);
- }
-
- multiqueue_free(waiting_jobs);
- xfree(jobs);
- ui_busy_stop();
- tv_list_ref(rv);
- rettv->v_type = VAR_LIST;
- rettv->vval.v_list = rv;
-}
-
-/*
- * "join()" function
- */
-static void f_join(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- if (argvars[0].v_type != VAR_LIST) {
- EMSG(_(e_listreq));
- return;
- }
- const char *const sep = (argvars[1].v_type == VAR_UNKNOWN
- ? " "
- : tv_get_string_chk(&argvars[1]));
-
- rettv->v_type = VAR_STRING;
-
- if (sep != NULL) {
- garray_T ga;
- ga_init(&ga, (int)sizeof(char), 80);
- tv_list_join(&ga, argvars[0].vval.v_list, sep);
- ga_append(&ga, NUL);
- rettv->vval.v_string = (char_u *)ga.ga_data;
- } else {
- rettv->vval.v_string = NULL;
- }
-}
-
-/// json_decode() function
-static void f_json_decode(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- char numbuf[NUMBUFLEN];
- const char *s = NULL;
- char *tofree = NULL;
- size_t len;
- if (argvars[0].v_type == VAR_LIST) {
- if (!encode_vim_list_to_buf(argvars[0].vval.v_list, &len, &tofree)) {
- EMSG(_("E474: Failed to convert list to string"));
- return;
- }
- s = tofree;
- if (s == NULL) {
- assert(len == 0);
- s = "";
- }
- } else {
- s = tv_get_string_buf_chk(&argvars[0], numbuf);
- if (s) {
- len = strlen(s);
- } else {
- return;
- }
- }
- if (json_decode_string(s, len, rettv) == FAIL) {
- emsgf(_("E474: Failed to parse %.*s"), (int)len, s);
- rettv->v_type = VAR_NUMBER;
- rettv->vval.v_number = 0;
- }
- assert(rettv->v_type != VAR_UNKNOWN);
- xfree(tofree);
-}
-
-/// json_encode() function
-static void f_json_encode(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = (char_u *) encode_tv2json(&argvars[0], NULL);
-}
-
-/*
- * "keys()" function
- */
-static void f_keys(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- dict_list(argvars, rettv, 0);
-}
-
-/*
- * "last_buffer_nr()" function.
- */
-static void f_last_buffer_nr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- int n = 0;
-
- FOR_ALL_BUFFERS(buf) {
- if (n < buf->b_fnum) {
- n = buf->b_fnum;
- }
- }
-
- rettv->vval.v_number = n;
-}
-
-/*
- * "len()" function
- */
-static void f_len(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- switch (argvars[0].v_type) {
- case VAR_STRING:
- case VAR_NUMBER: {
- rettv->vval.v_number = (varnumber_T)strlen(
- tv_get_string(&argvars[0]));
- break;
- }
- case VAR_LIST: {
- rettv->vval.v_number = tv_list_len(argvars[0].vval.v_list);
- break;
- }
- case VAR_DICT: {
- rettv->vval.v_number = tv_dict_len(argvars[0].vval.v_dict);
- break;
- }
- case VAR_UNKNOWN:
- case VAR_SPECIAL:
- case VAR_FLOAT:
- case VAR_PARTIAL:
- case VAR_FUNC: {
- EMSG(_("E701: Invalid type for len()"));
- break;
- }
- }
-}
-
-static void libcall_common(typval_T *argvars, typval_T *rettv, int out_type)
-{
- rettv->v_type = out_type;
- if (out_type != VAR_NUMBER) {
- rettv->vval.v_string = NULL;
- }
-
- if (check_restricted() || check_secure()) {
- return;
- }
-
- // The first two args (libname and funcname) must be strings
- if (argvars[0].v_type != VAR_STRING || argvars[1].v_type != VAR_STRING) {
- return;
- }
-
- const char *libname = (char *) argvars[0].vval.v_string;
- const char *funcname = (char *) argvars[1].vval.v_string;
-
- VarType in_type = argvars[2].v_type;
-
- // input variables
- char *str_in = (in_type == VAR_STRING)
- ? (char *) argvars[2].vval.v_string : NULL;
- int int_in = argvars[2].vval.v_number;
-
- // output variables
- char **str_out = (out_type == VAR_STRING)
- ? (char **)&rettv->vval.v_string : NULL;
- int int_out = 0;
-
- bool success = os_libcall(libname, funcname,
- str_in, int_in,
- str_out, &int_out);
-
- if (!success) {
- EMSG2(_(e_libcall), funcname);
- return;
- }
-
- if (out_type == VAR_NUMBER) {
- rettv->vval.v_number = (varnumber_T)int_out;
- }
-}
-
-/*
- * "libcall()" function
- */
-static void f_libcall(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- libcall_common(argvars, rettv, VAR_STRING);
-}
-
-/*
- * "libcallnr()" function
- */
-static void f_libcallnr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- libcall_common(argvars, rettv, VAR_NUMBER);
-}
-
-/*
- * "line(string)" function
- */
-static void f_line(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- linenr_T lnum = 0;
- pos_T *fp;
- int fnum;
-
- fp = var2fpos(&argvars[0], TRUE, &fnum);
- if (fp != NULL)
- lnum = fp->lnum;
- rettv->vval.v_number = lnum;
-}
-
-/*
- * "line2byte(lnum)" function
- */
-static void f_line2byte(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const linenr_T lnum = tv_get_lnum(argvars);
- if (lnum < 1 || lnum > curbuf->b_ml.ml_line_count + 1) {
- rettv->vval.v_number = -1;
- } else {
- rettv->vval.v_number = ml_find_line_or_offset(curbuf, lnum, NULL, false);
- }
- if (rettv->vval.v_number >= 0) {
- rettv->vval.v_number++;
- }
-}
-
-/*
- * "lispindent(lnum)" function
- */
-static void f_lispindent(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const pos_T pos = curwin->w_cursor;
- const linenr_T lnum = tv_get_lnum(argvars);
- if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count) {
- curwin->w_cursor.lnum = lnum;
- rettv->vval.v_number = get_lisp_indent();
- curwin->w_cursor = pos;
- } else {
- rettv->vval.v_number = -1;
- }
-}
-
-/*
- * "localtime()" function
- */
-static void f_localtime(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = (varnumber_T)time(NULL);
-}
-
-
-static void get_maparg(typval_T *argvars, typval_T *rettv, int exact)
-{
- char_u *keys_buf = NULL;
- char_u *rhs;
- int mode;
- int abbr = FALSE;
- int get_dict = FALSE;
- mapblock_T *mp;
- int buffer_local;
-
- // Return empty string for failure.
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = NULL;
-
- char_u *keys = (char_u *)tv_get_string(&argvars[0]);
- if (*keys == NUL) {
- return;
- }
-
- char buf[NUMBUFLEN];
- const char *which;
- if (argvars[1].v_type != VAR_UNKNOWN) {
- which = tv_get_string_buf_chk(&argvars[1], buf);
- if (argvars[2].v_type != VAR_UNKNOWN) {
- abbr = tv_get_number(&argvars[2]);
- if (argvars[3].v_type != VAR_UNKNOWN) {
- get_dict = tv_get_number(&argvars[3]);
- }
- }
- } else {
- which = "";
- }
- if (which == NULL) {
- return;
- }
-
- mode = get_map_mode((char_u **)&which, 0);
-
- keys = replace_termcodes(keys, STRLEN(keys), &keys_buf, true, true, true,
- CPO_TO_CPO_FLAGS);
- rhs = check_map(keys, mode, exact, false, abbr, &mp, &buffer_local);
- xfree(keys_buf);
-
- if (!get_dict) {
- // Return a string.
- if (rhs != NULL) {
- if (*rhs == NUL) {
- rettv->vval.v_string = vim_strsave((char_u *)"<Nop>");
- } else {
- rettv->vval.v_string = (char_u *)str2special_save(
- (char *)rhs, false, false);
- }
- }
-
- } else {
- tv_dict_alloc_ret(rettv);
- if (rhs != NULL) {
- // Return a dictionary.
- mapblock_fill_dict(rettv->vval.v_dict, mp, buffer_local, true);
- }
- }
-}
-
-/// luaeval() function implementation
-static void f_luaeval(typval_T *argvars, typval_T *rettv, FunPtr fptr)
- FUNC_ATTR_NONNULL_ALL
-{
- const char *const str = (const char *)tv_get_string_chk(&argvars[0]);
- if (str == NULL) {
- return;
- }
-
- executor_eval_lua(cstr_as_string((char *)str), &argvars[1], rettv);
-}
-
/// Fill a dictionary with all applicable maparg() like dictionaries
///
/// @param dict The dictionary to be filled
@@ -12860,6 +6831,7 @@ void mapblock_fill_dict(dict_T *const dict,
}
tv_dict_add_allocated_str(dict, S_LEN("lhs"), lhs);
tv_dict_add_nr(dict, S_LEN("noremap"), noremap_value);
+ tv_dict_add_nr(dict, S_LEN("script"), mp->m_noremap == REMAP_SCRIPT ? 1 : 0);
tv_dict_add_nr(dict, S_LEN("expr"), mp->m_expr ? 1 : 0);
tv_dict_add_nr(dict, S_LEN("silent"), mp->m_silent ? 1 : 0);
tv_dict_add_nr(dict, S_LEN("sid"), (varnumber_T)mp->m_script_ctx.sc_sid);
@@ -12869,260 +6841,8 @@ void mapblock_fill_dict(dict_T *const dict,
tv_dict_add_allocated_str(dict, S_LEN("mode"), mapmode);
}
-/*
- * "map()" function
- */
-static void f_map(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- filter_map(argvars, rettv, TRUE);
-}
-
-/*
- * "maparg()" function
- */
-static void f_maparg(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- get_maparg(argvars, rettv, TRUE);
-}
-
-/*
- * "mapcheck()" function
- */
-static void f_mapcheck(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- get_maparg(argvars, rettv, FALSE);
-}
-
-
-static void find_some_match(typval_T *const argvars, typval_T *const rettv,
- const SomeMatchType type)
-{
- char_u *str = NULL;
- long len = 0;
- char_u *expr = NULL;
- regmatch_T regmatch;
- char_u *save_cpo;
- long start = 0;
- long nth = 1;
- colnr_T startcol = 0;
- bool match = false;
- list_T *l = NULL;
- listitem_T *li = NULL;
- long idx = 0;
- char_u *tofree = NULL;
-
- /* Make 'cpoptions' empty, the 'l' flag should not be used here. */
- save_cpo = p_cpo;
- p_cpo = (char_u *)"";
-
- rettv->vval.v_number = -1;
- switch (type) {
- // matchlist(): return empty list when there are no matches.
- case kSomeMatchList: {
- tv_list_alloc_ret(rettv, kListLenMayKnow);
- break;
- }
- // matchstrpos(): return ["", -1, -1, -1]
- case kSomeMatchStrPos: {
- tv_list_alloc_ret(rettv, 4);
- tv_list_append_string(rettv->vval.v_list, "", 0);
- tv_list_append_number(rettv->vval.v_list, -1);
- tv_list_append_number(rettv->vval.v_list, -1);
- tv_list_append_number(rettv->vval.v_list, -1);
- break;
- }
- case kSomeMatchStr: {
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = NULL;
- break;
- }
- case kSomeMatch:
- case kSomeMatchEnd: {
- // Do nothing: zero is default.
- break;
- }
- }
-
- if (argvars[0].v_type == VAR_LIST) {
- if ((l = argvars[0].vval.v_list) == NULL) {
- goto theend;
- }
- li = tv_list_first(l);
- } else {
- expr = str = (char_u *)tv_get_string(&argvars[0]);
- len = (long)STRLEN(str);
- }
-
- char patbuf[NUMBUFLEN];
- const char *const pat = tv_get_string_buf_chk(&argvars[1], patbuf);
- if (pat == NULL) {
- goto theend;
- }
-
- if (argvars[2].v_type != VAR_UNKNOWN) {
- bool error = false;
-
- start = tv_get_number_chk(&argvars[2], &error);
- if (error) {
- goto theend;
- }
- if (l != NULL) {
- idx = tv_list_uidx(l, start);
- if (idx == -1) {
- goto theend;
- }
- li = tv_list_find(l, idx);
- } else {
- if (start < 0)
- start = 0;
- if (start > len)
- goto theend;
- /* When "count" argument is there ignore matches before "start",
- * otherwise skip part of the string. Differs when pattern is "^"
- * or "\<". */
- if (argvars[3].v_type != VAR_UNKNOWN)
- startcol = start;
- else {
- str += start;
- len -= start;
- }
- }
-
- if (argvars[3].v_type != VAR_UNKNOWN) {
- nth = tv_get_number_chk(&argvars[3], &error);
- }
- if (error) {
- goto theend;
- }
- }
-
- regmatch.regprog = vim_regcomp((char_u *)pat, RE_MAGIC + RE_STRING);
- if (regmatch.regprog != NULL) {
- regmatch.rm_ic = p_ic;
-
- for (;; ) {
- if (l != NULL) {
- if (li == NULL) {
- match = false;
- break;
- }
- xfree(tofree);
- tofree = expr = str = (char_u *)encode_tv2echo(TV_LIST_ITEM_TV(li),
- NULL);
- if (str == NULL) {
- break;
- }
- }
-
- match = vim_regexec_nl(&regmatch, str, (colnr_T)startcol);
-
- if (match && --nth <= 0)
- break;
- if (l == NULL && !match)
- break;
-
- /* Advance to just after the match. */
- if (l != NULL) {
- li = TV_LIST_ITEM_NEXT(l, li);
- idx++;
- } else {
- startcol = (colnr_T)(regmatch.startp[0]
- + (*mb_ptr2len)(regmatch.startp[0]) - str);
- if (startcol > (colnr_T)len || str + startcol <= regmatch.startp[0]) {
- match = false;
- break;
- }
- }
- }
-
- if (match) {
- switch (type) {
- case kSomeMatchStrPos: {
- list_T *const ret_l = rettv->vval.v_list;
- listitem_T *li1 = tv_list_first(ret_l);
- listitem_T *li2 = TV_LIST_ITEM_NEXT(ret_l, li1);
- listitem_T *li3 = TV_LIST_ITEM_NEXT(ret_l, li2);
- listitem_T *li4 = TV_LIST_ITEM_NEXT(ret_l, li3);
- xfree(TV_LIST_ITEM_TV(li1)->vval.v_string);
-
- const size_t rd = (size_t)(regmatch.endp[0] - regmatch.startp[0]);
- TV_LIST_ITEM_TV(li1)->vval.v_string = xmemdupz(
- (const char *)regmatch.startp[0], rd);
- TV_LIST_ITEM_TV(li3)->vval.v_number = (varnumber_T)(
- regmatch.startp[0] - expr);
- TV_LIST_ITEM_TV(li4)->vval.v_number = (varnumber_T)(
- regmatch.endp[0] - expr);
- if (l != NULL) {
- TV_LIST_ITEM_TV(li2)->vval.v_number = (varnumber_T)idx;
- }
- break;
- }
- case kSomeMatchList: {
- // Return list with matched string and submatches.
- for (int i = 0; i < NSUBEXP; i++) {
- if (regmatch.endp[i] == NULL) {
- tv_list_append_string(rettv->vval.v_list, NULL, 0);
- } else {
- tv_list_append_string(rettv->vval.v_list,
- (const char *)regmatch.startp[i],
- (regmatch.endp[i] - regmatch.startp[i]));
- }
- }
- break;
- }
- case kSomeMatchStr: {
- // Return matched string.
- if (l != NULL) {
- tv_copy(TV_LIST_ITEM_TV(li), rettv);
- } else {
- rettv->vval.v_string = (char_u *)xmemdupz(
- (const char *)regmatch.startp[0],
- (size_t)(regmatch.endp[0] - regmatch.startp[0]));
- }
- break;
- }
- case kSomeMatch:
- case kSomeMatchEnd: {
- if (l != NULL) {
- rettv->vval.v_number = idx;
- } else {
- if (type == kSomeMatch) {
- rettv->vval.v_number =
- (varnumber_T)(regmatch.startp[0] - str);
- } else {
- rettv->vval.v_number =
- (varnumber_T)(regmatch.endp[0] - str);
- }
- rettv->vval.v_number += (varnumber_T)(str - expr);
- }
- break;
- }
- }
- }
- vim_regfree(regmatch.regprog);
- }
-
-theend:
- if (type == kSomeMatchStrPos && l == NULL && rettv->vval.v_list != NULL) {
- // matchstrpos() without a list: drop the second item
- list_T *const ret_l = rettv->vval.v_list;
- tv_list_item_remove(ret_l, TV_LIST_ITEM_NEXT(ret_l, tv_list_first(ret_l)));
- }
-
- xfree(tofree);
- p_cpo = save_cpo;
-}
-
-/*
- * "match()" function
- */
-static void f_match(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- find_some_match(argvars, rettv, kSomeMatch);
-}
-
-static int matchadd_dict_arg(typval_T *tv, const char **conceal_char,
- win_T **win)
+int matchadd_dict_arg(typval_T *tv, const char **conceal_char,
+ win_T **win)
{
dictitem_T *di;
@@ -13138,7 +6858,7 @@ static int matchadd_dict_arg(typval_T *tv, const char **conceal_char,
if ((di = tv_dict_find(tv->vval.v_dict, S_LEN("window"))) != NULL) {
*win = find_win_by_nr_or_id(&di->di_tv);
if (*win == NULL) {
- EMSG(_("E957: Invalid window number"));
+ EMSG(_(e_invalwindow));
return FAIL;
}
}
@@ -13146,759 +6866,7 @@ static int matchadd_dict_arg(typval_T *tv, const char **conceal_char,
return OK;
}
-/*
- * "matchadd()" function
- */
-static void f_matchadd(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- char grpbuf[NUMBUFLEN];
- char patbuf[NUMBUFLEN];
- const char *const grp = tv_get_string_buf_chk(&argvars[0], grpbuf);
- const char *const pat = tv_get_string_buf_chk(&argvars[1], patbuf);
- int prio = 10;
- int id = -1;
- bool error = false;
- const char *conceal_char = NULL;
- win_T *win = curwin;
-
- rettv->vval.v_number = -1;
-
- if (grp == NULL || pat == NULL) {
- return;
- }
- if (argvars[2].v_type != VAR_UNKNOWN) {
- prio = tv_get_number_chk(&argvars[2], &error);
- if (argvars[3].v_type != VAR_UNKNOWN) {
- id = tv_get_number_chk(&argvars[3], &error);
- if (argvars[4].v_type != VAR_UNKNOWN
- && matchadd_dict_arg(&argvars[4], &conceal_char, &win) == FAIL) {
- return;
- }
- }
- }
- if (error) {
- return;
- }
- if (id >= 1 && id <= 3) {
- EMSGN(_("E798: ID is reserved for \":match\": %" PRId64), id);
- return;
- }
-
- rettv->vval.v_number = match_add(win, grp, pat, prio, id, NULL, conceal_char);
-}
-
-static void f_matchaddpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = -1;
-
- char buf[NUMBUFLEN];
- const char *const group = tv_get_string_buf_chk(&argvars[0], buf);
- if (group == NULL) {
- return;
- }
-
- if (argvars[1].v_type != VAR_LIST) {
- EMSG2(_(e_listarg), "matchaddpos()");
- return;
- }
-
- list_T *l;
- l = argvars[1].vval.v_list;
- if (l == NULL) {
- return;
- }
-
- bool error = false;
- int prio = 10;
- int id = -1;
- const char *conceal_char = NULL;
- win_T *win = curwin;
-
- if (argvars[2].v_type != VAR_UNKNOWN) {
- prio = tv_get_number_chk(&argvars[2], &error);
- if (argvars[3].v_type != VAR_UNKNOWN) {
- id = tv_get_number_chk(&argvars[3], &error);
- if (argvars[4].v_type != VAR_UNKNOWN
- && matchadd_dict_arg(&argvars[4], &conceal_char, &win) == FAIL) {
- return;
- }
- }
- }
- if (error == true) {
- return;
- }
-
- // id == 3 is ok because matchaddpos() is supposed to substitute :3match
- if (id == 1 || id == 2) {
- EMSGN(_("E798: ID is reserved for \"match\": %" PRId64), id);
- return;
- }
-
- rettv->vval.v_number = match_add(win, group, NULL, prio, id, l, conceal_char);
-}
-
-/*
- * "matcharg()" function
- */
-static void f_matcharg(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const int id = tv_get_number(&argvars[0]);
-
- tv_list_alloc_ret(rettv, (id >= 1 && id <= 3
- ? 2
- : 0));
-
- if (id >= 1 && id <= 3) {
- matchitem_T *const m = (matchitem_T *)get_match(curwin, id);
-
- if (m != NULL) {
- tv_list_append_string(rettv->vval.v_list,
- (const char *)syn_id2name(m->hlg_id), -1);
- tv_list_append_string(rettv->vval.v_list, (const char *)m->pattern, -1);
- } else {
- tv_list_append_string(rettv->vval.v_list, NULL, 0);
- tv_list_append_string(rettv->vval.v_list, NULL, 0);
- }
- }
-}
-
-/*
- * "matchdelete()" function
- */
-static void f_matchdelete(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = match_delete(curwin,
- (int)tv_get_number(&argvars[0]), true);
-}
-
-/*
- * "matchend()" function
- */
-static void f_matchend(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- find_some_match(argvars, rettv, kSomeMatchEnd);
-}
-
-/*
- * "matchlist()" function
- */
-static void f_matchlist(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- find_some_match(argvars, rettv, kSomeMatchList);
-}
-
-/*
- * "matchstr()" function
- */
-static void f_matchstr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- find_some_match(argvars, rettv, kSomeMatchStr);
-}
-
-/// "matchstrpos()" function
-static void f_matchstrpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- find_some_match(argvars, rettv, kSomeMatchStrPos);
-}
-
-/// Get maximal/minimal number value in a list or dictionary
-///
-/// @param[in] tv List or dictionary to work with. If it contains something
-/// that is not an integer number (or cannot be coerced to
-/// it) error is given.
-/// @param[out] rettv Location where result will be saved. Only assigns
-/// vval.v_number, type is not touched. Returns zero for
-/// empty lists/dictionaries.
-/// @param[in] domax Determines whether maximal or minimal value is desired.
-static void max_min(const typval_T *const tv, typval_T *const rettv,
- const bool domax)
- FUNC_ATTR_NONNULL_ALL
-{
- bool error = false;
-
- rettv->vval.v_number = 0;
- varnumber_T n = (domax ? VARNUMBER_MIN : VARNUMBER_MAX);
- if (tv->v_type == VAR_LIST) {
- if (tv_list_len(tv->vval.v_list) == 0) {
- return;
- }
- TV_LIST_ITER_CONST(tv->vval.v_list, li, {
- const varnumber_T i = tv_get_number_chk(TV_LIST_ITEM_TV(li), &error);
- if (error) {
- return;
- }
- if (domax ? i > n : i < n) {
- n = i;
- }
- });
- } else if (tv->v_type == VAR_DICT) {
- if (tv_dict_len(tv->vval.v_dict) == 0) {
- return;
- }
- TV_DICT_ITER(tv->vval.v_dict, di, {
- const varnumber_T i = tv_get_number_chk(&di->di_tv, &error);
- if (error) {
- return;
- }
- if (domax ? i > n : i < n) {
- n = i;
- }
- });
- } else {
- EMSG2(_(e_listdictarg), domax ? "max()" : "min()");
- return;
- }
- rettv->vval.v_number = n;
-}
-
-/*
- * "max()" function
- */
-static void f_max(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- max_min(argvars, rettv, TRUE);
-}
-
-/*
- * "min()" function
- */
-static void f_min(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- max_min(argvars, rettv, FALSE);
-}
-
-/*
- * "mkdir()" function
- */
-static void f_mkdir(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- int prot = 0755; // -V536
-
- rettv->vval.v_number = FAIL;
- if (check_restricted() || check_secure())
- return;
-
- char buf[NUMBUFLEN];
- const char *const dir = tv_get_string_buf(&argvars[0], buf);
- if (*dir == NUL) {
- return;
- }
-
- if (*path_tail((char_u *)dir) == NUL) {
- // Remove trailing slashes.
- *path_tail_with_sep((char_u *)dir) = NUL;
- }
-
- if (argvars[1].v_type != VAR_UNKNOWN) {
- if (argvars[2].v_type != VAR_UNKNOWN) {
- prot = tv_get_number_chk(&argvars[2], NULL);
- if (prot == -1) {
- return;
- }
- }
- if (strcmp(tv_get_string(&argvars[1]), "p") == 0) {
- char *failed_dir;
- int ret = os_mkdir_recurse(dir, prot, &failed_dir);
- if (ret != 0) {
- EMSG3(_(e_mkdir), failed_dir, os_strerror(ret));
- xfree(failed_dir);
- rettv->vval.v_number = FAIL;
- return;
- } else {
- rettv->vval.v_number = OK;
- return;
- }
- }
- }
- rettv->vval.v_number = vim_mkdir_emsg(dir, prot);
-}
-
-/// "mode()" function
-static void f_mode(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- char *mode = get_mode();
-
- // Clear out the minor mode when the argument is not a non-zero number or
- // non-empty string.
- if (!non_zero_arg(&argvars[0])) {
- mode[1] = NUL;
- }
-
- rettv->vval.v_string = (char_u *)mode;
- rettv->v_type = VAR_STRING;
-}
-
-/// "msgpackdump()" function
-static void f_msgpackdump(typval_T *argvars, typval_T *rettv, FunPtr fptr)
- FUNC_ATTR_NONNULL_ALL
-{
- if (argvars[0].v_type != VAR_LIST) {
- EMSG2(_(e_listarg), "msgpackdump()");
- return;
- }
- list_T *const ret_list = tv_list_alloc_ret(rettv, kListLenMayKnow);
- list_T *const list = argvars[0].vval.v_list;
- msgpack_packer *lpacker = msgpack_packer_new(ret_list, &encode_list_write);
- const char *const msg = _("msgpackdump() argument, index %i");
- // Assume that translation will not take more then 4 times more space
- char msgbuf[sizeof("msgpackdump() argument, index ") * 4 + NUMBUFLEN];
- int idx = 0;
- TV_LIST_ITER(list, li, {
- vim_snprintf(msgbuf, sizeof(msgbuf), (char *)msg, idx);
- idx++;
- if (encode_vim_to_msgpack(lpacker, TV_LIST_ITEM_TV(li), msgbuf) == FAIL) {
- break;
- }
- });
- msgpack_packer_free(lpacker);
-}
-
-/// "msgpackparse" function
-static void f_msgpackparse(typval_T *argvars, typval_T *rettv, FunPtr fptr)
- FUNC_ATTR_NONNULL_ALL
-{
- if (argvars[0].v_type != VAR_LIST) {
- EMSG2(_(e_listarg), "msgpackparse()");
- return;
- }
- list_T *const ret_list = tv_list_alloc_ret(rettv, kListLenMayKnow);
- const list_T *const list = argvars[0].vval.v_list;
- if (tv_list_len(list) == 0) {
- return;
- }
- if (TV_LIST_ITEM_TV(tv_list_first(list))->v_type != VAR_STRING) {
- EMSG2(_(e_invarg2), "List item is not a string");
- return;
- }
- ListReaderState lrstate = encode_init_lrstate(list);
- msgpack_unpacker *const unpacker = msgpack_unpacker_new(IOSIZE);
- if (unpacker == NULL) {
- EMSG(_(e_outofmem));
- return;
- }
- msgpack_unpacked unpacked;
- msgpack_unpacked_init(&unpacked);
- do {
- if (!msgpack_unpacker_reserve_buffer(unpacker, IOSIZE)) {
- EMSG(_(e_outofmem));
- goto f_msgpackparse_exit;
- }
- size_t read_bytes;
- const int rlret = encode_read_from_list(
- &lrstate, msgpack_unpacker_buffer(unpacker), IOSIZE, &read_bytes);
- if (rlret == FAIL) {
- EMSG2(_(e_invarg2), "List item is not a string");
- goto f_msgpackparse_exit;
- }
- msgpack_unpacker_buffer_consumed(unpacker, read_bytes);
- if (read_bytes == 0) {
- break;
- }
- while (unpacker->off < unpacker->used) {
- const msgpack_unpack_return result = msgpack_unpacker_next(unpacker,
- &unpacked);
- if (result == MSGPACK_UNPACK_PARSE_ERROR) {
- EMSG2(_(e_invarg2), "Failed to parse msgpack string");
- goto f_msgpackparse_exit;
- }
- if (result == MSGPACK_UNPACK_NOMEM_ERROR) {
- EMSG(_(e_outofmem));
- goto f_msgpackparse_exit;
- }
- if (result == MSGPACK_UNPACK_SUCCESS) {
- typval_T tv = { .v_type = VAR_UNKNOWN };
- if (msgpack_to_vim(unpacked.data, &tv) == FAIL) {
- EMSG2(_(e_invarg2), "Failed to convert msgpack string");
- goto f_msgpackparse_exit;
- }
- tv_list_append_owned_tv(ret_list, tv);
- }
- if (result == MSGPACK_UNPACK_CONTINUE) {
- if (rlret == OK) {
- EMSG2(_(e_invarg2), "Incomplete msgpack string");
- }
- break;
- }
- }
- if (rlret == OK) {
- break;
- }
- } while (true);
-
-f_msgpackparse_exit:
- msgpack_unpacked_destroy(&unpacked);
- msgpack_unpacker_free(unpacker);
- return;
-}
-
-/*
- * "nextnonblank()" function
- */
-static void f_nextnonblank(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- linenr_T lnum;
-
- for (lnum = tv_get_lnum(argvars);; lnum++) {
- if (lnum < 0 || lnum > curbuf->b_ml.ml_line_count) {
- lnum = 0;
- break;
- }
- if (*skipwhite(ml_get(lnum)) != NUL) {
- break;
- }
- }
- rettv->vval.v_number = lnum;
-}
-
-/*
- * "nr2char()" function
- */
-static void f_nr2char(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- if (argvars[1].v_type != VAR_UNKNOWN) {
- if (!tv_check_num(&argvars[1])) {
- return;
- }
- }
-
- bool error = false;
- const varnumber_T num = tv_get_number_chk(&argvars[0], &error);
- if (error) {
- return;
- }
- if (num < 0) {
- EMSG(_("E5070: Character number must not be less than zero"));
- return;
- }
- if (num > INT_MAX) {
- emsgf(_("E5071: Character number must not be greater than INT_MAX (%i)"),
- INT_MAX);
- return;
- }
-
- char buf[MB_MAXBYTES];
- const int len = utf_char2bytes((int)num, (char_u *)buf);
-
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = xmemdupz(buf, (size_t)len);
-}
-
-/*
- * "or(expr, expr)" function
- */
-static void f_or(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = tv_get_number_chk(&argvars[0], NULL)
- | tv_get_number_chk(&argvars[1], NULL);
-}
-
-/*
- * "pathshorten()" function
- */
-static void f_pathshorten(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->v_type = VAR_STRING;
- const char *const s = tv_get_string_chk(&argvars[0]);
- if (!s) {
- return;
- }
- rettv->vval.v_string = shorten_dir((char_u *)xstrdup(s));
-}
-
-/*
- * "pow()" function
- */
-static void f_pow(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- float_T fx;
- float_T fy;
-
- rettv->v_type = VAR_FLOAT;
- if (tv_get_float_chk(argvars, &fx) && tv_get_float_chk(&argvars[1], &fy)) {
- rettv->vval.v_float = pow(fx, fy);
- } else {
- rettv->vval.v_float = 0.0;
- }
-}
-
-/*
- * "prevnonblank()" function
- */
-static void f_prevnonblank(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- linenr_T lnum = tv_get_lnum(argvars);
- if (lnum < 1 || lnum > curbuf->b_ml.ml_line_count) {
- lnum = 0;
- } else {
- while (lnum >= 1 && *skipwhite(ml_get(lnum)) == NUL) {
- lnum--;
- }
- }
- rettv->vval.v_number = lnum;
-}
-
-/*
- * "printf()" function
- */
-static void f_printf(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = NULL;
- {
- int len;
- int saved_did_emsg = did_emsg;
-
- // Get the required length, allocate the buffer and do it for real.
- did_emsg = false;
- char buf[NUMBUFLEN];
- const char *fmt = tv_get_string_buf(&argvars[0], buf);
- len = vim_vsnprintf_typval(NULL, 0, fmt, dummy_ap, argvars + 1);
- if (!did_emsg) {
- char *s = xmalloc(len + 1);
- rettv->vval.v_string = (char_u *)s;
- (void)vim_vsnprintf_typval(s, len + 1, fmt, dummy_ap, argvars + 1);
- }
- did_emsg |= saved_did_emsg;
- }
-}
-
-/*
- * "pumvisible()" function
- */
-static void f_pumvisible(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- if (pum_visible())
- rettv->vval.v_number = 1;
-}
-
-/*
- * "pyeval()" function
- */
-static void f_pyeval(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- script_host_eval("python", argvars, rettv);
-}
-
-/*
- * "py3eval()" function
- */
-static void f_py3eval(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- script_host_eval("python3", argvars, rettv);
-}
-
-// "pyxeval()" function
-static void f_pyxeval(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- init_pyxversion();
- if (p_pyx == 2) {
- f_pyeval(argvars, rettv, NULL);
- } else {
- f_py3eval(argvars, rettv, NULL);
- }
-}
-
-/*
- * "range()" function
- */
-static void f_range(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- varnumber_T start;
- varnumber_T end;
- varnumber_T stride = 1;
- varnumber_T i;
- bool error = false;
-
- start = tv_get_number_chk(&argvars[0], &error);
- if (argvars[1].v_type == VAR_UNKNOWN) {
- end = start - 1;
- start = 0;
- } else {
- end = tv_get_number_chk(&argvars[1], &error);
- if (argvars[2].v_type != VAR_UNKNOWN) {
- stride = tv_get_number_chk(&argvars[2], &error);
- }
- }
-
- if (error) {
- return; // Type error; errmsg already given.
- }
- if (stride == 0) {
- EMSG(_("E726: Stride is zero"));
- } else if (stride > 0 ? end + 1 < start : end - 1 > start) {
- EMSG(_("E727: Start past end"));
- } else {
- tv_list_alloc_ret(rettv, (end - start) / stride);
- for (i = start; stride > 0 ? i <= end : i >= end; i += stride) {
- tv_list_append_number(rettv->vval.v_list, (varnumber_T)i);
- }
- }
-}
-
-/*
- * "readfile()" function
- */
-static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- bool binary = false;
- FILE *fd;
- char_u buf[(IOSIZE/256)*256]; /* rounded to avoid odd + 1 */
- int io_size = sizeof(buf);
- int readlen; /* size of last fread() */
- char_u *prev = NULL; /* previously read bytes, if any */
- long prevlen = 0; /* length of data in prev */
- long prevsize = 0; /* size of prev buffer */
- long maxline = MAXLNUM;
-
- if (argvars[1].v_type != VAR_UNKNOWN) {
- if (strcmp(tv_get_string(&argvars[1]), "b") == 0) {
- binary = true;
- }
- if (argvars[2].v_type != VAR_UNKNOWN) {
- maxline = tv_get_number(&argvars[2]);
- }
- }
-
- list_T *const l = tv_list_alloc_ret(rettv, kListLenUnknown);
-
- // Always open the file in binary mode, library functions have a mind of
- // their own about CR-LF conversion.
- const char *const fname = tv_get_string(&argvars[0]);
- if (*fname == NUL || (fd = os_fopen(fname, READBIN)) == NULL) {
- EMSG2(_(e_notopen), *fname == NUL ? _("<empty>") : fname);
- return;
- }
-
- while (maxline < 0 || tv_list_len(l) < maxline) {
- readlen = (int)fread(buf, 1, io_size, fd);
-
- // This for loop processes what was read, but is also entered at end
- // of file so that either:
- // - an incomplete line gets written
- // - a "binary" file gets an empty line at the end if it ends in a
- // newline.
- char_u *p; // Position in buf.
- char_u *start; // Start of current line.
- for (p = buf, start = buf;
- p < buf + readlen || (readlen <= 0 && (prevlen > 0 || binary));
- p++) {
- if (*p == '\n' || readlen <= 0) {
- char_u *s = NULL;
- size_t len = p - start;
-
- /* Finished a line. Remove CRs before NL. */
- if (readlen > 0 && !binary) {
- while (len > 0 && start[len - 1] == '\r')
- --len;
- /* removal may cross back to the "prev" string */
- if (len == 0)
- while (prevlen > 0 && prev[prevlen - 1] == '\r')
- --prevlen;
- }
- if (prevlen == 0) {
- assert(len < INT_MAX);
- s = vim_strnsave(start, (int)len);
- } else {
- /* Change "prev" buffer to be the right size. This way
- * the bytes are only copied once, and very long lines are
- * allocated only once. */
- s = xrealloc(prev, prevlen + len + 1);
- memcpy(s + prevlen, start, len);
- s[prevlen + len] = NUL;
- prev = NULL; /* the list will own the string */
- prevlen = prevsize = 0;
- }
-
- tv_list_append_owned_tv(l, (typval_T) {
- .v_type = VAR_STRING,
- .v_lock = VAR_UNLOCKED,
- .vval.v_string = s,
- });
-
- start = p + 1; // Step over newline.
- if (maxline < 0) {
- if (tv_list_len(l) > -maxline) {
- assert(tv_list_len(l) == 1 + (-maxline));
- tv_list_item_remove(l, tv_list_first(l));
- }
- } else if (tv_list_len(l) >= maxline) {
- assert(tv_list_len(l) == maxline);
- break;
- }
- if (readlen <= 0) {
- break;
- }
- } else if (*p == NUL) {
- *p = '\n';
- // Check for utf8 "bom"; U+FEFF is encoded as EF BB BF. Do this
- // when finding the BF and check the previous two bytes.
- } else if (*p == 0xbf && !binary) {
- // Find the two bytes before the 0xbf. If p is at buf, or buf + 1,
- // these may be in the "prev" string.
- char_u back1 = p >= buf + 1 ? p[-1]
- : prevlen >= 1 ? prev[prevlen - 1] : NUL;
- char_u back2 = p >= buf + 2 ? p[-2]
- : p == buf + 1 && prevlen >= 1 ? prev[prevlen - 1]
- : prevlen >= 2 ? prev[prevlen - 2] : NUL;
-
- if (back2 == 0xef && back1 == 0xbb) {
- char_u *dest = p - 2;
-
- /* Usually a BOM is at the beginning of a file, and so at
- * the beginning of a line; then we can just step over it.
- */
- if (start == dest)
- start = p + 1;
- else {
- /* have to shuffle buf to close gap */
- int adjust_prevlen = 0;
-
- if (dest < buf) { // -V782
- adjust_prevlen = (int)(buf - dest); // -V782
- // adjust_prevlen must be 1 or 2.
- dest = buf;
- }
- if (readlen > p - buf + 1)
- memmove(dest, p + 1, readlen - (p - buf) - 1);
- readlen -= 3 - adjust_prevlen;
- prevlen -= adjust_prevlen;
- p = dest - 1;
- }
- }
- }
- } /* for */
-
- if ((maxline >= 0 && tv_list_len(l) >= maxline) || readlen <= 0) {
- break;
- }
- if (start < p) {
- /* There's part of a line in buf, store it in "prev". */
- if (p - start + prevlen >= prevsize) {
-
- /* A common use case is ordinary text files and "prev" gets a
- * fragment of a line, so the first allocation is made
- * small, to avoid repeatedly 'allocing' large and
- * 'reallocing' small. */
- if (prevsize == 0)
- prevsize = (long)(p - start);
- else {
- long grow50pc = (prevsize * 3) / 2;
- long growmin = (long)((p - start) * 2 + prevlen);
- prevsize = grow50pc > growmin ? grow50pc : growmin;
- }
- prev = xrealloc(prev, prevsize);
- }
- /* Add the line part to end of "prev". */
- memmove(prev + prevlen, start, p - start);
- prevlen += (long)(p - start);
- }
- } /* while */
-
- xfree(prev);
- fclose(fd);
-}
-
-static void return_register(int regname, typval_T *rettv)
+void return_register(int regname, typval_T *rettv)
{
char_u buf[2] = { regname, 0 };
@@ -13906,806 +6874,7 @@ static void return_register(int regname, typval_T *rettv)
rettv->vval.v_string = vim_strsave(buf);
}
-// "reg_executing()" function
-static void f_reg_executing(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- return_register(reg_executing, rettv);
-}
-
-// "reg_recording()" function
-static void f_reg_recording(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- return_register(reg_recording, rettv);
-}
-
-/// list2proftime - convert a List to proftime_T
-///
-/// @param arg The input list, must be of type VAR_LIST and have
-/// exactly 2 items
-/// @param[out] tm The proftime_T representation of `arg`
-/// @return OK In case of success, FAIL in case of error
-static int list2proftime(typval_T *arg, proftime_T *tm) FUNC_ATTR_NONNULL_ALL
-{
- if (arg->v_type != VAR_LIST || tv_list_len(arg->vval.v_list) != 2) {
- return FAIL;
- }
-
- bool error = false;
- varnumber_T n1 = tv_list_find_nr(arg->vval.v_list, 0L, &error);
- varnumber_T n2 = tv_list_find_nr(arg->vval.v_list, 1L, &error);
- if (error) {
- return FAIL;
- }
-
- // in f_reltime() we split up the 64-bit proftime_T into two 32-bit
- // values, now we combine them again.
- union {
- struct { int32_t low, high; } split;
- proftime_T prof;
- } u = { .split.high = n1, .split.low = n2 };
-
- *tm = u.prof;
-
- return OK;
-}
-
-/// f_reltime - return an item that represents a time value
-///
-/// @param[out] rettv Without an argument it returns the current time. With
-/// one argument it returns the time passed since the argument.
-/// With two arguments it returns the time passed between
-/// the two arguments.
-static void f_reltime(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- proftime_T res;
- proftime_T start;
-
- if (argvars[0].v_type == VAR_UNKNOWN) {
- // no arguments: get current time.
- res = profile_start();
- } else if (argvars[1].v_type == VAR_UNKNOWN) {
- if (list2proftime(&argvars[0], &res) == FAIL) {
- return;
- }
- res = profile_end(res);
- } else {
- // two arguments: compute the difference.
- if (list2proftime(&argvars[0], &start) == FAIL
- || list2proftime(&argvars[1], &res) == FAIL) {
- return;
- }
- res = profile_sub(res, start);
- }
-
- // we have to store the 64-bit proftime_T inside of a list of int's
- // (varnumber_T is defined as int). For all our supported platforms, int's
- // are at least 32-bits wide. So we'll use two 32-bit values to store it.
- union {
- struct { int32_t low, high; } split;
- proftime_T prof;
- } u = { .prof = res };
-
- // statically assert that the union type conv will provide the correct
- // results, if varnumber_T or proftime_T change, the union cast will need
- // to be revised.
- STATIC_ASSERT(sizeof(u.prof) == sizeof(u) && sizeof(u.split) == sizeof(u),
- "type punning will produce incorrect results on this platform");
-
- tv_list_alloc_ret(rettv, 2);
- tv_list_append_number(rettv->vval.v_list, u.split.high);
- tv_list_append_number(rettv->vval.v_list, u.split.low);
-}
-
-/// "reltimestr()" function
-static void f_reltimestr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
- FUNC_ATTR_NONNULL_ALL
-{
- proftime_T tm;
-
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = NULL;
- if (list2proftime(&argvars[0], &tm) == OK) {
- rettv->vval.v_string = (char_u *)xstrdup(profile_msg(tm));
- }
-}
-
-/*
- * "remove()" function
- */
-static void f_remove(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- list_T *l;
- listitem_T *item, *item2;
- listitem_T *li;
- long idx;
- long end;
- dict_T *d;
- dictitem_T *di;
- const char *const arg_errmsg = N_("remove() argument");
-
- if (argvars[0].v_type == VAR_DICT) {
- if (argvars[2].v_type != VAR_UNKNOWN) {
- EMSG2(_(e_toomanyarg), "remove()");
- } else if ((d = argvars[0].vval.v_dict) != NULL
- && !tv_check_lock(d->dv_lock, arg_errmsg, TV_TRANSLATE)) {
- const char *key = tv_get_string_chk(&argvars[1]);
- if (key != NULL) {
- di = tv_dict_find(d, key, -1);
- if (di == NULL) {
- EMSG2(_(e_dictkey), key);
- } else if (!var_check_fixed(di->di_flags, arg_errmsg, TV_TRANSLATE)
- && !var_check_ro(di->di_flags, arg_errmsg, TV_TRANSLATE)) {
- *rettv = di->di_tv;
- di->di_tv = TV_INITIAL_VALUE;
- tv_dict_item_remove(d, di);
- if (tv_dict_is_watched(d)) {
- tv_dict_watcher_notify(d, key, NULL, rettv);
- }
- }
- }
- }
- } else if (argvars[0].v_type != VAR_LIST) {
- EMSG2(_(e_listdictarg), "remove()");
- } else if (!tv_check_lock(tv_list_locked((l = argvars[0].vval.v_list)),
- arg_errmsg, TV_TRANSLATE)) {
- bool error = false;
-
- idx = tv_get_number_chk(&argvars[1], &error);
- if (error) {
- // Type error: do nothing, errmsg already given.
- } else if ((item = tv_list_find(l, idx)) == NULL) {
- EMSGN(_(e_listidx), idx);
- } else {
- if (argvars[2].v_type == VAR_UNKNOWN) {
- // Remove one item, return its value.
- tv_list_drop_items(l, item, item);
- *rettv = *TV_LIST_ITEM_TV(item);
- xfree(item);
- } else {
- // Remove range of items, return list with values.
- end = tv_get_number_chk(&argvars[2], &error);
- if (error) {
- // Type error: do nothing.
- } else if ((item2 = tv_list_find(l, end)) == NULL) {
- EMSGN(_(e_listidx), end);
- } else {
- int cnt = 0;
-
- for (li = item; li != NULL; li = TV_LIST_ITEM_NEXT(l, li)) {
- cnt++;
- if (li == item2) {
- break;
- }
- }
- if (li == NULL) { // Didn't find "item2" after "item".
- EMSG(_(e_invrange));
- } else {
- tv_list_move_items(l, item, item2, tv_list_alloc_ret(rettv, cnt),
- cnt);
- }
- }
- }
- }
- }
-}
-
-/*
- * "rename({from}, {to})" function
- */
-static void f_rename(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- if (check_restricted() || check_secure()) {
- rettv->vval.v_number = -1;
- } else {
- char buf[NUMBUFLEN];
- rettv->vval.v_number = vim_rename(
- (const char_u *)tv_get_string(&argvars[0]),
- (const char_u *)tv_get_string_buf(&argvars[1], buf));
- }
-}
-
-/*
- * "repeat()" function
- */
-static void f_repeat(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- varnumber_T n = tv_get_number(&argvars[1]);
- if (argvars[0].v_type == VAR_LIST) {
- tv_list_alloc_ret(rettv, (n > 0) * n * tv_list_len(argvars[0].vval.v_list));
- while (n-- > 0) {
- tv_list_extend(rettv->vval.v_list, argvars[0].vval.v_list, NULL);
- }
- } else {
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = NULL;
- if (n <= 0) {
- return;
- }
-
- const char *const p = tv_get_string(&argvars[0]);
-
- const size_t slen = strlen(p);
- if (slen == 0) {
- return;
- }
- const size_t len = slen * n;
- // Detect overflow.
- if (len / n != slen) {
- return;
- }
-
- char *const r = xmallocz(len);
- for (varnumber_T i = 0; i < n; i++) {
- memmove(r + i * slen, p, slen);
- }
-
- rettv->vval.v_string = (char_u *)r;
- }
-}
-
-/*
- * "resolve()" function
- */
-static void f_resolve(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->v_type = VAR_STRING;
- const char *fname = tv_get_string(&argvars[0]);
-#ifdef WIN32
- char *const v = os_resolve_shortcut(fname);
- rettv->vval.v_string = (char_u *)(v == NULL ? xstrdup(fname) : v);
-#else
-# ifdef HAVE_READLINK
- {
- bool is_relative_to_current = false;
- bool has_trailing_pathsep = false;
- int limit = 100;
-
- char *p = xstrdup(fname);
-
- if (p[0] == '.' && (vim_ispathsep(p[1])
- || (p[1] == '.' && (vim_ispathsep(p[2]))))) {
- is_relative_to_current = true;
- }
-
- ptrdiff_t len = (ptrdiff_t)strlen(p);
- if (len > 0 && after_pathsep(p, p + len)) {
- has_trailing_pathsep = true;
- p[len - 1] = NUL; // The trailing slash breaks readlink().
- }
-
- char *q = (char *)path_next_component(p);
- char *remain = NULL;
- if (*q != NUL) {
- // Separate the first path component in "p", and keep the
- // remainder (beginning with the path separator).
- remain = xstrdup(q - 1);
- q[-1] = NUL;
- }
-
- char *const buf = xmallocz(MAXPATHL);
-
- char *cpy;
- for (;; ) {
- for (;; ) {
- len = readlink(p, buf, MAXPATHL);
- if (len <= 0) {
- break;
- }
- buf[len] = NUL;
-
- if (limit-- == 0) {
- xfree(p);
- xfree(remain);
- EMSG(_("E655: Too many symbolic links (cycle?)"));
- rettv->vval.v_string = NULL;
- xfree(buf);
- return;
- }
-
- // Ensure that the result will have a trailing path separator
- // if the argument has one. */
- if (remain == NULL && has_trailing_pathsep) {
- add_pathsep(buf);
- }
-
- // Separate the first path component in the link value and
- // concatenate the remainders. */
- q = (char *)path_next_component(vim_ispathsep(*buf) ? buf + 1 : buf);
- if (*q != NUL) {
- cpy = remain;
- remain = (remain
- ? (char *)concat_str((char_u *)q - 1, (char_u *)remain)
- : xstrdup(q - 1));
- xfree(cpy);
- q[-1] = NUL;
- }
-
- q = (char *)path_tail((char_u *)p);
- if (q > p && *q == NUL) {
- // Ignore trailing path separator.
- q[-1] = NUL;
- q = (char *)path_tail((char_u *)p);
- }
- if (q > p && !path_is_absolute((const char_u *)buf)) {
- // Symlink is relative to directory of argument. Replace the
- // symlink with the resolved name in the same directory.
- const size_t p_len = strlen(p);
- const size_t buf_len = strlen(buf);
- p = xrealloc(p, p_len + buf_len + 1);
- memcpy(path_tail((char_u *)p), buf, buf_len + 1);
- } else {
- xfree(p);
- p = xstrdup(buf);
- }
- }
-
- if (remain == NULL) {
- break;
- }
-
- // Append the first path component of "remain" to "p".
- q = (char *)path_next_component(remain + 1);
- len = q - remain - (*q != NUL);
- const size_t p_len = strlen(p);
- cpy = xmallocz(p_len + len);
- memcpy(cpy, p, p_len + 1);
- xstrlcat(cpy + p_len, remain, len + 1);
- xfree(p);
- p = cpy;
-
- // Shorten "remain".
- if (*q != NUL) {
- STRMOVE(remain, q - 1);
- } else {
- XFREE_CLEAR(remain);
- }
- }
-
- // If the result is a relative path name, make it explicitly relative to
- // the current directory if and only if the argument had this form.
- if (!vim_ispathsep(*p)) {
- if (is_relative_to_current
- && *p != NUL
- && !(p[0] == '.'
- && (p[1] == NUL
- || vim_ispathsep(p[1])
- || (p[1] == '.'
- && (p[2] == NUL
- || vim_ispathsep(p[2])))))) {
- // Prepend "./".
- cpy = (char *)concat_str((const char_u *)"./", (const char_u *)p);
- xfree(p);
- p = cpy;
- } else if (!is_relative_to_current) {
- // Strip leading "./".
- q = p;
- while (q[0] == '.' && vim_ispathsep(q[1])) {
- q += 2;
- }
- if (q > p) {
- STRMOVE(p, p + 2);
- }
- }
- }
-
- // Ensure that the result will have no trailing path separator
- // if the argument had none. But keep "/" or "//".
- if (!has_trailing_pathsep) {
- q = p + strlen(p);
- if (after_pathsep(p, q)) {
- *path_tail_with_sep((char_u *)p) = NUL;
- }
- }
-
- rettv->vval.v_string = (char_u *)p;
- xfree(buf);
- }
-# else
- rettv->vval.v_string = (char_u *)xstrdup(p);
-# endif
-#endif
-
- simplify_filename(rettv->vval.v_string);
-}
-
-/*
- * "reverse({list})" function
- */
-static void f_reverse(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- list_T *l;
- if (argvars[0].v_type != VAR_LIST) {
- EMSG2(_(e_listarg), "reverse()");
- } else if (!tv_check_lock(tv_list_locked((l = argvars[0].vval.v_list)),
- N_("reverse() argument"), TV_TRANSLATE)) {
- tv_list_reverse(l);
- tv_list_set_ret(rettv, l);
- }
-}
-
-#define SP_NOMOVE 0x01 ///< don't move cursor
-#define SP_REPEAT 0x02 ///< repeat to find outer pair
-#define SP_RETCOUNT 0x04 ///< return matchcount
-#define SP_SETPCMARK 0x08 ///< set previous context mark
-#define SP_START 0x10 ///< accept match at start position
-#define SP_SUBPAT 0x20 ///< return nr of matching sub-pattern
-#define SP_END 0x40 ///< leave cursor at end of match
-#define SP_COLUMN 0x80 ///< start at cursor column
-
-/*
- * Get flags for a search function.
- * Possibly sets "p_ws".
- * Returns BACKWARD, FORWARD or zero (for an error).
- */
-static int get_search_arg(typval_T *varp, int *flagsp)
-{
- int dir = FORWARD;
- int mask;
-
- if (varp->v_type != VAR_UNKNOWN) {
- char nbuf[NUMBUFLEN];
- const char *flags = tv_get_string_buf_chk(varp, nbuf);
- if (flags == NULL) {
- return 0; // Type error; errmsg already given.
- }
- while (*flags != NUL) {
- switch (*flags) {
- case 'b': dir = BACKWARD; break;
- case 'w': p_ws = true; break;
- case 'W': p_ws = false; break;
- default: {
- mask = 0;
- if (flagsp != NULL) {
- switch (*flags) {
- case 'c': mask = SP_START; break;
- case 'e': mask = SP_END; break;
- case 'm': mask = SP_RETCOUNT; break;
- case 'n': mask = SP_NOMOVE; break;
- case 'p': mask = SP_SUBPAT; break;
- case 'r': mask = SP_REPEAT; break;
- case 's': mask = SP_SETPCMARK; break;
- case 'z': mask = SP_COLUMN; break;
- }
- }
- if (mask == 0) {
- emsgf(_(e_invarg2), flags);
- dir = 0;
- } else {
- *flagsp |= mask;
- }
- }
- }
- if (dir == 0) {
- break;
- }
- flags++;
- }
- }
- return dir;
-}
-
-// Shared by search() and searchpos() functions.
-static int search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp)
-{
- int flags;
- pos_T pos;
- pos_T save_cursor;
- bool save_p_ws = p_ws;
- int dir;
- int retval = 0; /* default: FAIL */
- long lnum_stop = 0;
- proftime_T tm;
- long time_limit = 0;
- int options = SEARCH_KEEP;
- int subpatnum;
-
- const char *const pat = tv_get_string(&argvars[0]);
- dir = get_search_arg(&argvars[1], flagsp); // May set p_ws.
- if (dir == 0) {
- goto theend;
- }
- flags = *flagsp;
- if (flags & SP_START) {
- options |= SEARCH_START;
- }
- if (flags & SP_END) {
- options |= SEARCH_END;
- }
- if (flags & SP_COLUMN) {
- options |= SEARCH_COL;
- }
-
- /* Optional arguments: line number to stop searching and timeout. */
- if (argvars[1].v_type != VAR_UNKNOWN && argvars[2].v_type != VAR_UNKNOWN) {
- lnum_stop = tv_get_number_chk(&argvars[2], NULL);
- if (lnum_stop < 0) {
- goto theend;
- }
- if (argvars[3].v_type != VAR_UNKNOWN) {
- time_limit = tv_get_number_chk(&argvars[3], NULL);
- if (time_limit < 0) {
- goto theend;
- }
- }
- }
-
- /* Set the time limit, if there is one. */
- tm = profile_setlimit(time_limit);
-
- /*
- * This function does not accept SP_REPEAT and SP_RETCOUNT flags.
- * Check to make sure only those flags are set.
- * Also, Only the SP_NOMOVE or the SP_SETPCMARK flag can be set. Both
- * flags cannot be set. Check for that condition also.
- */
- if (((flags & (SP_REPEAT | SP_RETCOUNT)) != 0)
- || ((flags & SP_NOMOVE) && (flags & SP_SETPCMARK))) {
- EMSG2(_(e_invarg2), tv_get_string(&argvars[1]));
- goto theend;
- }
-
- pos = save_cursor = curwin->w_cursor;
- subpatnum = searchit(curwin, curbuf, &pos, NULL, dir, (char_u *)pat, 1,
- options, RE_SEARCH, (linenr_T)lnum_stop, &tm, NULL);
- if (subpatnum != FAIL) {
- if (flags & SP_SUBPAT)
- retval = subpatnum;
- else
- retval = pos.lnum;
- if (flags & SP_SETPCMARK)
- setpcmark();
- curwin->w_cursor = pos;
- if (match_pos != NULL) {
- /* Store the match cursor position */
- match_pos->lnum = pos.lnum;
- match_pos->col = pos.col + 1;
- }
- /* "/$" will put the cursor after the end of the line, may need to
- * correct that here */
- check_cursor();
- }
-
- /* If 'n' flag is used: restore cursor position. */
- if (flags & SP_NOMOVE)
- curwin->w_cursor = save_cursor;
- else
- curwin->w_set_curswant = TRUE;
-theend:
- p_ws = save_p_ws;
-
- return retval;
-}
-
-// "rpcnotify()" function
-static void f_rpcnotify(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->v_type = VAR_NUMBER;
- rettv->vval.v_number = 0;
-
- if (check_restricted() || check_secure()) {
- return;
- }
-
- if (argvars[0].v_type != VAR_NUMBER || argvars[0].vval.v_number < 0) {
- EMSG2(_(e_invarg2), "Channel id must be a positive integer");
- return;
- }
-
- if (argvars[1].v_type != VAR_STRING) {
- EMSG2(_(e_invarg2), "Event type must be a string");
- return;
- }
-
- Array args = ARRAY_DICT_INIT;
-
- for (typval_T *tv = argvars + 2; tv->v_type != VAR_UNKNOWN; tv++) {
- ADD(args, vim_to_object(tv));
- }
-
- if (!rpc_send_event((uint64_t)argvars[0].vval.v_number,
- tv_get_string(&argvars[1]), args)) {
- EMSG2(_(e_invarg2), "Channel doesn't exist");
- return;
- }
-
- rettv->vval.v_number = 1;
-}
-
-// "rpcrequest()" function
-static void f_rpcrequest(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->v_type = VAR_NUMBER;
- rettv->vval.v_number = 0;
- const int l_provider_call_nesting = provider_call_nesting;
-
- if (check_restricted() || check_secure()) {
- return;
- }
-
- if (argvars[0].v_type != VAR_NUMBER || argvars[0].vval.v_number <= 0) {
- EMSG2(_(e_invarg2), "Channel id must be a positive integer");
- return;
- }
-
- if (argvars[1].v_type != VAR_STRING) {
- EMSG2(_(e_invarg2), "Method name must be a string");
- return;
- }
-
- Array args = ARRAY_DICT_INIT;
-
- for (typval_T *tv = argvars + 2; tv->v_type != VAR_UNKNOWN; tv++) {
- ADD(args, vim_to_object(tv));
- }
-
- sctx_T save_current_sctx;
- uint8_t *save_sourcing_name, *save_autocmd_fname, *save_autocmd_match;
- linenr_T save_sourcing_lnum;
- int save_autocmd_bufnr;
- void *save_funccalp;
-
- if (l_provider_call_nesting) {
- // If this is called from a provider function, restore the scope
- // information of the caller.
- save_current_sctx = current_sctx;
- save_sourcing_name = sourcing_name;
- save_sourcing_lnum = sourcing_lnum;
- save_autocmd_fname = autocmd_fname;
- save_autocmd_match = autocmd_match;
- save_autocmd_bufnr = autocmd_bufnr;
- save_funccalp = save_funccal();
-
- current_sctx = provider_caller_scope.script_ctx;
- sourcing_name = provider_caller_scope.sourcing_name;
- sourcing_lnum = provider_caller_scope.sourcing_lnum;
- autocmd_fname = provider_caller_scope.autocmd_fname;
- autocmd_match = provider_caller_scope.autocmd_match;
- autocmd_bufnr = provider_caller_scope.autocmd_bufnr;
- restore_funccal(provider_caller_scope.funccalp);
- }
-
-
- Error err = ERROR_INIT;
-
- uint64_t chan_id = (uint64_t)argvars[0].vval.v_number;
- const char *method = tv_get_string(&argvars[1]);
-
- Object result = rpc_send_call(chan_id, method, args, &err);
-
- if (l_provider_call_nesting) {
- current_sctx = save_current_sctx;
- sourcing_name = save_sourcing_name;
- sourcing_lnum = save_sourcing_lnum;
- autocmd_fname = save_autocmd_fname;
- autocmd_match = save_autocmd_match;
- autocmd_bufnr = save_autocmd_bufnr;
- restore_funccal(save_funccalp);
- }
-
- if (ERROR_SET(&err)) {
- const char *name = NULL;
- Channel *chan = find_channel(chan_id);
- if (chan) {
- name = rpc_client_name(chan);
- }
- msg_ext_set_kind("rpc_error");
- if (name) {
- emsgf_multiline("Error invoking '%s' on channel %"PRIu64" (%s):\n%s",
- method, chan_id, name, err.msg);
- } else {
- emsgf_multiline("Error invoking '%s' on channel %"PRIu64":\n%s",
- method, chan_id, err.msg);
- }
-
- goto end;
- }
-
- if (!object_to_vim(result, rettv, &err)) {
- EMSG2(_("Error converting the call result: %s"), err.msg);
- }
-
-end:
- api_free_object(result);
- api_clear_error(&err);
-}
-
-// "rpcstart()" function (DEPRECATED)
-static void f_rpcstart(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->v_type = VAR_NUMBER;
- rettv->vval.v_number = 0;
-
- if (check_restricted() || check_secure()) {
- return;
- }
-
- if (argvars[0].v_type != VAR_STRING
- || (argvars[1].v_type != VAR_LIST && argvars[1].v_type != VAR_UNKNOWN)) {
- // Wrong argument types
- EMSG(_(e_invarg));
- return;
- }
-
- list_T *args = NULL;
- int argsl = 0;
- if (argvars[1].v_type == VAR_LIST) {
- args = argvars[1].vval.v_list;
- argsl = tv_list_len(args);
- // Assert that all list items are strings
- int i = 0;
- TV_LIST_ITER_CONST(args, arg, {
- if (TV_LIST_ITEM_TV(arg)->v_type != VAR_STRING) {
- emsgf(_("E5010: List item %d of the second argument is not a string"),
- i);
- return;
- }
- i++;
- });
- }
-
- if (argvars[0].vval.v_string == NULL || argvars[0].vval.v_string[0] == NUL) {
- EMSG(_(e_api_spawn_failed));
- return;
- }
-
- // Allocate extra memory for the argument vector and the NULL pointer
- int argvl = argsl + 2;
- char **argv = xmalloc(sizeof(char_u *) * argvl);
-
- // Copy program name
- argv[0] = xstrdup((char *)argvars[0].vval.v_string);
-
- int i = 1;
- // Copy arguments to the vector
- if (argsl > 0) {
- TV_LIST_ITER_CONST(args, arg, {
- argv[i++] = xstrdup(tv_get_string(TV_LIST_ITEM_TV(arg)));
- });
- }
-
- // The last item of argv must be NULL
- argv[i] = NULL;
-
- Channel *chan = channel_job_start(argv, CALLBACK_READER_INIT,
- CALLBACK_READER_INIT, CALLBACK_NONE,
- false, true, false, NULL, 0, 0, NULL,
- &rettv->vval.v_number);
- if (chan) {
- channel_create_event(chan, NULL);
- }
-}
-
-// "rpcstop()" function
-static void f_rpcstop(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->v_type = VAR_NUMBER;
- rettv->vval.v_number = 0;
-
- if (check_restricted() || check_secure()) {
- return;
- }
-
- if (argvars[0].v_type != VAR_NUMBER) {
- // Wrong argument types
- EMSG(_(e_invarg));
- return;
- }
-
- // if called with a job, stop it, else closes the channel
- uint64_t id = argvars[0].vval.v_number;
- if (find_job(id, false)) {
- f_jobstop(argvars, rettv, NULL);
- } else {
- const char *error;
- rettv->vval.v_number = channel_close(argvars[0].vval.v_number,
- kChannelPartRpc, &error);
- if (!rettv->vval.v_number) {
- EMSG(error);
- }
- }
-}
-
-static void screenchar_adjust_grid(ScreenGrid **grid, int *row, int *col)
+void screenchar_adjust_grid(ScreenGrid **grid, int *row, int *col)
{
// TODO(bfredl): this is a hack for legacy tests which use screenchar()
// to check printed messages on the screen (but not floats etc
@@ -14720,496 +6889,9 @@ static void screenchar_adjust_grid(ScreenGrid **grid, int *row, int *col)
}
}
-/*
- * "screenattr()" function
- */
-static void f_screenattr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- int c;
-
- int row = (int)tv_get_number_chk(&argvars[0], NULL) - 1;
- int col = (int)tv_get_number_chk(&argvars[1], NULL) - 1;
- if (row < 0 || row >= default_grid.Rows
- || col < 0 || col >= default_grid.Columns) {
- c = -1;
- } else {
- ScreenGrid *grid = &default_grid;
- screenchar_adjust_grid(&grid, &row, &col);
- c = grid->attrs[grid->line_offset[row] + col];
- }
- rettv->vval.v_number = c;
-}
-
-/*
- * "screenchar()" function
- */
-static void f_screenchar(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- int c;
-
- int row = tv_get_number_chk(&argvars[0], NULL) - 1;
- int col = tv_get_number_chk(&argvars[1], NULL) - 1;
- if (row < 0 || row >= default_grid.Rows
- || col < 0 || col >= default_grid.Columns) {
- c = -1;
- } else {
- ScreenGrid *grid = &default_grid;
- screenchar_adjust_grid(&grid, &row, &col);
- c = utf_ptr2char(grid->chars[grid->line_offset[row] + col]);
- }
- rettv->vval.v_number = c;
-}
-
-/*
- * "screencol()" function
- *
- * First column is 1 to be consistent with virtcol().
- */
-static void f_screencol(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = ui_current_col() + 1;
-}
-
-/// "screenpos({winid}, {lnum}, {col})" function
-static void f_screenpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- pos_T pos;
- int row = 0;
- int scol = 0, ccol = 0, ecol = 0;
-
- tv_dict_alloc_ret(rettv);
- dict_T *dict = rettv->vval.v_dict;
-
- win_T *wp = find_win_by_nr_or_id(&argvars[0]);
- if (wp == NULL) {
- return;
- }
-
- pos.lnum = tv_get_number(&argvars[1]);
- pos.col = tv_get_number(&argvars[2]) - 1;
- pos.coladd = 0;
- textpos2screenpos(wp, &pos, &row, &scol, &ccol, &ecol, false);
-
- tv_dict_add_nr(dict, S_LEN("row"), row);
- tv_dict_add_nr(dict, S_LEN("col"), scol);
- tv_dict_add_nr(dict, S_LEN("curscol"), ccol);
- tv_dict_add_nr(dict, S_LEN("endcol"), ecol);
-}
-
-/*
- * "screenrow()" function
- */
-static void f_screenrow(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = ui_current_row() + 1;
-}
-
-/*
- * "search()" function
- */
-static void f_search(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- int flags = 0;
-
- rettv->vval.v_number = search_cmn(argvars, NULL, &flags);
-}
-
-/*
- * "searchdecl()" function
- */
-static void f_searchdecl(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- int locally = 1;
- int thisblock = 0;
- bool error = false;
-
- rettv->vval.v_number = 1; /* default: FAIL */
-
- const char *const name = tv_get_string_chk(&argvars[0]);
- if (argvars[1].v_type != VAR_UNKNOWN) {
- locally = tv_get_number_chk(&argvars[1], &error) == 0;
- if (!error && argvars[2].v_type != VAR_UNKNOWN) {
- thisblock = tv_get_number_chk(&argvars[2], &error) != 0;
- }
- }
- if (!error && name != NULL) {
- rettv->vval.v_number = find_decl((char_u *)name, strlen(name), locally,
- thisblock, SEARCH_KEEP) == FAIL;
- }
-}
-
-/*
- * Used by searchpair() and searchpairpos()
- */
-static int searchpair_cmn(typval_T *argvars, pos_T *match_pos)
-{
- bool save_p_ws = p_ws;
- int dir;
- int flags = 0;
- int retval = 0; // default: FAIL
- long lnum_stop = 0;
- long time_limit = 0;
-
- // Get the three pattern arguments: start, middle, end. Will result in an
- // error if not a valid argument.
- char nbuf1[NUMBUFLEN];
- char nbuf2[NUMBUFLEN];
- const char *spat = tv_get_string_chk(&argvars[0]);
- const char *mpat = tv_get_string_buf_chk(&argvars[1], nbuf1);
- const char *epat = tv_get_string_buf_chk(&argvars[2], nbuf2);
- if (spat == NULL || mpat == NULL || epat == NULL) {
- goto theend; // Type error.
- }
-
- // Handle the optional fourth argument: flags.
- dir = get_search_arg(&argvars[3], &flags); // may set p_ws.
- if (dir == 0) {
- goto theend;
- }
-
- // Don't accept SP_END or SP_SUBPAT.
- // Only one of the SP_NOMOVE or SP_SETPCMARK flags can be set.
- if ((flags & (SP_END | SP_SUBPAT)) != 0
- || ((flags & SP_NOMOVE) && (flags & SP_SETPCMARK))) {
- EMSG2(_(e_invarg2), tv_get_string(&argvars[3]));
- goto theend;
- }
-
- // Using 'r' implies 'W', otherwise it doesn't work.
- if (flags & SP_REPEAT) {
- p_ws = false;
- }
-
- // Optional fifth argument: skip expression.
- const typval_T *skip;
- if (argvars[3].v_type == VAR_UNKNOWN
- || argvars[4].v_type == VAR_UNKNOWN) {
- skip = NULL;
- } else {
- skip = &argvars[4];
- if (skip->v_type != VAR_FUNC
- && skip->v_type != VAR_PARTIAL
- && skip->v_type != VAR_STRING) {
- emsgf(_(e_invarg2), tv_get_string(&argvars[4]));
- goto theend; // Type error.
- }
- if (argvars[5].v_type != VAR_UNKNOWN) {
- lnum_stop = tv_get_number_chk(&argvars[5], NULL);
- if (lnum_stop < 0) {
- emsgf(_(e_invarg2), tv_get_string(&argvars[5]));
- goto theend;
- }
- if (argvars[6].v_type != VAR_UNKNOWN) {
- time_limit = tv_get_number_chk(&argvars[6], NULL);
- if (time_limit < 0) {
- emsgf(_(e_invarg2), tv_get_string(&argvars[6]));
- goto theend;
- }
- }
- }
- }
-
- retval = do_searchpair(
- (char_u *)spat, (char_u *)mpat, (char_u *)epat, dir, skip,
- flags, match_pos, lnum_stop, time_limit);
-
-theend:
- p_ws = save_p_ws;
-
- return retval;
-}
-
-/*
- * "searchpair()" function
- */
-static void f_searchpair(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = searchpair_cmn(argvars, NULL);
-}
-
-/*
- * "searchpairpos()" function
- */
-static void f_searchpairpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- pos_T match_pos;
- int lnum = 0;
- int col = 0;
-
- tv_list_alloc_ret(rettv, 2);
-
- if (searchpair_cmn(argvars, &match_pos) > 0) {
- lnum = match_pos.lnum;
- col = match_pos.col;
- }
-
- tv_list_append_number(rettv->vval.v_list, (varnumber_T)lnum);
- tv_list_append_number(rettv->vval.v_list, (varnumber_T)col);
-}
-
-/*
- * Search for a start/middle/end thing.
- * Used by searchpair(), see its documentation for the details.
- * Returns 0 or -1 for no match,
- */
-long
-do_searchpair(
- char_u *spat, // start pattern
- char_u *mpat, // middle pattern
- char_u *epat, // end pattern
- int dir, // BACKWARD or FORWARD
- const typval_T *skip, // skip expression
- int flags, // SP_SETPCMARK and other SP_ values
- pos_T *match_pos,
- linenr_T lnum_stop, // stop at this line if not zero
- long time_limit // stop after this many msec
-)
-{
- char_u *save_cpo;
- char_u *pat, *pat2 = NULL, *pat3 = NULL;
- long retval = 0;
- pos_T pos;
- pos_T firstpos;
- pos_T foundpos;
- pos_T save_cursor;
- pos_T save_pos;
- int n;
- int nest = 1;
- bool use_skip = false;
- int options = SEARCH_KEEP;
- proftime_T tm;
- size_t pat2_len;
- size_t pat3_len;
-
- /* Make 'cpoptions' empty, the 'l' flag should not be used here. */
- save_cpo = p_cpo;
- p_cpo = empty_option;
-
- /* Set the time limit, if there is one. */
- tm = profile_setlimit(time_limit);
-
- // Make two search patterns: start/end (pat2, for in nested pairs) and
- // start/middle/end (pat3, for the top pair).
- pat2_len = STRLEN(spat) + STRLEN(epat) + 17;
- pat2 = xmalloc(pat2_len);
- pat3_len = STRLEN(spat) + STRLEN(mpat) + STRLEN(epat) + 25;
- pat3 = xmalloc(pat3_len);
- snprintf((char *)pat2, pat2_len, "\\m\\(%s\\m\\)\\|\\(%s\\m\\)", spat, epat);
- if (*mpat == NUL) {
- STRCPY(pat3, pat2);
- } else {
- snprintf((char *)pat3, pat3_len,
- "\\m\\(%s\\m\\)\\|\\(%s\\m\\)\\|\\(%s\\m\\)", spat, epat, mpat);
- }
- if (flags & SP_START) {
- options |= SEARCH_START;
- }
-
- if (skip != NULL) {
- // Empty string means to not use the skip expression.
- if (skip->v_type == VAR_STRING || skip->v_type == VAR_FUNC) {
- use_skip = skip->vval.v_string != NULL && *skip->vval.v_string != NUL;
- }
- }
-
- save_cursor = curwin->w_cursor;
- pos = curwin->w_cursor;
- clearpos(&firstpos);
- clearpos(&foundpos);
- pat = pat3;
- for (;; ) {
- n = searchit(curwin, curbuf, &pos, NULL, dir, pat, 1L,
- options, RE_SEARCH, lnum_stop, &tm, NULL);
- if (n == FAIL || (firstpos.lnum != 0 && equalpos(pos, firstpos))) {
- // didn't find it or found the first match again: FAIL
- break;
- }
-
- if (firstpos.lnum == 0)
- firstpos = pos;
- if (equalpos(pos, foundpos)) {
- /* Found the same position again. Can happen with a pattern that
- * has "\zs" at the end and searching backwards. Advance one
- * character and try again. */
- if (dir == BACKWARD)
- decl(&pos);
- else
- incl(&pos);
- }
- foundpos = pos;
-
- /* clear the start flag to avoid getting stuck here */
- options &= ~SEARCH_START;
-
- // If the skip pattern matches, ignore this match.
- if (use_skip) {
- save_pos = curwin->w_cursor;
- curwin->w_cursor = pos;
- bool err = false;
- const bool r = eval_expr_to_bool(skip, &err);
- curwin->w_cursor = save_pos;
- if (err) {
- /* Evaluating {skip} caused an error, break here. */
- curwin->w_cursor = save_cursor;
- retval = -1;
- break;
- }
- if (r)
- continue;
- }
-
- if ((dir == BACKWARD && n == 3) || (dir == FORWARD && n == 2)) {
- /* Found end when searching backwards or start when searching
- * forward: nested pair. */
- ++nest;
- pat = pat2; /* nested, don't search for middle */
- } else {
- /* Found end when searching forward or start when searching
- * backward: end of (nested) pair; or found middle in outer pair. */
- if (--nest == 1)
- pat = pat3; /* outer level, search for middle */
- }
-
- if (nest == 0) {
- /* Found the match: return matchcount or line number. */
- if (flags & SP_RETCOUNT)
- ++retval;
- else
- retval = pos.lnum;
- if (flags & SP_SETPCMARK)
- setpcmark();
- curwin->w_cursor = pos;
- if (!(flags & SP_REPEAT))
- break;
- nest = 1; /* search for next unmatched */
- }
- }
-
- if (match_pos != NULL) {
- /* Store the match cursor position */
- match_pos->lnum = curwin->w_cursor.lnum;
- match_pos->col = curwin->w_cursor.col + 1;
- }
-
- /* If 'n' flag is used or search failed: restore cursor position. */
- if ((flags & SP_NOMOVE) || retval == 0)
- curwin->w_cursor = save_cursor;
-
- xfree(pat2);
- xfree(pat3);
- if (p_cpo == empty_option)
- p_cpo = save_cpo;
- else
- /* Darn, evaluating the {skip} expression changed the value. */
- free_string_option(save_cpo);
-
- return retval;
-}
-
-/*
- * "searchpos()" function
- */
-static void f_searchpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- pos_T match_pos;
- int flags = 0;
-
- const int n = search_cmn(argvars, &match_pos, &flags);
-
- tv_list_alloc_ret(rettv, 2 + (!!(flags & SP_SUBPAT)));
-
- const int lnum = (n > 0 ? match_pos.lnum : 0);
- const int col = (n > 0 ? match_pos.col : 0);
-
- tv_list_append_number(rettv->vval.v_list, (varnumber_T)lnum);
- tv_list_append_number(rettv->vval.v_list, (varnumber_T)col);
- if (flags & SP_SUBPAT) {
- tv_list_append_number(rettv->vval.v_list, (varnumber_T)n);
- }
-}
-
-/// "serverlist()" function
-static void f_serverlist(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- size_t n;
- char **addrs = server_address_list(&n);
-
- // Copy addrs into a linked list.
- list_T *const l = tv_list_alloc_ret(rettv, n);
- for (size_t i = 0; i < n; i++) {
- tv_list_append_allocated_string(l, addrs[i]);
- }
- xfree(addrs);
-}
-
-/// "serverstart()" function
-static void f_serverstart(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = NULL; // Address of the new server
-
- if (check_restricted() || check_secure()) {
- return;
- }
-
- char *address;
- // If the user supplied an address, use it, otherwise use a temp.
- if (argvars[0].v_type != VAR_UNKNOWN) {
- if (argvars[0].v_type != VAR_STRING) {
- EMSG(_(e_invarg));
- return;
- } else {
- address = xstrdup(tv_get_string(argvars));
- }
- } else {
- address = server_address_new();
- }
-
- int result = server_start(address);
- xfree(address);
-
- if (result != 0) {
- EMSG2("Failed to start server: %s",
- result > 0 ? "Unknown system error" : uv_strerror(result));
- return;
- }
-
- // Since it's possible server_start adjusted the given {address} (e.g.,
- // "localhost:" will now have a port), return the final value to the user.
- size_t n;
- char **addrs = server_address_list(&n);
- rettv->vval.v_string = (char_u *)addrs[n - 1];
-
- n--;
- for (size_t i = 0; i < n; i++) {
- xfree(addrs[i]);
- }
- xfree(addrs);
-}
-
-/// "serverstop()" function
-static void f_serverstop(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- if (check_restricted() || check_secure()) {
- return;
- }
-
- if (argvars[0].v_type != VAR_STRING) {
- EMSG(_(e_invarg));
- return;
- }
-
- rettv->v_type = VAR_NUMBER;
- rettv->vval.v_number = 0;
- if (argvars[0].vval.v_string) {
- bool rv = server_stop((char *)argvars[0].vval.v_string);
- rettv->vval.v_number = (rv ? 1 : 0);
- }
-}
-
/// Set line or list of lines in buffer "buf".
-static void set_buffer_lines(buf_T *buf, linenr_T lnum_arg, bool append,
- const typval_T *lines, typval_T *rettv)
+void set_buffer_lines(buf_T *buf, linenr_T lnum_arg, bool append,
+ const typval_T *lines, typval_T *rettv)
FUNC_ATTR_NONNULL_ARG(4, 5)
{
linenr_T lnum = lnum_arg + (append ? 1 : 0);
@@ -15323,632 +7005,11 @@ static void set_buffer_lines(buf_T *buf, linenr_T lnum_arg, bool append,
}
}
-/// "setbufline()" function
-static void f_setbufline(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- linenr_T lnum;
- buf_T *buf;
-
- buf = tv_get_buf(&argvars[0], false);
- if (buf == NULL) {
- rettv->vval.v_number = 1; // FAIL
- } else {
- lnum = tv_get_lnum_buf(&argvars[1], buf);
- set_buffer_lines(buf, lnum, false, &argvars[2], rettv);
- }
-}
-
-/*
- * "setbufvar()" function
- */
-static void f_setbufvar(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- if (check_restricted()
- || check_secure()
- || !tv_check_str_or_nr(&argvars[0])) {
- return;
- }
- const char *varname = tv_get_string_chk(&argvars[1]);
- buf_T *const buf = tv_get_buf(&argvars[0], false);
- typval_T *varp = &argvars[2];
-
- if (buf != NULL && varname != NULL) {
- if (*varname == '&') {
- long numval;
- bool error = false;
- aco_save_T aco;
-
- // set curbuf to be our buf, temporarily
- aucmd_prepbuf(&aco, buf);
-
- varname++;
- numval = tv_get_number_chk(varp, &error);
- char nbuf[NUMBUFLEN];
- const char *const strval = tv_get_string_buf_chk(varp, nbuf);
- if (!error && strval != NULL) {
- set_option_value(varname, numval, strval, OPT_LOCAL);
- }
-
- // reset notion of buffer
- aucmd_restbuf(&aco);
- } else {
- buf_T *save_curbuf = curbuf;
-
- const size_t varname_len = STRLEN(varname);
- char *const bufvarname = xmalloc(varname_len + 3);
- curbuf = buf;
- memcpy(bufvarname, "b:", 2);
- memcpy(bufvarname + 2, varname, varname_len + 1);
- set_var(bufvarname, varname_len + 2, varp, true);
- xfree(bufvarname);
- curbuf = save_curbuf;
- }
- }
-}
-
-static void f_setcharsearch(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- dict_T *d;
- dictitem_T *di;
-
- if (argvars[0].v_type != VAR_DICT) {
- EMSG(_(e_dictreq));
- return;
- }
-
- if ((d = argvars[0].vval.v_dict) != NULL) {
- char_u *const csearch = (char_u *)tv_dict_get_string(d, "char", false);
- if (csearch != NULL) {
- if (enc_utf8) {
- int pcc[MAX_MCO];
- int c = utfc_ptr2char(csearch, pcc);
- set_last_csearch(c, csearch, utfc_ptr2len(csearch));
- }
- else
- set_last_csearch(PTR2CHAR(csearch),
- csearch, MB_PTR2LEN(csearch));
- }
-
- di = tv_dict_find(d, S_LEN("forward"));
- if (di != NULL) {
- set_csearch_direction(tv_get_number(&di->di_tv) ? FORWARD : BACKWARD);
- }
-
- di = tv_dict_find(d, S_LEN("until"));
- if (di != NULL) {
- set_csearch_until(!!tv_get_number(&di->di_tv));
- }
- }
-}
-
-/*
- * "setcmdpos()" function
- */
-static void f_setcmdpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const int pos = (int)tv_get_number(&argvars[0]) - 1;
-
- if (pos >= 0) {
- rettv->vval.v_number = set_cmdline_pos(pos);
- }
-}
-
-/// "setenv()" function
-static void f_setenv(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- char namebuf[NUMBUFLEN];
- char valbuf[NUMBUFLEN];
- const char *name = tv_get_string_buf(&argvars[0], namebuf);
-
- if (argvars[1].v_type == VAR_SPECIAL
- && argvars[1].vval.v_number == kSpecialVarNull) {
- os_unsetenv(name);
- } else {
- os_setenv(name, tv_get_string_buf(&argvars[1], valbuf), 1);
- }
-}
-
-/// "setfperm({fname}, {mode})" function
-static void f_setfperm(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = 0;
-
- const char *const fname = tv_get_string_chk(&argvars[0]);
- if (fname == NULL) {
- return;
- }
-
- char modebuf[NUMBUFLEN];
- const char *const mode_str = tv_get_string_buf_chk(&argvars[1], modebuf);
- if (mode_str == NULL) {
- return;
- }
- if (strlen(mode_str) != 9) {
- EMSG2(_(e_invarg2), mode_str);
- return;
- }
-
- int mask = 1;
- int mode = 0;
- for (int i = 8; i >= 0; i--) {
- if (mode_str[i] != '-') {
- mode |= mask;
- }
- mask = mask << 1;
- }
- rettv->vval.v_number = os_setperm(fname, mode) == OK;
-}
-
-/*
- * "setline()" function
- */
-static void f_setline(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- linenr_T lnum = tv_get_lnum(&argvars[0]);
- set_buffer_lines(curbuf, lnum, false, &argvars[1], rettv);
-}
-
-/// Create quickfix/location list from VimL values
-///
-/// Used by `setqflist()` and `setloclist()` functions. Accepts invalid
-/// list_arg, action_arg and what_arg arguments in which case errors out,
-/// including VAR_UNKNOWN parameters.
-///
-/// @param[in,out] wp Window to create location list for. May be NULL in
-/// which case quickfix list will be created.
-/// @param[in] list_arg Quickfix list contents.
-/// @param[in] action_arg Action to perform: append to an existing list,
-/// replace its content or create a new one.
-/// @param[in] title_arg New list title. Defaults to caller function name.
-/// @param[out] rettv Return value: 0 in case of success, -1 otherwise.
-static void set_qf_ll_list(win_T *wp, typval_T *args, typval_T *rettv)
- FUNC_ATTR_NONNULL_ARG(2, 3)
-{
- static char *e_invact = N_("E927: Invalid action: '%s'");
- const char *title = NULL;
- int action = ' ';
- static int recursive = 0;
- rettv->vval.v_number = -1;
- dict_T *d = NULL;
-
- typval_T *list_arg = &args[0];
- if (list_arg->v_type != VAR_LIST) {
- EMSG(_(e_listreq));
- return;
- } else if (recursive != 0) {
- EMSG(_(e_au_recursive));
- return;
- }
-
- typval_T *action_arg = &args[1];
- if (action_arg->v_type == VAR_UNKNOWN) {
- // Option argument was not given.
- goto skip_args;
- } else if (action_arg->v_type != VAR_STRING) {
- EMSG(_(e_stringreq));
- return;
- }
- const char *const act = tv_get_string_chk(action_arg);
- if ((*act == 'a' || *act == 'r' || *act == ' ' || *act == 'f')
- && act[1] == NUL) {
- action = *act;
- } else {
- EMSG2(_(e_invact), act);
- return;
- }
-
- typval_T *title_arg = &args[2];
- if (title_arg->v_type == VAR_UNKNOWN) {
- // Option argument was not given.
- goto skip_args;
- } else if (title_arg->v_type == VAR_STRING) {
- title = tv_get_string_chk(title_arg);
- if (!title) {
- // Type error. Error already printed by tv_get_string_chk().
- return;
- }
- } else if (title_arg->v_type == VAR_DICT) {
- d = title_arg->vval.v_dict;
- } else {
- EMSG(_(e_dictreq));
- return;
- }
-
-skip_args:
- if (!title) {
- title = (wp ? ":setloclist()" : ":setqflist()");
- }
-
- recursive++;
- list_T *const l = list_arg->vval.v_list;
- if (set_errorlist(wp, l, action, (char_u *)title, d) == OK) {
- rettv->vval.v_number = 0;
- }
- recursive--;
-}
-
-/*
- * "setloclist()" function
- */
-static void f_setloclist(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- win_T *win;
-
- rettv->vval.v_number = -1;
-
- win = find_win_by_nr_or_id(&argvars[0]);
- if (win != NULL) {
- set_qf_ll_list(win, &argvars[1], rettv);
- }
-}
-
-/*
- * "setmatches()" function
- */
-static void f_setmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- dict_T *d;
- list_T *s = NULL;
-
- rettv->vval.v_number = -1;
- if (argvars[0].v_type != VAR_LIST) {
- EMSG(_(e_listreq));
- return;
- }
- list_T *const l = argvars[0].vval.v_list;
- // To some extent make sure that we are dealing with a list from
- // "getmatches()".
- int li_idx = 0;
- TV_LIST_ITER_CONST(l, li, {
- if (TV_LIST_ITEM_TV(li)->v_type != VAR_DICT
- || (d = TV_LIST_ITEM_TV(li)->vval.v_dict) == NULL) {
- emsgf(_("E474: List item %d is either not a dictionary "
- "or an empty one"), li_idx);
- return;
- }
- if (!(tv_dict_find(d, S_LEN("group")) != NULL
- && (tv_dict_find(d, S_LEN("pattern")) != NULL
- || tv_dict_find(d, S_LEN("pos1")) != NULL)
- && tv_dict_find(d, S_LEN("priority")) != NULL
- && tv_dict_find(d, S_LEN("id")) != NULL)) {
- emsgf(_("E474: List item %d is missing one of the required keys"),
- li_idx);
- return;
- }
- li_idx++;
- });
-
- clear_matches(curwin);
- bool match_add_failed = false;
- TV_LIST_ITER_CONST(l, li, {
- int i = 0;
-
- d = TV_LIST_ITEM_TV(li)->vval.v_dict;
- dictitem_T *const di = tv_dict_find(d, S_LEN("pattern"));
- if (di == NULL) {
- if (s == NULL) {
- s = tv_list_alloc(9);
- }
-
- // match from matchaddpos()
- for (i = 1; i < 9; i++) {
- char buf[30]; // use 30 to avoid compiler warning
- snprintf(buf, sizeof(buf), "pos%d", i);
- dictitem_T *const pos_di = tv_dict_find(d, buf, -1);
- if (pos_di != NULL) {
- if (pos_di->di_tv.v_type != VAR_LIST) {
- return;
- }
-
- tv_list_append_tv(s, &pos_di->di_tv);
- tv_list_ref(s);
- } else {
- break;
- }
- }
- }
-
- // Note: there are three number buffers involved:
- // - group_buf below.
- // - numbuf in tv_dict_get_string().
- // - mybuf in tv_get_string().
- //
- // If you change this code make sure that buffers will not get
- // accidentally reused.
- char group_buf[NUMBUFLEN];
- const char *const group = tv_dict_get_string_buf(d, "group", group_buf);
- const int priority = (int)tv_dict_get_number(d, "priority");
- const int id = (int)tv_dict_get_number(d, "id");
- dictitem_T *const conceal_di = tv_dict_find(d, S_LEN("conceal"));
- const char *const conceal = (conceal_di != NULL
- ? tv_get_string(&conceal_di->di_tv)
- : NULL);
- if (i == 0) {
- if (match_add(curwin, group,
- tv_dict_get_string(d, "pattern", false),
- priority, id, NULL, conceal) != id) {
- match_add_failed = true;
- }
- } else {
- if (match_add(curwin, group, NULL, priority, id, s, conceal) != id) {
- match_add_failed = true;
- }
- tv_list_unref(s);
- s = NULL;
- }
- });
- if (!match_add_failed) {
- rettv->vval.v_number = 0;
- }
-}
-
-/*
- * "setpos()" function
- */
-static void f_setpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- pos_T pos;
- int fnum;
- colnr_T curswant = -1;
-
- rettv->vval.v_number = -1;
- const char *const name = tv_get_string_chk(argvars);
- if (name != NULL) {
- if (list2fpos(&argvars[1], &pos, &fnum, &curswant) == OK) {
- if (--pos.col < 0) {
- pos.col = 0;
- }
- if (name[0] == '.' && name[1] == NUL) {
- // set cursor; "fnum" is ignored
- curwin->w_cursor = pos;
- if (curswant >= 0) {
- curwin->w_curswant = curswant - 1;
- curwin->w_set_curswant = false;
- }
- check_cursor();
- rettv->vval.v_number = 0;
- } else if (name[0] == '\'' && name[1] != NUL && name[2] == NUL) {
- // set mark
- if (setmark_pos((uint8_t)name[1], &pos, fnum) == OK) {
- rettv->vval.v_number = 0;
- }
- } else {
- EMSG(_(e_invarg));
- }
- }
- }
-}
-
-/*
- * "setqflist()" function
- */
-static void f_setqflist(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- set_qf_ll_list(NULL, argvars, rettv);
-}
-
-/*
- * "setreg()" function
- */
-static void f_setreg(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- int regname;
- bool append = false;
- MotionType yank_type;
- long block_len;
-
- block_len = -1;
- yank_type = kMTUnknown;
-
- rettv->vval.v_number = 1; // FAIL is default.
-
- const char *const strregname = tv_get_string_chk(argvars);
- if (strregname == NULL) {
- return; // Type error; errmsg already given.
- }
- regname = (uint8_t)(*strregname);
- if (regname == 0 || regname == '@') {
- regname = '"';
- }
-
- bool set_unnamed = false;
- if (argvars[2].v_type != VAR_UNKNOWN) {
- const char *stropt = tv_get_string_chk(&argvars[2]);
- if (stropt == NULL) {
- return; // Type error.
- }
- for (; *stropt != NUL; stropt++) {
- switch (*stropt) {
- case 'a': case 'A': { // append
- append = true;
- break;
- }
- case 'v': case 'c': { // character-wise selection
- yank_type = kMTCharWise;
- break;
- }
- case 'V': case 'l': { // line-wise selection
- yank_type = kMTLineWise;
- break;
- }
- case 'b': case Ctrl_V: { // block-wise selection
- yank_type = kMTBlockWise;
- if (ascii_isdigit(stropt[1])) {
- stropt++;
- block_len = getdigits_long((char_u **)&stropt, true, 0) - 1;
- stropt--;
- }
- break;
- }
- case 'u': case '"': { // unnamed register
- set_unnamed = true;
- break;
- }
- }
- }
- }
-
- if (argvars[1].v_type == VAR_LIST) {
- list_T *ll = argvars[1].vval.v_list;
- // If the list is NULL handle like an empty list.
- const int len = tv_list_len(ll);
-
- // First half: use for pointers to result lines; second half: use for
- // pointers to allocated copies.
- char **lstval = xmalloc(sizeof(char *) * ((len + 1) * 2));
- const char **curval = (const char **)lstval;
- char **allocval = lstval + len + 2;
- char **curallocval = allocval;
-
- TV_LIST_ITER_CONST(ll, li, {
- char buf[NUMBUFLEN];
- *curval = tv_get_string_buf_chk(TV_LIST_ITEM_TV(li), buf);
- if (*curval == NULL) {
- goto free_lstval;
- }
- if (*curval == buf) {
- // Need to make a copy,
- // next tv_get_string_buf_chk() will overwrite the string.
- *curallocval = xstrdup(*curval);
- *curval = *curallocval;
- curallocval++;
- }
- curval++;
- });
- *curval++ = NULL;
-
- write_reg_contents_lst(regname, (char_u **)lstval, append, yank_type,
- block_len);
-
-free_lstval:
- while (curallocval > allocval) {
- xfree(*--curallocval);
- }
- xfree(lstval);
- } else {
- const char *strval = tv_get_string_chk(&argvars[1]);
- if (strval == NULL) {
- return;
- }
- write_reg_contents_ex(regname, (const char_u *)strval, STRLEN(strval),
- append, yank_type, block_len);
- }
- rettv->vval.v_number = 0;
-
- if (set_unnamed) {
- // Discard the result. We already handle the error case.
- if (op_reg_set_previous(regname)) { }
- }
-}
-
-/*
- * "settabvar()" function
- */
-static void f_settabvar(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = 0;
-
- if (check_restricted() || check_secure()) {
- return;
- }
-
- tabpage_T *const tp = find_tabpage((int)tv_get_number_chk(&argvars[0], NULL));
- const char *const varname = tv_get_string_chk(&argvars[1]);
- typval_T *const varp = &argvars[2];
-
- if (varname != NULL && tp != NULL) {
- tabpage_T *const save_curtab = curtab;
- goto_tabpage_tp(tp, false, false);
-
- const size_t varname_len = strlen(varname);
- char *const tabvarname = xmalloc(varname_len + 3);
- memcpy(tabvarname, "t:", 2);
- memcpy(tabvarname + 2, varname, varname_len + 1);
- set_var(tabvarname, varname_len + 2, varp, true);
- xfree(tabvarname);
-
- // Restore current tabpage.
- if (valid_tabpage(save_curtab)) {
- goto_tabpage_tp(save_curtab, false, false);
- }
- }
-}
-
-/*
- * "settabwinvar()" function
- */
-static void f_settabwinvar(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- setwinvar(argvars, rettv, 1);
-}
-
-// "settagstack()" function
-static void f_settagstack(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- static char *e_invact2 = N_("E962: Invalid action: '%s'");
- win_T *wp;
- dict_T *d;
- int action = 'r';
-
- rettv->vval.v_number = -1;
-
- // first argument: window number or id
- wp = find_win_by_nr_or_id(&argvars[0]);
- if (wp == NULL) {
- return;
- }
-
- // second argument: dict with items to set in the tag stack
- if (argvars[1].v_type != VAR_DICT) {
- EMSG(_(e_dictreq));
- return;
- }
- d = argvars[1].vval.v_dict;
- if (d == NULL) {
- return;
- }
-
- // third argument: action - 'a' for append and 'r' for replace.
- // default is to replace the stack.
- if (argvars[2].v_type == VAR_UNKNOWN) {
- action = 'r';
- } else if (argvars[2].v_type == VAR_STRING) {
- const char *actstr;
- actstr = tv_get_string_chk(&argvars[2]);
- if (actstr == NULL) {
- return;
- }
- if ((*actstr == 'r' || *actstr == 'a') && actstr[1] == NUL) {
- action = *actstr;
- } else {
- EMSG2(_(e_invact2), actstr);
- return;
- }
- } else {
- EMSG(_(e_stringreq));
- return;
- }
-
- if (set_tagstack(wp, d, action) == OK) {
- rettv->vval.v_number = 0;
- } else {
- EMSG(_(e_listreq));
- }
-}
-
-/*
- * "setwinvar()" function
- */
-static void f_setwinvar(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- setwinvar(argvars, rettv, 0);
-}
-
/*
* "setwinvar()" and "settabwinvar()" functions
*/
-static void setwinvar(typval_T *argvars, typval_T *rettv, int off)
+void setwinvar(typval_T *argvars, typval_T *rettv, int off)
{
if (check_secure()) {
return;
@@ -15996,990 +7057,8 @@ static void setwinvar(typval_T *argvars, typval_T *rettv, int off)
}
}
-/// f_sha256 - sha256({string}) function
-static void f_sha256(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const char *p = tv_get_string(&argvars[0]);
- const char *hash = sha256_bytes((const uint8_t *)p, strlen(p) , NULL, 0);
-
- // make a copy of the hash (sha256_bytes returns a static buffer)
- rettv->vval.v_string = (char_u *)xstrdup(hash);
- rettv->v_type = VAR_STRING;
-}
-
-/*
- * "shellescape({string})" function
- */
-static void f_shellescape(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const bool do_special = non_zero_arg(&argvars[1]);
-
- rettv->vval.v_string = vim_strsave_shellescape(
- (const char_u *)tv_get_string(&argvars[0]), do_special, do_special);
- rettv->v_type = VAR_STRING;
-}
-
-/*
- * shiftwidth() function
- */
-static void f_shiftwidth(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = get_sw_value(curbuf);
-}
-
-/// "sign_define()" function
-static void f_sign_define(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const char *name;
- dict_T *dict;
- char *icon = NULL;
- char *linehl = NULL;
- char *text = NULL;
- char *texthl = NULL;
- char *numhl = NULL;
-
- rettv->vval.v_number = -1;
-
- name = tv_get_string_chk(&argvars[0]);
- if (name == NULL) {
- return;
- }
-
- if (argvars[1].v_type != VAR_UNKNOWN) {
- if (argvars[1].v_type != VAR_DICT) {
- EMSG(_(e_dictreq));
- return;
- }
-
- // sign attributes
- dict = argvars[1].vval.v_dict;
- if (tv_dict_find(dict, "icon", -1) != NULL) {
- icon = tv_dict_get_string(dict, "icon", true);
- }
- if (tv_dict_find(dict, "linehl", -1) != NULL) {
- linehl = tv_dict_get_string(dict, "linehl", true);
- }
- if (tv_dict_find(dict, "text", -1) != NULL) {
- text = tv_dict_get_string(dict, "text", true);
- }
- if (tv_dict_find(dict, "texthl", -1) != NULL) {
- texthl = tv_dict_get_string(dict, "texthl", true);
- }
- if (tv_dict_find(dict, "numhl", -1) != NULL) {
- numhl = tv_dict_get_string(dict, "numhl", true);
- }
- }
-
- if (sign_define_by_name((char_u *)name, (char_u *)icon, (char_u *)linehl,
- (char_u *)text, (char_u *)texthl, (char_u *)numhl)
- == OK) {
- rettv->vval.v_number = 0;
- }
-
- xfree(icon);
- xfree(linehl);
- xfree(text);
- xfree(texthl);
-}
-
-/// "sign_getdefined()" function
-static void f_sign_getdefined(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const char *name = NULL;
-
- tv_list_alloc_ret(rettv, 0);
-
- if (argvars[0].v_type != VAR_UNKNOWN) {
- name = tv_get_string(&argvars[0]);
- }
-
- sign_getlist((const char_u *)name, rettv->vval.v_list);
-}
-
-/// "sign_getplaced()" function
-static void f_sign_getplaced(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- buf_T *buf = NULL;
- dict_T *dict;
- dictitem_T *di;
- linenr_T lnum = 0;
- int sign_id = 0;
- const char *group = NULL;
- bool notanum = false;
-
- tv_list_alloc_ret(rettv, 0);
-
- if (argvars[0].v_type != VAR_UNKNOWN) {
- // get signs placed in the specified buffer
- buf = get_buf_arg(&argvars[0]);
- if (buf == NULL) {
- return;
- }
-
- if (argvars[1].v_type != VAR_UNKNOWN) {
- if (argvars[1].v_type != VAR_DICT
- || ((dict = argvars[1].vval.v_dict) == NULL)) {
- EMSG(_(e_dictreq));
- return;
- }
- if ((di = tv_dict_find(dict, "lnum", -1)) != NULL) {
- // get signs placed at this line
- lnum = (linenr_T)tv_get_number_chk(&di->di_tv, &notanum);
- if (notanum) {
- return;
- }
- (void)lnum;
- lnum = tv_get_lnum(&di->di_tv);
- }
- if ((di = tv_dict_find(dict, "id", -1)) != NULL) {
- // get sign placed with this identifier
- sign_id = (int)tv_get_number_chk(&di->di_tv, &notanum);
- if (notanum) {
- return;
- }
- }
- if ((di = tv_dict_find(dict, "group", -1)) != NULL) {
- group = tv_get_string_chk(&di->di_tv);
- if (group == NULL) {
- return;
- }
- if (*group == '\0') { // empty string means global group
- group = NULL;
- }
- }
- }
- }
-
- sign_get_placed(buf, lnum, sign_id, (const char_u *)group,
- rettv->vval.v_list);
-}
-
-/// "sign_jump()" function
-static void f_sign_jump(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- int sign_id;
- char *sign_group = NULL;
- buf_T *buf;
- bool notanum = false;
-
- rettv->vval.v_number = -1;
-
- // Sign identifer
- sign_id = (int)tv_get_number_chk(&argvars[0], &notanum);
- if (notanum) {
- return;
- }
- if (sign_id <= 0) {
- EMSG(_(e_invarg));
- return;
- }
-
- // Sign group
- const char * sign_group_chk = tv_get_string_chk(&argvars[1]);
- if (sign_group_chk == NULL) {
- return;
- }
- if (sign_group_chk[0] == '\0') {
- sign_group = NULL; // global sign group
- } else {
- sign_group = xstrdup(sign_group_chk);
- }
-
- // Buffer to place the sign
- buf = get_buf_arg(&argvars[2]);
- if (buf == NULL) {
- goto cleanup;
- }
-
- rettv->vval.v_number = sign_jump(sign_id, (char_u *)sign_group, buf);
-
-cleanup:
- xfree(sign_group);
-}
-
-/// "sign_place()" function
-static void f_sign_place(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- int sign_id;
- char_u *group = NULL;
- const char *sign_name;
- buf_T *buf;
- dict_T *dict;
- dictitem_T *di;
- linenr_T lnum = 0;
- int prio = SIGN_DEF_PRIO;
- bool notanum = false;
-
- rettv->vval.v_number = -1;
-
- // Sign identifer
- sign_id = (int)tv_get_number_chk(&argvars[0], &notanum);
- if (notanum) {
- return;
- }
- if (sign_id < 0) {
- EMSG(_(e_invarg));
- return;
- }
-
- // Sign group
- const char *group_chk = tv_get_string_chk(&argvars[1]);
- if (group_chk == NULL) {
- return;
- }
- if (group_chk[0] == '\0') {
- group = NULL; // global sign group
- } else {
- group = vim_strsave((const char_u *)group_chk);
- }
-
- // Sign name
- sign_name = tv_get_string_chk(&argvars[2]);
- if (sign_name == NULL) {
- goto cleanup;
- }
-
- // Buffer to place the sign
- buf = get_buf_arg(&argvars[3]);
- if (buf == NULL) {
- goto cleanup;
- }
-
- if (argvars[4].v_type != VAR_UNKNOWN) {
- if (argvars[4].v_type != VAR_DICT
- || ((dict = argvars[4].vval.v_dict) == NULL)) {
- EMSG(_(e_dictreq));
- goto cleanup;
- }
-
- // Line number where the sign is to be placed
- if ((di = tv_dict_find(dict, "lnum", -1)) != NULL) {
- lnum = (linenr_T)tv_get_number_chk(&di->di_tv, &notanum);
- if (notanum) {
- goto cleanup;
- }
- (void)lnum;
- lnum = tv_get_lnum(&di->di_tv);
- }
- if ((di = tv_dict_find(dict, "priority", -1)) != NULL) {
- // Sign priority
- prio = (int)tv_get_number_chk(&di->di_tv, &notanum);
- if (notanum) {
- goto cleanup;
- }
- }
- }
-
- if (sign_place(&sign_id, group, (const char_u *)sign_name, buf, lnum, prio)
- == OK) {
- rettv->vval.v_number = sign_id;
- }
-
-cleanup:
- xfree(group);
-}
-
-/// "sign_undefine()" function
-static void f_sign_undefine(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const char *name;
-
- rettv->vval.v_number = -1;
-
- if (argvars[0].v_type == VAR_UNKNOWN) {
- // Free all the signs
- free_signs();
- rettv->vval.v_number = 0;
- } else {
- // Free only the specified sign
- name = tv_get_string_chk(&argvars[0]);
- if (name == NULL) {
- return;
- }
-
- if (sign_undefine_by_name((const char_u *)name) == OK) {
- rettv->vval.v_number = 0;
- }
- }
-}
-
-/// "sign_unplace()" function
-static void f_sign_unplace(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- dict_T *dict;
- dictitem_T *di;
- int sign_id = 0;
- buf_T *buf = NULL;
- char_u *group = NULL;
-
- rettv->vval.v_number = -1;
-
- if (argvars[0].v_type != VAR_STRING) {
- EMSG(_(e_invarg));
- return;
- }
-
- const char *group_chk = tv_get_string(&argvars[0]);
- if (group_chk[0] == '\0') {
- group = NULL; // global sign group
- } else {
- group = vim_strsave((const char_u *)group_chk);
- }
-
- if (argvars[1].v_type != VAR_UNKNOWN) {
- if (argvars[1].v_type != VAR_DICT) {
- EMSG(_(e_dictreq));
- goto cleanup;
- }
- dict = argvars[1].vval.v_dict;
-
- if ((di = tv_dict_find(dict, "buffer", -1)) != NULL) {
- buf = get_buf_arg(&di->di_tv);
- if (buf == NULL) {
- goto cleanup;
- }
- }
- if (tv_dict_find(dict, "id", -1) != NULL) {
- sign_id = tv_dict_get_number(dict, "id");
- }
- }
-
- if (buf == NULL) {
- // Delete the sign in all the buffers
- FOR_ALL_BUFFERS(cbuf) {
- if (sign_unplace(sign_id, group, cbuf, 0) == OK) {
- rettv->vval.v_number = 0;
- }
- }
- } else {
- if (sign_unplace(sign_id, group, buf, 0) == OK) {
- rettv->vval.v_number = 0;
- }
- }
-
-cleanup:
- xfree(group);
-}
-
-/*
- * "simplify()" function
- */
-static void f_simplify(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const char *const p = tv_get_string(&argvars[0]);
- rettv->vval.v_string = (char_u *)xstrdup(p);
- simplify_filename(rettv->vval.v_string); // Simplify in place.
- rettv->v_type = VAR_STRING;
-}
-
-/// "sockconnect()" function
-static void f_sockconnect(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- if (argvars[0].v_type != VAR_STRING || argvars[1].v_type != VAR_STRING) {
- EMSG(_(e_invarg));
- return;
- }
- if (argvars[2].v_type != VAR_DICT && argvars[2].v_type != VAR_UNKNOWN) {
- // Wrong argument types
- EMSG2(_(e_invarg2), "expected dictionary");
- return;
- }
-
- const char *mode = tv_get_string(&argvars[0]);
- const char *address = tv_get_string(&argvars[1]);
-
- bool tcp;
- if (strcmp(mode, "tcp") == 0) {
- tcp = true;
- } else if (strcmp(mode, "pipe") == 0) {
- tcp = false;
- } else {
- EMSG2(_(e_invarg2), "invalid mode");
- return;
- }
-
- bool rpc = false;
- CallbackReader on_data = CALLBACK_READER_INIT;
- if (argvars[2].v_type == VAR_DICT) {
- dict_T *opts = argvars[2].vval.v_dict;
- rpc = tv_dict_get_number(opts, "rpc") != 0;
-
- if (!tv_dict_get_callback(opts, S_LEN("on_data"), &on_data.cb)) {
- return;
- }
- on_data.buffered = tv_dict_get_number(opts, "data_buffered");
- if (on_data.buffered && on_data.cb.type == kCallbackNone) {
- on_data.self = opts;
- }
- }
-
- const char *error = NULL;
- uint64_t id = channel_connect(tcp, address, rpc, on_data, 50, &error);
-
- if (error) {
- EMSG2(_("connection failed: %s"), error);
- }
-
- rettv->vval.v_number = (varnumber_T)id;
- rettv->v_type = VAR_NUMBER;
-}
-
-/// struct storing information about current sort
-typedef struct {
- int item_compare_ic;
- bool item_compare_numeric;
- bool item_compare_numbers;
- bool item_compare_float;
- const char *item_compare_func;
- partial_T *item_compare_partial;
- dict_T *item_compare_selfdict;
- bool item_compare_func_err;
-} sortinfo_T;
-static sortinfo_T *sortinfo = NULL;
-
-#define ITEM_COMPARE_FAIL 999
-
-/*
- * Compare functions for f_sort() and f_uniq() below.
- */
-static int item_compare(const void *s1, const void *s2, bool keep_zero)
-{
- ListSortItem *const si1 = (ListSortItem *)s1;
- ListSortItem *const si2 = (ListSortItem *)s2;
-
- typval_T *const tv1 = TV_LIST_ITEM_TV(si1->item);
- typval_T *const tv2 = TV_LIST_ITEM_TV(si2->item);
-
- int res;
-
- if (sortinfo->item_compare_numbers) {
- const varnumber_T v1 = tv_get_number(tv1);
- const varnumber_T v2 = tv_get_number(tv2);
-
- res = v1 == v2 ? 0 : v1 > v2 ? 1 : -1;
- goto item_compare_end;
- }
-
- if (sortinfo->item_compare_float) {
- const float_T v1 = tv_get_float(tv1);
- const float_T v2 = tv_get_float(tv2);
-
- res = v1 == v2 ? 0 : v1 > v2 ? 1 : -1;
- goto item_compare_end;
- }
-
- char *tofree1 = NULL;
- char *tofree2 = NULL;
- char *p1;
- char *p2;
-
- // encode_tv2string() puts quotes around a string and allocates memory. Don't
- // do that for string variables. Use a single quote when comparing with
- // a non-string to do what the docs promise.
- if (tv1->v_type == VAR_STRING) {
- if (tv2->v_type != VAR_STRING || sortinfo->item_compare_numeric) {
- p1 = "'";
- } else {
- p1 = (char *)tv1->vval.v_string;
- }
- } else {
- tofree1 = p1 = encode_tv2string(tv1, NULL);
- }
- if (tv2->v_type == VAR_STRING) {
- if (tv1->v_type != VAR_STRING || sortinfo->item_compare_numeric) {
- p2 = "'";
- } else {
- p2 = (char *)tv2->vval.v_string;
- }
- } else {
- tofree2 = p2 = encode_tv2string(tv2, NULL);
- }
- if (p1 == NULL) {
- p1 = "";
- }
- if (p2 == NULL) {
- p2 = "";
- }
- if (!sortinfo->item_compare_numeric) {
- if (sortinfo->item_compare_ic) {
- res = STRICMP(p1, p2);
- } else {
- res = STRCMP(p1, p2);
- }
- } else {
- double n1, n2;
- n1 = strtod(p1, &p1);
- n2 = strtod(p2, &p2);
- res = n1 == n2 ? 0 : n1 > n2 ? 1 : -1;
- }
-
- xfree(tofree1);
- xfree(tofree2);
-
-item_compare_end:
- // When the result would be zero, compare the item indexes. Makes the
- // sort stable.
- if (res == 0 && !keep_zero) {
- // WARNING: When using uniq si1 and si2 are actually listitem_T **, no
- // indexes are there.
- res = si1->idx > si2->idx ? 1 : -1;
- }
- return res;
-}
-
-static int item_compare_keeping_zero(const void *s1, const void *s2)
-{
- return item_compare(s1, s2, true);
-}
-
-static int item_compare_not_keeping_zero(const void *s1, const void *s2)
-{
- return item_compare(s1, s2, false);
-}
-
-static int item_compare2(const void *s1, const void *s2, bool keep_zero)
-{
- ListSortItem *si1, *si2;
- int res;
- typval_T rettv;
- typval_T argv[3];
- int dummy;
- const char *func_name;
- partial_T *partial = sortinfo->item_compare_partial;
-
- // shortcut after failure in previous call; compare all items equal
- if (sortinfo->item_compare_func_err) {
- return 0;
- }
-
- si1 = (ListSortItem *)s1;
- si2 = (ListSortItem *)s2;
-
- if (partial == NULL) {
- func_name = sortinfo->item_compare_func;
- } else {
- func_name = (const char *)partial_name(partial);
- }
-
- // Copy the values. This is needed to be able to set v_lock to VAR_FIXED
- // in the copy without changing the original list items.
- tv_copy(TV_LIST_ITEM_TV(si1->item), &argv[0]);
- tv_copy(TV_LIST_ITEM_TV(si2->item), &argv[1]);
-
- rettv.v_type = VAR_UNKNOWN; // tv_clear() uses this
- res = call_func((const char_u *)func_name,
- (int)STRLEN(func_name),
- &rettv, 2, argv, NULL, 0L, 0L, &dummy, true,
- partial, sortinfo->item_compare_selfdict);
- tv_clear(&argv[0]);
- tv_clear(&argv[1]);
-
- if (res == FAIL) {
- res = ITEM_COMPARE_FAIL;
- } else {
- res = tv_get_number_chk(&rettv, &sortinfo->item_compare_func_err);
- }
- if (sortinfo->item_compare_func_err) {
- res = ITEM_COMPARE_FAIL; // return value has wrong type
- }
- tv_clear(&rettv);
-
- // When the result would be zero, compare the pointers themselves. Makes
- // the sort stable.
- if (res == 0 && !keep_zero) {
- // WARNING: When using uniq si1 and si2 are actually listitem_T **, no
- // indexes are there.
- res = si1->idx > si2->idx ? 1 : -1;
- }
-
- return res;
-}
-
-static int item_compare2_keeping_zero(const void *s1, const void *s2)
-{
- return item_compare2(s1, s2, true);
-}
-
-static int item_compare2_not_keeping_zero(const void *s1, const void *s2)
-{
- return item_compare2(s1, s2, false);
-}
-
-/*
- * "sort({list})" function
- */
-static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort)
-{
- ListSortItem *ptrs;
- long len;
- long i;
-
- // Pointer to current info struct used in compare function. Save and restore
- // the current one for nested calls.
- sortinfo_T info;
- sortinfo_T *old_sortinfo = sortinfo;
- sortinfo = &info;
-
- const char *const arg_errmsg = (sort
- ? N_("sort() argument")
- : N_("uniq() argument"));
-
- if (argvars[0].v_type != VAR_LIST) {
- EMSG2(_(e_listarg), sort ? "sort()" : "uniq()");
- } else {
- list_T *const l = argvars[0].vval.v_list;
- if (tv_check_lock(tv_list_locked(l), arg_errmsg, TV_TRANSLATE)) {
- goto theend;
- }
- tv_list_set_ret(rettv, l);
-
- len = tv_list_len(l);
- if (len <= 1) {
- goto theend; // short list sorts pretty quickly
- }
-
- info.item_compare_ic = false;
- info.item_compare_numeric = false;
- info.item_compare_numbers = false;
- info.item_compare_float = false;
- info.item_compare_func = NULL;
- info.item_compare_partial = NULL;
- info.item_compare_selfdict = NULL;
-
- if (argvars[1].v_type != VAR_UNKNOWN) {
- /* optional second argument: {func} */
- if (argvars[1].v_type == VAR_FUNC) {
- info.item_compare_func = (const char *)argvars[1].vval.v_string;
- } else if (argvars[1].v_type == VAR_PARTIAL) {
- info.item_compare_partial = argvars[1].vval.v_partial;
- } else {
- bool error = false;
-
- i = tv_get_number_chk(&argvars[1], &error);
- if (error) {
- goto theend; // type error; errmsg already given
- }
- if (i == 1) {
- info.item_compare_ic = true;
- } else if (argvars[1].v_type != VAR_NUMBER) {
- info.item_compare_func = tv_get_string(&argvars[1]);
- } else if (i != 0) {
- EMSG(_(e_invarg));
- goto theend;
- }
- if (info.item_compare_func != NULL) {
- if (*info.item_compare_func == NUL) {
- // empty string means default sort
- info.item_compare_func = NULL;
- } else if (strcmp(info.item_compare_func, "n") == 0) {
- info.item_compare_func = NULL;
- info.item_compare_numeric = true;
- } else if (strcmp(info.item_compare_func, "N") == 0) {
- info.item_compare_func = NULL;
- info.item_compare_numbers = true;
- } else if (strcmp(info.item_compare_func, "f") == 0) {
- info.item_compare_func = NULL;
- info.item_compare_float = true;
- } else if (strcmp(info.item_compare_func, "i") == 0) {
- info.item_compare_func = NULL;
- info.item_compare_ic = true;
- }
- }
- }
-
- if (argvars[2].v_type != VAR_UNKNOWN) {
- // optional third argument: {dict}
- if (argvars[2].v_type != VAR_DICT) {
- EMSG(_(e_dictreq));
- goto theend;
- }
- info.item_compare_selfdict = argvars[2].vval.v_dict;
- }
- }
-
- // Make an array with each entry pointing to an item in the List.
- ptrs = xmalloc((size_t)(len * sizeof(ListSortItem)));
-
- if (sort) {
- info.item_compare_func_err = false;
- tv_list_item_sort(l, ptrs,
- ((info.item_compare_func == NULL
- && info.item_compare_partial == NULL)
- ? item_compare_not_keeping_zero
- : item_compare2_not_keeping_zero),
- &info.item_compare_func_err);
- if (info.item_compare_func_err) {
- EMSG(_("E702: Sort compare function failed"));
- }
- } else {
- ListSorter item_compare_func_ptr;
-
- // f_uniq(): ptrs will be a stack of items to remove.
- info.item_compare_func_err = false;
- if (info.item_compare_func != NULL
- || info.item_compare_partial != NULL) {
- item_compare_func_ptr = item_compare2_keeping_zero;
- } else {
- item_compare_func_ptr = item_compare_keeping_zero;
- }
-
- int idx = 0;
- for (listitem_T *li = TV_LIST_ITEM_NEXT(l, tv_list_first(l))
- ; li != NULL;) {
- listitem_T *const prev_li = TV_LIST_ITEM_PREV(l, li);
- if (item_compare_func_ptr(&prev_li, &li) == 0) {
- if (info.item_compare_func_err) { // -V547
- EMSG(_("E882: Uniq compare function failed"));
- break;
- }
- li = tv_list_item_remove(l, li);
- } else {
- idx++;
- li = TV_LIST_ITEM_NEXT(l, li);
- }
- }
- }
-
- xfree(ptrs);
- }
-
-theend:
- sortinfo = old_sortinfo;
-}
-
-/// "sort"({list})" function
-static void f_sort(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- do_sort_uniq(argvars, rettv, true);
-}
-
-/// "stdioopen()" function
-static void f_stdioopen(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- if (argvars[0].v_type != VAR_DICT) {
- EMSG(_(e_invarg));
- return;
- }
-
-
- bool rpc = false;
- CallbackReader on_stdin = CALLBACK_READER_INIT;
- dict_T *opts = argvars[0].vval.v_dict;
- rpc = tv_dict_get_number(opts, "rpc") != 0;
-
- if (!tv_dict_get_callback(opts, S_LEN("on_stdin"), &on_stdin.cb)) {
- return;
- }
- on_stdin.buffered = tv_dict_get_number(opts, "stdin_buffered");
- if (on_stdin.buffered && on_stdin.cb.type == kCallbackNone) {
- on_stdin.self = opts;
- }
-
- const char *error;
- uint64_t id = channel_from_stdio(rpc, on_stdin, &error);
- if (!id) {
- EMSG2(e_stdiochan2, error);
- }
-
-
- rettv->vval.v_number = (varnumber_T)id;
- rettv->v_type = VAR_NUMBER;
-}
-
-/// "uniq({list})" function
-static void f_uniq(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- do_sort_uniq(argvars, rettv, false);
-}
-
-// "reltimefloat()" function
-static void f_reltimefloat(typval_T *argvars , typval_T *rettv, FunPtr fptr)
- FUNC_ATTR_NONNULL_ALL
-{
- proftime_T tm;
-
- rettv->v_type = VAR_FLOAT;
- rettv->vval.v_float = 0;
- if (list2proftime(&argvars[0], &tm) == OK) {
- rettv->vval.v_float = (float_T)profile_signed(tm) / 1000000000.0;
- }
-}
-
-/*
- * "soundfold({word})" function
- */
-static void f_soundfold(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->v_type = VAR_STRING;
- const char *const s = tv_get_string(&argvars[0]);
- rettv->vval.v_string = (char_u *)eval_soundfold(s);
-}
-
-/*
- * "spellbadword()" function
- */
-static void f_spellbadword(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const char *word = "";
- hlf_T attr = HLF_COUNT;
- size_t len = 0;
-
- if (argvars[0].v_type == VAR_UNKNOWN) {
- // Find the start and length of the badly spelled word.
- len = spell_move_to(curwin, FORWARD, true, true, &attr);
- if (len != 0) {
- word = (char *)get_cursor_pos_ptr();
- curwin->w_set_curswant = true;
- }
- } else if (curwin->w_p_spell && *curbuf->b_s.b_p_spl != NUL) {
- const char *str = tv_get_string_chk(&argvars[0]);
- int capcol = -1;
-
- if (str != NULL) {
- // Check the argument for spelling.
- while (*str != NUL) {
- len = spell_check(curwin, (char_u *)str, &attr, &capcol, false);
- if (attr != HLF_COUNT) {
- word = str;
- break;
- }
- str += len;
- capcol -= len;
- len = 0;
- }
- }
- }
-
- assert(len <= INT_MAX);
- tv_list_alloc_ret(rettv, 2);
- tv_list_append_string(rettv->vval.v_list, word, len);
- tv_list_append_string(rettv->vval.v_list,
- (attr == HLF_SPB ? "bad"
- : attr == HLF_SPR ? "rare"
- : attr == HLF_SPL ? "local"
- : attr == HLF_SPC ? "caps"
- : NULL), -1);
-}
-
-/*
- * "spellsuggest()" function
- */
-static void f_spellsuggest(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- bool typeerr = false;
- int maxcount;
- garray_T ga = GA_EMPTY_INIT_VALUE;
- bool need_capital = false;
-
- if (curwin->w_p_spell && *curwin->w_s->b_p_spl != NUL) {
- const char *const str = tv_get_string(&argvars[0]);
- if (argvars[1].v_type != VAR_UNKNOWN) {
- maxcount = tv_get_number_chk(&argvars[1], &typeerr);
- if (maxcount <= 0) {
- goto f_spellsuggest_return;
- }
- if (argvars[2].v_type != VAR_UNKNOWN) {
- need_capital = tv_get_number_chk(&argvars[2], &typeerr);
- if (typeerr) {
- goto f_spellsuggest_return;
- }
- }
- } else {
- maxcount = 25;
- }
-
- spell_suggest_list(&ga, (char_u *)str, maxcount, need_capital, false);
- }
-
-f_spellsuggest_return:
- tv_list_alloc_ret(rettv, (ptrdiff_t)ga.ga_len);
- for (int i = 0; i < ga.ga_len; i++) {
- char *const p = ((char **)ga.ga_data)[i];
- tv_list_append_allocated_string(rettv->vval.v_list, p);
- }
- ga_clear(&ga);
-}
-
-static void f_split(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- char_u *save_cpo;
- int match;
- colnr_T col = 0;
- bool keepempty = false;
- bool typeerr = false;
-
- /* Make 'cpoptions' empty, the 'l' flag should not be used here. */
- save_cpo = p_cpo;
- p_cpo = (char_u *)"";
-
- const char *str = tv_get_string(&argvars[0]);
- const char *pat = NULL;
- char patbuf[NUMBUFLEN];
- if (argvars[1].v_type != VAR_UNKNOWN) {
- pat = tv_get_string_buf_chk(&argvars[1], patbuf);
- if (pat == NULL) {
- typeerr = true;
- }
- if (argvars[2].v_type != VAR_UNKNOWN) {
- keepempty = (bool)tv_get_number_chk(&argvars[2], &typeerr);
- }
- }
- if (pat == NULL || *pat == NUL) {
- pat = "[\\x01- ]\\+";
- }
-
- tv_list_alloc_ret(rettv, kListLenMayKnow);
-
- if (typeerr) {
- return;
- }
-
- regmatch_T regmatch = {
- .regprog = vim_regcomp((char_u *)pat, RE_MAGIC + RE_STRING),
- .startp = { NULL },
- .endp = { NULL },
- .rm_ic = false,
- };
- if (regmatch.regprog != NULL) {
- while (*str != NUL || keepempty) {
- if (*str == NUL) {
- match = false; // Empty item at the end.
- } else {
- match = vim_regexec_nl(&regmatch, (char_u *)str, col);
- }
- const char *end;
- if (match) {
- end = (const char *)regmatch.startp[0];
- } else {
- end = str + strlen(str);
- }
- if (keepempty || end > str || (tv_list_len(rettv->vval.v_list) > 0
- && *str != NUL
- && match
- && end < (const char *)regmatch.endp[0])) {
- tv_list_append_string(rettv->vval.v_list, str, end - str);
- }
- if (!match) {
- break;
- }
- // Advance to just after the match.
- if (regmatch.endp[0] > (char_u *)str) {
- col = 0;
- } else {
- // Don't get stuck at the same match.
- col = (*mb_ptr2len)(regmatch.endp[0]);
- }
- str = (const char *)regmatch.endp[0];
- }
-
- vim_regfree(regmatch.regprog);
- }
-
- p_cpo = save_cpo;
-}
-
/// "stdpath()" helper for list results
-static void get_xdg_var_list(const XDGVarType xdg, typval_T *rettv)
+void get_xdg_var_list(const XDGVarType xdg, typval_T *rettv)
FUNC_ATTR_NONNULL_ALL
{
const void *iter = NULL;
@@ -17005,693 +7084,6 @@ static void get_xdg_var_list(const XDGVarType xdg, typval_T *rettv)
xfree(dirs);
}
-/// "stdpath(type)" function
-static void f_stdpath(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = NULL;
-
- const char *const p = tv_get_string_chk(&argvars[0]);
- if (p == NULL) {
- return; // Type error; errmsg already given.
- }
-
- if (strequal(p, "config")) {
- rettv->vval.v_string = (char_u *)get_xdg_home(kXDGConfigHome);
- } else if (strequal(p, "data")) {
- rettv->vval.v_string = (char_u *)get_xdg_home(kXDGDataHome);
- } else if (strequal(p, "cache")) {
- rettv->vval.v_string = (char_u *)get_xdg_home(kXDGCacheHome);
- } else if (strequal(p, "config_dirs")) {
- get_xdg_var_list(kXDGConfigDirs, rettv);
- } else if (strequal(p, "data_dirs")) {
- get_xdg_var_list(kXDGDataDirs, rettv);
- } else {
- EMSG2(_("E6100: \"%s\" is not a valid stdpath"), p);
- }
-}
-
-/*
- * "str2float()" function
- */
-static void f_str2float(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- char_u *p = skipwhite((const char_u *)tv_get_string(&argvars[0]));
- bool isneg = (*p == '-');
-
- if (*p == '+' || *p == '-') {
- p = skipwhite(p + 1);
- }
- (void)string2float((char *)p, &rettv->vval.v_float);
- if (isneg) {
- rettv->vval.v_float *= -1;
- }
- rettv->v_type = VAR_FLOAT;
-}
-
-// "str2nr()" function
-static void f_str2nr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- int base = 10;
- varnumber_T n;
- int what;
-
- if (argvars[1].v_type != VAR_UNKNOWN) {
- base = tv_get_number(&argvars[1]);
- if (base != 2 && base != 8 && base != 10 && base != 16) {
- EMSG(_(e_invarg));
- return;
- }
- }
-
- char_u *p = skipwhite((const char_u *)tv_get_string(&argvars[0]));
- bool isneg = (*p == '-');
- if (*p == '+' || *p == '-') {
- p = skipwhite(p + 1);
- }
- switch (base) {
- case 2: {
- what = STR2NR_BIN | STR2NR_FORCE;
- break;
- }
- case 8: {
- what = STR2NR_OCT | STR2NR_FORCE;
- break;
- }
- case 16: {
- what = STR2NR_HEX | STR2NR_FORCE;
- break;
- }
- default: {
- what = 0;
- }
- }
- vim_str2nr(p, NULL, NULL, what, &n, NULL, 0);
- if (isneg) {
- rettv->vval.v_number = -n;
- } else {
- rettv->vval.v_number = n;
- }
-}
-
-/*
- * "strftime({format}[, {time}])" function
- */
-static void f_strftime(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- time_t seconds;
-
- rettv->v_type = VAR_STRING;
-
- char *p = (char *)tv_get_string(&argvars[0]);
- if (argvars[1].v_type == VAR_UNKNOWN) {
- seconds = time(NULL);
- } else {
- seconds = (time_t)tv_get_number(&argvars[1]);
- }
-
- struct tm curtime;
- struct tm *curtime_ptr = os_localtime_r(&seconds, &curtime);
- /* MSVC returns NULL for an invalid value of seconds. */
- if (curtime_ptr == NULL)
- rettv->vval.v_string = vim_strsave((char_u *)_("(Invalid)"));
- else {
- vimconv_T conv;
- char_u *enc;
-
- conv.vc_type = CONV_NONE;
- enc = enc_locale();
- convert_setup(&conv, p_enc, enc);
- if (conv.vc_type != CONV_NONE) {
- p = (char *)string_convert(&conv, (char_u *)p, NULL);
- }
- char result_buf[256];
- if (p != NULL) {
- (void)strftime(result_buf, sizeof(result_buf), p, curtime_ptr);
- } else {
- result_buf[0] = NUL;
- }
-
- if (conv.vc_type != CONV_NONE) {
- xfree(p);
- }
- convert_setup(&conv, enc, p_enc);
- if (conv.vc_type != CONV_NONE) {
- rettv->vval.v_string = string_convert(&conv, (char_u *)result_buf, NULL);
- } else {
- rettv->vval.v_string = (char_u *)xstrdup(result_buf);
- }
-
- // Release conversion descriptors.
- convert_setup(&conv, NULL, NULL);
- xfree(enc);
- }
-}
-
-// "strgetchar()" function
-static void f_strgetchar(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = -1;
-
- const char *const str = tv_get_string_chk(&argvars[0]);
- if (str == NULL) {
- return;
- }
- bool error = false;
- varnumber_T charidx = tv_get_number_chk(&argvars[1], &error);
- if (error) {
- return;
- }
-
- const size_t len = STRLEN(str);
- size_t byteidx = 0;
-
- while (charidx >= 0 && byteidx < len) {
- if (charidx == 0) {
- rettv->vval.v_number = utf_ptr2char((const char_u *)str + byteidx);
- break;
- }
- charidx--;
- byteidx += MB_CPTR2LEN((const char_u *)str + byteidx);
- }
-}
-
-/*
- * "stridx()" function
- */
-static void f_stridx(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = -1;
-
- char buf[NUMBUFLEN];
- const char *const needle = tv_get_string_chk(&argvars[1]);
- const char *haystack = tv_get_string_buf_chk(&argvars[0], buf);
- const char *const haystack_start = haystack;
- if (needle == NULL || haystack == NULL) {
- return; // Type error; errmsg already given.
- }
-
- if (argvars[2].v_type != VAR_UNKNOWN) {
- bool error = false;
-
- const ptrdiff_t start_idx = (ptrdiff_t)tv_get_number_chk(&argvars[2],
- &error);
- if (error || start_idx >= (ptrdiff_t)strlen(haystack)) {
- return;
- }
- if (start_idx >= 0) {
- haystack += start_idx;
- }
- }
-
- const char *pos = strstr(haystack, needle);
- if (pos != NULL) {
- rettv->vval.v_number = (varnumber_T)(pos - haystack_start);
- }
-}
-
-/*
- * "string()" function
- */
-static void f_string(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = (char_u *) encode_tv2string(&argvars[0], NULL);
-}
-
-/*
- * "strlen()" function
- */
-static void f_strlen(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = (varnumber_T)strlen(tv_get_string(&argvars[0]));
-}
-
-/*
- * "strchars()" function
- */
-static void f_strchars(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const char *s = tv_get_string(&argvars[0]);
- int skipcc = 0;
- varnumber_T len = 0;
- int (*func_mb_ptr2char_adv)(const char_u **pp);
-
- if (argvars[1].v_type != VAR_UNKNOWN) {
- skipcc = tv_get_number_chk(&argvars[1], NULL);
- }
- if (skipcc < 0 || skipcc > 1) {
- EMSG(_(e_invarg));
- } else {
- func_mb_ptr2char_adv = skipcc ? mb_ptr2char_adv : mb_cptr2char_adv;
- while (*s != NUL) {
- func_mb_ptr2char_adv((const char_u **)&s);
- len++;
- }
- rettv->vval.v_number = len;
- }
-}
-
-/*
- * "strdisplaywidth()" function
- */
-static void f_strdisplaywidth(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const char *const s = tv_get_string(&argvars[0]);
- int col = 0;
-
- if (argvars[1].v_type != VAR_UNKNOWN) {
- col = tv_get_number(&argvars[1]);
- }
-
- rettv->vval.v_number = (varnumber_T)(linetabsize_col(col, (char_u *)s) - col);
-}
-
-/*
- * "strwidth()" function
- */
-static void f_strwidth(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const char *const s = tv_get_string(&argvars[0]);
-
- rettv->vval.v_number = (varnumber_T)mb_string2cells((const char_u *)s);
-}
-
-// "strcharpart()" function
-static void f_strcharpart(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const char *const p = tv_get_string(&argvars[0]);
- const size_t slen = STRLEN(p);
-
- int nbyte = 0;
- bool error = false;
- varnumber_T nchar = tv_get_number_chk(&argvars[1], &error);
- if (!error) {
- if (nchar > 0) {
- while (nchar > 0 && (size_t)nbyte < slen) {
- nbyte += MB_CPTR2LEN((const char_u *)p + nbyte);
- nchar--;
- }
- } else {
- nbyte = nchar;
- }
- }
- int len = 0;
- if (argvars[2].v_type != VAR_UNKNOWN) {
- int charlen = tv_get_number(&argvars[2]);
- while (charlen > 0 && nbyte + len < (int)slen) {
- int off = nbyte + len;
-
- if (off < 0) {
- len += 1;
- } else {
- len += (size_t)MB_CPTR2LEN((const char_u *)p + off);
- }
- charlen--;
- }
- } else {
- len = slen - nbyte; // default: all bytes that are available.
- }
-
- // Only return the overlap between the specified part and the actual
- // string.
- if (nbyte < 0) {
- len += nbyte;
- nbyte = 0;
- } else if ((size_t)nbyte > slen) {
- nbyte = slen;
- }
- if (len < 0) {
- len = 0;
- } else if (nbyte + len > (int)slen) {
- len = slen - nbyte;
- }
-
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = (char_u *)xstrndup(p + nbyte, (size_t)len);
-}
-
-/*
- * "strpart()" function
- */
-static void f_strpart(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- bool error = false;
-
- const char *const p = tv_get_string(&argvars[0]);
- const size_t slen = strlen(p);
-
- varnumber_T n = tv_get_number_chk(&argvars[1], &error);
- varnumber_T len;
- if (error) {
- len = 0;
- } else if (argvars[2].v_type != VAR_UNKNOWN) {
- len = tv_get_number(&argvars[2]);
- } else {
- len = slen - n; // Default len: all bytes that are available.
- }
-
- // Only return the overlap between the specified part and the actual
- // string.
- if (n < 0) {
- len += n;
- n = 0;
- } else if (n > (varnumber_T)slen) {
- n = slen;
- }
- if (len < 0) {
- len = 0;
- } else if (n + len > (varnumber_T)slen) {
- len = slen - n;
- }
-
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = (char_u *)xmemdupz(p + n, (size_t)len);
-}
-
-/*
- * "strridx()" function
- */
-static void f_strridx(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- char buf[NUMBUFLEN];
- const char *const needle = tv_get_string_chk(&argvars[1]);
- const char *const haystack = tv_get_string_buf_chk(&argvars[0], buf);
-
- rettv->vval.v_number = -1;
- if (needle == NULL || haystack == NULL) {
- return; // Type error; errmsg already given.
- }
-
- const size_t haystack_len = STRLEN(haystack);
- ptrdiff_t end_idx;
- if (argvars[2].v_type != VAR_UNKNOWN) {
- // Third argument: upper limit for index.
- end_idx = (ptrdiff_t)tv_get_number_chk(&argvars[2], NULL);
- if (end_idx < 0) {
- return; // Can never find a match.
- }
- } else {
- end_idx = (ptrdiff_t)haystack_len;
- }
-
- const char *lastmatch = NULL;
- if (*needle == NUL) {
- // Empty string matches past the end.
- lastmatch = haystack + end_idx;
- } else {
- for (const char *rest = haystack; *rest != NUL; rest++) {
- rest = strstr(rest, needle);
- if (rest == NULL || rest > haystack + end_idx) {
- break;
- }
- lastmatch = rest;
- }
- }
-
- if (lastmatch == NULL) {
- rettv->vval.v_number = -1;
- } else {
- rettv->vval.v_number = (varnumber_T)(lastmatch - haystack);
- }
-}
-
-/*
- * "strtrans()" function
- */
-static void f_strtrans(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = (char_u *)transstr(tv_get_string(&argvars[0]));
-}
-
-/*
- * "submatch()" function
- */
-static void f_submatch(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- bool error = false;
- int no = (int)tv_get_number_chk(&argvars[0], &error);
- if (error) {
- return;
- }
-
- if (no < 0 || no >= NSUBEXP) {
- emsgf(_("E935: invalid submatch number: %d"), no);
- return;
- }
- int retList = 0;
-
- if (argvars[1].v_type != VAR_UNKNOWN) {
- retList = tv_get_number_chk(&argvars[1], &error);
- if (error) {
- return;
- }
- }
-
- if (retList == 0) {
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = reg_submatch(no);
- } else {
- rettv->v_type = VAR_LIST;
- rettv->vval.v_list = reg_submatch_list(no);
- }
-}
-
-/*
- * "substitute()" function
- */
-static void f_substitute(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- char patbuf[NUMBUFLEN];
- char subbuf[NUMBUFLEN];
- char flagsbuf[NUMBUFLEN];
-
- const char *const str = tv_get_string_chk(&argvars[0]);
- const char *const pat = tv_get_string_buf_chk(&argvars[1], patbuf);
- const char *sub = NULL;
- const char *const flg = tv_get_string_buf_chk(&argvars[3], flagsbuf);
-
- typval_T *expr = NULL;
- if (tv_is_func(argvars[2])) {
- expr = &argvars[2];
- } else {
- sub = tv_get_string_buf_chk(&argvars[2], subbuf);
- }
-
- rettv->v_type = VAR_STRING;
- if (str == NULL || pat == NULL || (sub == NULL && expr == NULL)
- || flg == NULL) {
- rettv->vval.v_string = NULL;
- } else {
- rettv->vval.v_string = do_string_sub((char_u *)str, (char_u *)pat,
- (char_u *)sub, expr, (char_u *)flg);
- }
-}
-
-/// "swapinfo(swap_filename)" function
-static void f_swapinfo(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- tv_dict_alloc_ret(rettv);
- get_b0_dict(tv_get_string(argvars), rettv->vval.v_dict);
-}
-
-/// "swapname(expr)" function
-static void f_swapname(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->v_type = VAR_STRING;
- buf_T *buf = tv_get_buf(&argvars[0], false);
- if (buf == NULL
- || buf->b_ml.ml_mfp == NULL
- || buf->b_ml.ml_mfp->mf_fname == NULL) {
- rettv->vval.v_string = NULL;
- } else {
- rettv->vval.v_string = vim_strsave(buf->b_ml.ml_mfp->mf_fname);
- }
-}
-
-/// "synID(lnum, col, trans)" function
-static void f_synID(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- // -1 on type error (both)
- const linenr_T lnum = tv_get_lnum(argvars);
- const colnr_T col = (colnr_T)tv_get_number(&argvars[1]) - 1;
-
- bool transerr = false;
- const int trans = tv_get_number_chk(&argvars[2], &transerr);
-
- int id = 0;
- if (!transerr && lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count
- && col >= 0 && (size_t)col < STRLEN(ml_get(lnum))) {
- id = syn_get_id(curwin, lnum, col, trans, NULL, false);
- }
-
- rettv->vval.v_number = id;
-}
-
-/*
- * "synIDattr(id, what [, mode])" function
- */
-static void f_synIDattr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const int id = (int)tv_get_number(&argvars[0]);
- const char *const what = tv_get_string(&argvars[1]);
- int modec;
- if (argvars[2].v_type != VAR_UNKNOWN) {
- char modebuf[NUMBUFLEN];
- const char *const mode = tv_get_string_buf(&argvars[2], modebuf);
- modec = TOLOWER_ASC(mode[0]);
- if (modec != 'c' && modec != 'g') {
- modec = 0; // Replace invalid with current.
- }
- } else if (ui_rgb_attached()) {
- modec = 'g';
- } else {
- modec = 'c';
- }
-
-
- const char *p = NULL;
- switch (TOLOWER_ASC(what[0])) {
- case 'b': {
- if (TOLOWER_ASC(what[1]) == 'g') { // bg[#]
- p = highlight_color(id, what, modec);
- } else { // bold
- p = highlight_has_attr(id, HL_BOLD, modec);
- }
- break;
- }
- case 'f': { // fg[#] or font
- p = highlight_color(id, what, modec);
- break;
- }
- case 'i': {
- if (TOLOWER_ASC(what[1]) == 'n') { // inverse
- p = highlight_has_attr(id, HL_INVERSE, modec);
- } else { // italic
- p = highlight_has_attr(id, HL_ITALIC, modec);
- }
- break;
- }
- case 'n': { // name
- p = get_highlight_name_ext(NULL, id - 1, false);
- break;
- }
- case 'r': { // reverse
- p = highlight_has_attr(id, HL_INVERSE, modec);
- break;
- }
- case 's': {
- if (TOLOWER_ASC(what[1]) == 'p') { // sp[#]
- p = highlight_color(id, what, modec);
- } else if (TOLOWER_ASC(what[1]) == 't'
- && TOLOWER_ASC(what[2]) == 'r') { // strikethrough
- p = highlight_has_attr(id, HL_STRIKETHROUGH, modec);
- } else { // standout
- p = highlight_has_attr(id, HL_STANDOUT, modec);
- }
- break;
- }
- case 'u': {
- if (STRLEN(what) <= 5 || TOLOWER_ASC(what[5]) != 'c') { // underline
- p = highlight_has_attr(id, HL_UNDERLINE, modec);
- } else { // undercurl
- p = highlight_has_attr(id, HL_UNDERCURL, modec);
- }
- break;
- }
- }
-
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = (char_u *)(p == NULL ? p : xstrdup(p));
-}
-
-/*
- * "synIDtrans(id)" function
- */
-static void f_synIDtrans(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- int id = tv_get_number(&argvars[0]);
-
- if (id > 0) {
- id = syn_get_final_id(id);
- } else {
- id = 0;
- }
-
- rettv->vval.v_number = id;
-}
-
-/*
- * "synconcealed(lnum, col)" function
- */
-static void f_synconcealed(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- int syntax_flags = 0;
- int cchar;
- int matchid = 0;
- char_u str[NUMBUFLEN];
-
- tv_list_set_ret(rettv, NULL);
-
- // -1 on type error (both)
- const linenr_T lnum = tv_get_lnum(argvars);
- const colnr_T col = (colnr_T)tv_get_number(&argvars[1]) - 1;
-
- memset(str, NUL, sizeof(str));
-
- if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count && col >= 0
- && (size_t)col <= STRLEN(ml_get(lnum)) && curwin->w_p_cole > 0) {
- (void)syn_get_id(curwin, lnum, col, false, NULL, false);
- syntax_flags = get_syntax_info(&matchid);
-
- // get the conceal character
- if ((syntax_flags & HL_CONCEAL) && curwin->w_p_cole < 3) {
- cchar = syn_get_sub_char();
- if (cchar == NUL && curwin->w_p_cole == 1) {
- cchar = (curwin->w_p_lcs_chars.conceal == NUL)
- ? ' '
- : curwin->w_p_lcs_chars.conceal;
- }
- if (cchar != NUL) {
- utf_char2bytes(cchar, str);
- }
- }
- }
-
- tv_list_alloc_ret(rettv, 3);
- tv_list_append_number(rettv->vval.v_list, (syntax_flags & HL_CONCEAL) != 0);
- // -1 to auto-determine strlen
- tv_list_append_string(rettv->vval.v_list, (const char *)str, -1);
- tv_list_append_number(rettv->vval.v_list, matchid);
-}
-
-/*
- * "synstack(lnum, col)" function
- */
-static void f_synstack(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- tv_list_set_ret(rettv, NULL);
-
- // -1 on type error (both)
- const linenr_T lnum = tv_get_lnum(argvars);
- const colnr_T col = (colnr_T)tv_get_number(&argvars[1]) - 1;
-
- if (lnum >= 1
- && lnum <= curbuf->b_ml.ml_line_count
- && col >= 0
- && (size_t)col <= STRLEN(ml_get(lnum))) {
- tv_list_alloc_ret(rettv, kListLenMayKnow);
- (void)syn_get_id(curwin, lnum, col, false, NULL, true);
-
- int id;
- int i = 0;
- while ((id = syn_get_stack_item(i++)) >= 0) {
- tv_list_append_number(rettv->vval.v_list, id);
- }
- }
-}
-
static list_T *string_to_list(const char *str, size_t len, const bool keepempty)
{
if (!keepempty && str[len - 1] == NL) {
@@ -17703,8 +7095,8 @@ static list_T *string_to_list(const char *str, size_t len, const bool keepempty)
}
// os_system wrapper. Handles 'verbose', :profile, and v:shell_error.
-static void get_system_output_as_rettv(typval_T *argvars, typval_T *rettv,
- bool retlist)
+void get_system_output_as_rettv(typval_T *argvars, typval_T *rettv,
+ bool retlist)
{
proftime_T wait_time;
bool profiling = do_profiling == PROF_YES;
@@ -17802,319 +7194,41 @@ static void get_system_output_as_rettv(typval_T *argvars, typval_T *rettv,
}
}
-/// f_system - the VimL system() function
-static void f_system(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- get_system_output_as_rettv(argvars, rettv, false);
-}
-
-static void f_systemlist(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- get_system_output_as_rettv(argvars, rettv, true);
-}
-
-
-/*
- * "tabpagebuflist()" function
- */
-static void f_tabpagebuflist(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- win_T *wp = NULL;
-
- if (argvars[0].v_type == VAR_UNKNOWN) {
- wp = firstwin;
- } else {
- tabpage_T *const tp = find_tabpage((int)tv_get_number(&argvars[0]));
- if (tp != NULL) {
- wp = (tp == curtab) ? firstwin : tp->tp_firstwin;
- }
- }
- if (wp != NULL) {
- tv_list_alloc_ret(rettv, kListLenMayKnow);
- while (wp != NULL) {
- tv_list_append_number(rettv->vval.v_list, wp->w_buffer->b_fnum);
- wp = wp->w_next;
- }
- }
-}
-
-/*
- * "tabpagenr()" function
- */
-static void f_tabpagenr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- int nr = 1;
-
- if (argvars[0].v_type != VAR_UNKNOWN) {
- const char *const arg = tv_get_string_chk(&argvars[0]);
- nr = 0;
- if (arg != NULL) {
- if (strcmp(arg, "$") == 0) {
- nr = tabpage_index(NULL) - 1;
- } else {
- EMSG2(_(e_invexpr2), arg);
- }
- }
- } else {
- nr = tabpage_index(curtab);
- }
- rettv->vval.v_number = nr;
-}
-
-
-
-/*
- * Common code for tabpagewinnr() and winnr().
- */
-static int get_winnr(tabpage_T *tp, typval_T *argvar)
-{
- win_T *twin;
- int nr = 1;
- win_T *wp;
-
- twin = (tp == curtab) ? curwin : tp->tp_curwin;
- if (argvar->v_type != VAR_UNKNOWN) {
- bool invalid_arg = false;
- const char *const arg = tv_get_string_chk(argvar);
- if (arg == NULL) {
- nr = 0; // Type error; errmsg already given.
- } else if (strcmp(arg, "$") == 0) {
- twin = (tp == curtab) ? lastwin : tp->tp_lastwin;
- } else if (strcmp(arg, "#") == 0) {
- twin = (tp == curtab) ? prevwin : tp->tp_prevwin;
- if (twin == NULL) {
- nr = 0;
- }
- } else {
- // Extract the window count (if specified). e.g. winnr('3j')
- char_u *endp;
- long count = strtol((char *)arg, (char **)&endp, 10);
- if (count <= 0) {
- // if count is not specified, default to 1
- count = 1;
- }
- if (endp != NULL && *endp != '\0') {
- if (strequal((char *)endp, "j")) {
- twin = win_vert_neighbor(tp, twin, false, count);
- } else if (strequal((char *)endp, "k")) {
- twin = win_vert_neighbor(tp, twin, true, count);
- } else if (strequal((char *)endp, "h")) {
- twin = win_horz_neighbor(tp, twin, true, count);
- } else if (strequal((char *)endp, "l")) {
- twin = win_horz_neighbor(tp, twin, false, count);
- } else {
- invalid_arg = true;
- }
- } else {
- invalid_arg = true;
- }
- }
-
- if (invalid_arg) {
- EMSG2(_(e_invexpr2), arg);
- nr = 0;
- }
- }
-
- if (nr > 0)
- for (wp = (tp == curtab) ? firstwin : tp->tp_firstwin;
- wp != twin; wp = wp->w_next) {
- if (wp == NULL) {
- /* didn't find it in this tabpage */
- nr = 0;
- break;
- }
- ++nr;
- }
- return nr;
-}
-
-/*
- * "tabpagewinnr()" function
- */
-static void f_tabpagewinnr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- int nr = 1;
- tabpage_T *const tp = find_tabpage((int)tv_get_number(&argvars[0]));
- if (tp == NULL) {
- nr = 0;
- } else {
- nr = get_winnr(tp, &argvars[1]);
- }
- rettv->vval.v_number = nr;
-}
-
-/*
- * "tagfiles()" function
- */
-static void f_tagfiles(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- char *fname;
- tagname_T tn;
-
- tv_list_alloc_ret(rettv, kListLenUnknown);
- fname = xmalloc(MAXPATHL);
-
- bool first = true;
- while (get_tagfname(&tn, first, (char_u *)fname) == OK) {
- tv_list_append_string(rettv->vval.v_list, fname, -1);
- first = false;
- }
-
- tagname_free(&tn);
- xfree(fname);
-}
-
-/*
- * "taglist()" function
- */
-static void f_taglist(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const char *const tag_pattern = tv_get_string(&argvars[0]);
-
- rettv->vval.v_number = false;
- if (*tag_pattern == NUL) {
- return;
- }
-
- const char *fname = NULL;
- if (argvars[1].v_type != VAR_UNKNOWN) {
- fname = tv_get_string(&argvars[1]);
- }
- (void)get_tags(tv_list_alloc_ret(rettv, kListLenUnknown),
- (char_u *)tag_pattern, (char_u *)fname);
-}
-
-/*
- * "tempname()" function
- */
-static void f_tempname(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = vim_tempname();
-}
-
-// "termopen(cmd[, cwd])" function
-static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- if (check_restricted() || check_secure()) {
- return;
- }
-
- if (curbuf->b_changed) {
- EMSG(_("Can only call this function in an unmodified buffer"));
- return;
- }
-
- const char *cmd;
- bool executable = true;
- char **argv = tv_to_argv(&argvars[0], &cmd, &executable);
- if (!argv) {
- rettv->vval.v_number = executable ? 0 : -1;
- return; // Did error message in tv_to_argv.
- }
-
- if (argvars[1].v_type != VAR_DICT && argvars[1].v_type != VAR_UNKNOWN) {
- // Wrong argument type
- EMSG2(_(e_invarg2), "expected dictionary");
- shell_free_argv(argv);
- return;
- }
-
- CallbackReader on_stdout = CALLBACK_READER_INIT,
- on_stderr = CALLBACK_READER_INIT;
- Callback on_exit = CALLBACK_NONE;
- dict_T *job_opts = NULL;
- const char *cwd = ".";
- if (argvars[1].v_type == VAR_DICT) {
- job_opts = argvars[1].vval.v_dict;
-
- const char *const new_cwd = tv_dict_get_string(job_opts, "cwd", false);
- if (new_cwd && *new_cwd != NUL) {
- cwd = new_cwd;
- // The new cwd must be a directory.
- if (!os_isdir_executable((const char *)cwd)) {
- EMSG2(_(e_invarg2), "expected valid directory");
- shell_free_argv(argv);
- return;
- }
- }
-
- if (!common_job_callbacks(job_opts, &on_stdout, &on_stderr, &on_exit)) {
- shell_free_argv(argv);
- return;
- }
- }
-
- uint16_t term_width = MAX(0, curwin->w_width_inner - win_col_off(curwin));
- Channel *chan = channel_job_start(argv, on_stdout, on_stderr, on_exit,
- true, false, false, cwd,
- term_width, curwin->w_height_inner,
- xstrdup("xterm-256color"),
- &rettv->vval.v_number);
- if (rettv->vval.v_number <= 0) {
- return;
- }
-
- int pid = chan->stream.pty.process.pid;
-
- char buf[1024];
- // format the title with the pid to conform with the term:// URI
- snprintf(buf, sizeof(buf), "term://%s//%d:%s", cwd, pid, cmd);
- // at this point the buffer has no terminal instance associated yet, so unset
- // the 'swapfile' option to ensure no swap file will be created
- curbuf->b_p_swf = false;
- (void)setfname(curbuf, (char_u *)buf, NULL, true);
- // Save the job id and pid in b:terminal_job_{id,pid}
- Error err = ERROR_INIT;
- // deprecated: use 'channel' buffer option
- dict_set_var(curbuf->b_vars, cstr_as_string("terminal_job_id"),
- INTEGER_OBJ(chan->id), false, false, &err);
- api_clear_error(&err);
- dict_set_var(curbuf->b_vars, cstr_as_string("terminal_job_pid"),
- INTEGER_OBJ(pid), false, false, &err);
- api_clear_error(&err);
-
- channel_terminal_open(chan);
- channel_create_event(chan, NULL);
-}
-
-// "test_garbagecollect_now()" function
-static void f_test_garbagecollect_now(typval_T *argvars,
- typval_T *rettv, FunPtr fptr)
-{
- // This is dangerous, any Lists and Dicts used internally may be freed
- // while still in use.
- garbage_collect(true);
-}
-
-// "test_write_list_log()" function
-static void f_test_write_list_log(typval_T *const argvars,
- typval_T *const rettv,
- FunPtr fptr)
-{
- const char *const fname = tv_get_string_chk(&argvars[0]);
- if (fname == NULL) {
- return;
- }
- list_write_log(fname);
-}
-
bool callback_from_typval(Callback *const callback, typval_T *const arg)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
{
+ int r = OK;
+
if (arg->v_type == VAR_PARTIAL && arg->vval.v_partial != NULL) {
callback->data.partial = arg->vval.v_partial;
callback->data.partial->pt_refcount++;
callback->type = kCallbackPartial;
+ } else if (arg->v_type == VAR_STRING
+ && arg->vval.v_string != NULL
+ && ascii_isdigit(*arg->vval.v_string)) {
+ r = FAIL;
} else if (arg->v_type == VAR_FUNC || arg->v_type == VAR_STRING) {
char_u *name = arg->vval.v_string;
func_ref(name);
callback->data.funcref = vim_strsave(name);
callback->type = kCallbackFuncref;
+ } else if (nlua_is_table_from_lua(arg)) {
+ char_u *name = nlua_register_table_as_callable(arg);
+
+ if (name != NULL) {
+ func_ref(name);
+ callback->data.funcref = vim_strsave(name);
+ callback->type = kCallbackFuncref;
+ } else {
+ r = FAIL;
+ }
} else if (arg->v_type == VAR_NUMBER && arg->vval.v_number == 0) {
callback->type = kCallbackNone;
} else {
+ r = FAIL;
+ }
+
+ if (r == FAIL) {
EMSG(_("E921: Invalid callback argument"));
return false;
}
@@ -18192,7 +7306,12 @@ static bool set_ref_in_callback_reader(CallbackReader *reader, int copyID,
return false;
}
-static void add_timer_info(typval_T *rettv, timer_T *timer)
+timer_T *find_timer_by_nr(varnumber_T xx)
+{
+ return pmap_get(uint64_t)(timers, xx);
+}
+
+void add_timer_info(typval_T *rettv, timer_T *timer)
{
list_T *list = rettv->vval.v_list;
dict_T *dict = tv_dict_alloc();
@@ -18221,8 +7340,9 @@ static void add_timer_info(typval_T *rettv, timer_T *timer)
}
}
-static void add_timer_info_all(typval_T *rettv)
+void add_timer_info_all(typval_T *rettv)
{
+ tv_list_alloc_ret(rettv, timers->table->n_occupied);
timer_T *timer;
map_foreach_value(timers, timer, {
if (!timer->stopped) {
@@ -18231,125 +7351,13 @@ static void add_timer_info_all(typval_T *rettv)
})
}
-/// "timer_info([timer])" function
-static void f_timer_info(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- tv_list_alloc_ret(rettv, (argvars[0].v_type != VAR_UNKNOWN
- ? 1
- : timers->table->n_occupied));
- if (argvars[0].v_type != VAR_UNKNOWN) {
- if (argvars[0].v_type != VAR_NUMBER) {
- EMSG(_(e_number_exp));
- return;
- }
- timer_T *timer = pmap_get(uint64_t)(timers, tv_get_number(&argvars[0]));
- if (timer != NULL && !timer->stopped) {
- add_timer_info(rettv, timer);
- }
- } else {
- add_timer_info_all(rettv);
- }
-}
-
-/// "timer_pause(timer, paused)" function
-static void f_timer_pause(typval_T *argvars, typval_T *unused, FunPtr fptr)
-{
- if (argvars[0].v_type != VAR_NUMBER) {
- EMSG(_(e_number_exp));
- return;
- }
- int paused = (bool)tv_get_number(&argvars[1]);
- timer_T *timer = pmap_get(uint64_t)(timers, tv_get_number(&argvars[0]));
- if (timer != NULL) {
- if (!timer->paused && paused) {
- time_watcher_stop(&timer->tw);
- } else if (timer->paused && !paused) {
- time_watcher_start(&timer->tw, timer_due_cb, timer->timeout,
- timer->timeout);
- }
- timer->paused = paused;
- }
-}
-
-/// "timer_start(timeout, callback, opts)" function
-static void f_timer_start(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const long timeout = tv_get_number(&argvars[0]);
- timer_T *timer;
- int repeat = 1;
- dict_T *dict;
-
- rettv->vval.v_number = -1;
-
- if (argvars[2].v_type != VAR_UNKNOWN) {
- if (argvars[2].v_type != VAR_DICT
- || (dict = argvars[2].vval.v_dict) == NULL) {
- EMSG2(_(e_invarg2), tv_get_string(&argvars[2]));
- return;
- }
- dictitem_T *const di = tv_dict_find(dict, S_LEN("repeat"));
- if (di != NULL) {
- repeat = tv_get_number(&di->di_tv);
- if (repeat == 0) {
- repeat = 1;
- }
- }
- }
-
- Callback callback;
- if (!callback_from_typval(&callback, &argvars[1])) {
- return;
- }
-
- timer = xmalloc(sizeof *timer);
- timer->refcount = 1;
- timer->stopped = false;
- timer->paused = false;
- timer->emsg_count = 0;
- timer->repeat_count = repeat;
- timer->timeout = timeout;
- timer->timer_id = last_timer_id++;
- timer->callback = callback;
-
- time_watcher_init(&main_loop, &timer->tw, timer);
- timer->tw.events = multiqueue_new_child(main_loop.events);
- // if main loop is blocked, don't queue up multiple events
- timer->tw.blockable = true;
- time_watcher_start(&timer->tw, timer_due_cb, timeout, timeout);
-
- pmap_put(uint64_t)(timers, timer->timer_id, timer);
- rettv->vval.v_number = timer->timer_id;
-}
-
-
-// "timer_stop(timerid)" function
-static void f_timer_stop(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- if (argvars[0].v_type != VAR_NUMBER) {
- EMSG(_(e_number_exp));
- return;
- }
-
- timer_T *timer = pmap_get(uint64_t)(timers, tv_get_number(&argvars[0]));
-
- if (timer == NULL) {
- return;
- }
-
- timer_stop(timer);
-}
-
-static void f_timer_stopall(typval_T *argvars, typval_T *unused, FunPtr fptr)
-{
- timer_stop_all();
-}
-
// invoked on the main loop
-static void timer_due_cb(TimeWatcher *tw, void *data)
+void timer_due_cb(TimeWatcher *tw, void *data)
{
timer_T *timer = (timer_T *)data;
int save_did_emsg = did_emsg;
int save_called_emsg = called_emsg;
+ const bool save_ex_pressedreturn = get_pressedreturn();
if (timer->stopped || timer->paused) {
return;
@@ -18378,6 +7386,7 @@ static void timer_due_cb(TimeWatcher *tw, void *data)
}
did_emsg = save_did_emsg;
called_emsg = save_called_emsg;
+ set_pressedreturn(save_ex_pressedreturn);
if (timer->emsg_count >= 3) {
timer_stop(timer);
@@ -18395,7 +7404,31 @@ static void timer_due_cb(TimeWatcher *tw, void *data)
timer_decref(timer);
}
-static void timer_stop(timer_T *timer)
+uint64_t timer_start(const long timeout,
+ const int repeat_count,
+ const Callback *const callback)
+{
+ timer_T *timer = xmalloc(sizeof *timer);
+ timer->refcount = 1;
+ timer->stopped = false;
+ timer->paused = false;
+ timer->emsg_count = 0;
+ timer->repeat_count = repeat_count;
+ timer->timeout = timeout;
+ timer->timer_id = last_timer_id++;
+ timer->callback = *callback;
+
+ time_watcher_init(&main_loop, &timer->tw, timer);
+ timer->tw.events = multiqueue_new_child(main_loop.events);
+ // if main loop is blocked, don't queue up multiple events
+ timer->tw.blockable = true;
+ time_watcher_start(&timer->tw, timer_due_cb, timeout, timeout);
+
+ pmap_put(uint64_t)(timers, timer->timer_id, timer);
+ return timer->timer_id;
+}
+
+void timer_stop(timer_T *timer)
{
if (timer->stopped) {
// avoid double free
@@ -18424,7 +7457,7 @@ static void timer_decref(timer_T *timer)
}
}
-static void timer_stop_all(void)
+void timer_stop_all(void)
{
timer_T *timer;
map_foreach_value(timers, timer, {
@@ -18437,517 +7470,6 @@ void timer_teardown(void)
timer_stop_all();
}
-/*
- * "tolower(string)" function
- */
-static void f_tolower(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = (char_u *)strcase_save(tv_get_string(&argvars[0]),
- false);
-}
-
-/*
- * "toupper(string)" function
- */
-static void f_toupper(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = (char_u *)strcase_save(tv_get_string(&argvars[0]),
- true);
-}
-
-/*
- * "tr(string, fromstr, tostr)" function
- */
-static void f_tr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- char buf[NUMBUFLEN];
- char buf2[NUMBUFLEN];
-
- const char *in_str = tv_get_string(&argvars[0]);
- const char *fromstr = tv_get_string_buf_chk(&argvars[1], buf);
- const char *tostr = tv_get_string_buf_chk(&argvars[2], buf2);
-
- // Default return value: empty string.
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = NULL;
- if (fromstr == NULL || tostr == NULL) {
- return; // Type error; errmsg already given.
- }
- garray_T ga;
- ga_init(&ga, (int)sizeof(char), 80);
-
- if (!has_mbyte) {
- // Not multi-byte: fromstr and tostr must be the same length.
- if (strlen(fromstr) != strlen(tostr)) {
- goto error;
- }
- }
-
- // fromstr and tostr have to contain the same number of chars.
- bool first = true;
- while (*in_str != NUL) {
- if (has_mbyte) {
- const char *cpstr = in_str;
- const int inlen = (*mb_ptr2len)((const char_u *)in_str);
- int cplen = inlen;
- int idx = 0;
- int fromlen;
- for (const char *p = fromstr; *p != NUL; p += fromlen) {
- fromlen = (*mb_ptr2len)((const char_u *)p);
- if (fromlen == inlen && STRNCMP(in_str, p, inlen) == 0) {
- int tolen;
- for (p = tostr; *p != NUL; p += tolen) {
- tolen = (*mb_ptr2len)((const char_u *)p);
- if (idx-- == 0) {
- cplen = tolen;
- cpstr = (char *)p;
- break;
- }
- }
- if (*p == NUL) { // tostr is shorter than fromstr.
- goto error;
- }
- break;
- }
- idx++;
- }
-
- if (first && cpstr == in_str) {
- // Check that fromstr and tostr have the same number of
- // (multi-byte) characters. Done only once when a character
- // of in_str doesn't appear in fromstr.
- first = false;
- int tolen;
- for (const char *p = tostr; *p != NUL; p += tolen) {
- tolen = (*mb_ptr2len)((const char_u *)p);
- idx--;
- }
- if (idx != 0) {
- goto error;
- }
- }
-
- ga_grow(&ga, cplen);
- memmove((char *)ga.ga_data + ga.ga_len, cpstr, (size_t)cplen);
- ga.ga_len += cplen;
-
- in_str += inlen;
- } else {
- // When not using multi-byte chars we can do it faster.
- const char *const p = strchr(fromstr, *in_str);
- if (p != NULL) {
- ga_append(&ga, tostr[p - fromstr]);
- } else {
- ga_append(&ga, *in_str);
- }
- in_str++;
- }
- }
-
- // add a terminating NUL
- ga_append(&ga, NUL);
-
- rettv->vval.v_string = ga.ga_data;
- return;
-error:
- EMSG2(_(e_invarg2), fromstr);
- ga_clear(&ga);
- return;
-}
-
-// "trim({expr})" function
-static void f_trim(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- char buf1[NUMBUFLEN];
- char buf2[NUMBUFLEN];
- const char_u *head = (const char_u *)tv_get_string_buf_chk(&argvars[0], buf1);
- const char_u *mask = NULL;
- const char_u *tail;
- const char_u *prev;
- const char_u *p;
- int c1;
-
- rettv->v_type = VAR_STRING;
- if (head == NULL) {
- rettv->vval.v_string = NULL;
- return;
- }
-
- if (argvars[1].v_type == VAR_STRING) {
- mask = (const char_u *)tv_get_string_buf_chk(&argvars[1], buf2);
- }
-
- while (*head != NUL) {
- c1 = PTR2CHAR(head);
- if (mask == NULL) {
- if (c1 > ' ' && c1 != 0xa0) {
- break;
- }
- } else {
- for (p = mask; *p != NUL; MB_PTR_ADV(p)) {
- if (c1 == PTR2CHAR(p)) {
- break;
- }
- }
- if (*p == NUL) {
- break;
- }
- }
- MB_PTR_ADV(head);
- }
-
- for (tail = head + STRLEN(head); tail > head; tail = prev) {
- prev = tail;
- MB_PTR_BACK(head, prev);
- c1 = PTR2CHAR(prev);
- if (mask == NULL) {
- if (c1 > ' ' && c1 != 0xa0) {
- break;
- }
- } else {
- for (p = mask; *p != NUL; MB_PTR_ADV(p)) {
- if (c1 == PTR2CHAR(p)) {
- break;
- }
- }
- if (*p == NUL) {
- break;
- }
- }
- }
- rettv->vval.v_string = vim_strnsave(head, (int)(tail - head));
-}
-
-/*
- * "type(expr)" function
- */
-static void f_type(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- int n = -1;
-
- switch (argvars[0].v_type) {
- case VAR_NUMBER: n = VAR_TYPE_NUMBER; break;
- case VAR_STRING: n = VAR_TYPE_STRING; break;
- case VAR_PARTIAL:
- case VAR_FUNC: n = VAR_TYPE_FUNC; break;
- case VAR_LIST: n = VAR_TYPE_LIST; break;
- case VAR_DICT: n = VAR_TYPE_DICT; break;
- case VAR_FLOAT: n = VAR_TYPE_FLOAT; break;
- case VAR_SPECIAL: {
- switch (argvars[0].vval.v_special) {
- case kSpecialVarTrue:
- case kSpecialVarFalse: {
- n = VAR_TYPE_BOOL;
- break;
- }
- case kSpecialVarNull: {
- n = 7;
- break;
- }
- }
- break;
- }
- case VAR_UNKNOWN: {
- internal_error("f_type(UNKNOWN)");
- break;
- }
- }
- rettv->vval.v_number = n;
-}
-
-/*
- * "undofile(name)" function
- */
-static void f_undofile(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->v_type = VAR_STRING;
- const char *const fname = tv_get_string(&argvars[0]);
-
- if (*fname == NUL) {
- // If there is no file name there will be no undo file.
- rettv->vval.v_string = NULL;
- } else {
- char *ffname = FullName_save(fname, true);
-
- if (ffname != NULL) {
- rettv->vval.v_string = (char_u *)u_get_undo_file_name(ffname, false);
- }
- xfree(ffname);
- }
-}
-
-/*
- * "undotree()" function
- */
-static void f_undotree(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- tv_dict_alloc_ret(rettv);
-
- dict_T *dict = rettv->vval.v_dict;
-
- tv_dict_add_nr(dict, S_LEN("synced"), (varnumber_T)curbuf->b_u_synced);
- tv_dict_add_nr(dict, S_LEN("seq_last"), (varnumber_T)curbuf->b_u_seq_last);
- tv_dict_add_nr(dict, S_LEN("save_last"),
- (varnumber_T)curbuf->b_u_save_nr_last);
- tv_dict_add_nr(dict, S_LEN("seq_cur"), (varnumber_T)curbuf->b_u_seq_cur);
- tv_dict_add_nr(dict, S_LEN("time_cur"), (varnumber_T)curbuf->b_u_time_cur);
- tv_dict_add_nr(dict, S_LEN("save_cur"), (varnumber_T)curbuf->b_u_save_nr_cur);
-
- tv_dict_add_list(dict, S_LEN("entries"), u_eval_tree(curbuf->b_u_oldhead));
-}
-
-/*
- * "values(dict)" function
- */
-static void f_values(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- dict_list(argvars, rettv, 1);
-}
-
-/*
- * "virtcol(string)" function
- */
-static void f_virtcol(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- colnr_T vcol = 0;
- pos_T *fp;
- int fnum = curbuf->b_fnum;
-
- fp = var2fpos(&argvars[0], FALSE, &fnum);
- if (fp != NULL && fp->lnum <= curbuf->b_ml.ml_line_count
- && fnum == curbuf->b_fnum) {
- getvvcol(curwin, fp, NULL, NULL, &vcol);
- ++vcol;
- }
-
- rettv->vval.v_number = vcol;
-}
-
-/*
- * "visualmode()" function
- */
-static void f_visualmode(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- char_u str[2];
-
- rettv->v_type = VAR_STRING;
- str[0] = curbuf->b_visual_mode_eval;
- str[1] = NUL;
- rettv->vval.v_string = vim_strsave(str);
-
- /* A non-zero number or non-empty string argument: reset mode. */
- if (non_zero_arg(&argvars[0]))
- curbuf->b_visual_mode_eval = NUL;
-}
-
-/*
- * "wildmenumode()" function
- */
-static void f_wildmenumode(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- if (wild_menu_showing)
- rettv->vval.v_number = 1;
-}
-
-/// "win_findbuf()" function
-static void f_win_findbuf(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- tv_list_alloc_ret(rettv, kListLenMayKnow);
- win_findbuf(argvars, rettv->vval.v_list);
-}
-
-/// "win_getid()" function
-static void f_win_getid(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = win_getid(argvars);
-}
-
-/// "win_gotoid()" function
-static void f_win_gotoid(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = win_gotoid(argvars);
-}
-
-/// "win_id2tabwin()" function
-static void f_win_id2tabwin(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- win_id2tabwin(argvars, rettv);
-}
-
-/// "win_id2win()" function
-static void f_win_id2win(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = win_id2win(argvars);
-}
-
-/// "winbufnr(nr)" function
-static void f_winbufnr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- win_T *wp = find_win_by_nr_or_id(&argvars[0]);
- if (wp == NULL) {
- rettv->vval.v_number = -1;
- } else {
- rettv->vval.v_number = wp->w_buffer->b_fnum;
- }
-}
-
-/*
- * "wincol()" function
- */
-static void f_wincol(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- validate_cursor();
- rettv->vval.v_number = curwin->w_wcol + 1;
-}
-
-/// "winheight(nr)" function
-static void f_winheight(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- win_T *wp = find_win_by_nr_or_id(&argvars[0]);
- if (wp == NULL) {
- rettv->vval.v_number = -1;
- } else {
- rettv->vval.v_number = wp->w_height;
- }
-}
-
-// "winlayout()" function
-static void f_winlayout(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- tabpage_T *tp;
-
- tv_list_alloc_ret(rettv, 2);
-
- if (argvars[0].v_type == VAR_UNKNOWN) {
- tp = curtab;
- } else {
- tp = find_tabpage((int)tv_get_number(&argvars[0]));
- if (tp == NULL) {
- return;
- }
- }
-
- get_framelayout(tp->tp_topframe, rettv->vval.v_list, true);
-}
-
-/*
- * "winline()" function
- */
-static void f_winline(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- validate_cursor();
- rettv->vval.v_number = curwin->w_wrow + 1;
-}
-
-/*
- * "winnr()" function
- */
-static void f_winnr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- int nr = 1;
-
- nr = get_winnr(curtab, &argvars[0]);
- rettv->vval.v_number = nr;
-}
-
-/*
- * "winrestcmd()" function
- */
-static void f_winrestcmd(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- int winnr = 1;
- garray_T ga;
- char_u buf[50];
-
- ga_init(&ga, (int)sizeof(char), 70);
- FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
- sprintf((char *)buf, "%dresize %d|", winnr, wp->w_height);
- ga_concat(&ga, buf);
- sprintf((char *)buf, "vert %dresize %d|", winnr, wp->w_width);
- ga_concat(&ga, buf);
- ++winnr;
- }
- ga_append(&ga, NUL);
-
- rettv->vval.v_string = ga.ga_data;
- rettv->v_type = VAR_STRING;
-}
-
-/*
- * "winrestview()" function
- */
-static void f_winrestview(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- dict_T *dict;
-
- if (argvars[0].v_type != VAR_DICT
- || (dict = argvars[0].vval.v_dict) == NULL) {
- EMSG(_(e_invarg));
- } else {
- dictitem_T *di;
- if ((di = tv_dict_find(dict, S_LEN("lnum"))) != NULL) {
- curwin->w_cursor.lnum = tv_get_number(&di->di_tv);
- }
- if ((di = tv_dict_find(dict, S_LEN("col"))) != NULL) {
- curwin->w_cursor.col = tv_get_number(&di->di_tv);
- }
- if ((di = tv_dict_find(dict, S_LEN("coladd"))) != NULL) {
- curwin->w_cursor.coladd = tv_get_number(&di->di_tv);
- }
- if ((di = tv_dict_find(dict, S_LEN("curswant"))) != NULL) {
- curwin->w_curswant = tv_get_number(&di->di_tv);
- curwin->w_set_curswant = false;
- }
- if ((di = tv_dict_find(dict, S_LEN("topline"))) != NULL) {
- set_topline(curwin, tv_get_number(&di->di_tv));
- }
- if ((di = tv_dict_find(dict, S_LEN("topfill"))) != NULL) {
- curwin->w_topfill = tv_get_number(&di->di_tv);
- }
- if ((di = tv_dict_find(dict, S_LEN("leftcol"))) != NULL) {
- curwin->w_leftcol = tv_get_number(&di->di_tv);
- }
- if ((di = tv_dict_find(dict, S_LEN("skipcol"))) != NULL) {
- curwin->w_skipcol = tv_get_number(&di->di_tv);
- }
-
- check_cursor();
- win_new_height(curwin, curwin->w_height);
- win_new_width(curwin, curwin->w_width);
- changed_window_setting();
-
- if (curwin->w_topline <= 0)
- curwin->w_topline = 1;
- if (curwin->w_topline > curbuf->b_ml.ml_line_count)
- curwin->w_topline = curbuf->b_ml.ml_line_count;
- check_topfill(curwin, true);
- }
-}
-
-/*
- * "winsaveview()" function
- */
-static void f_winsaveview(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- dict_T *dict;
-
- tv_dict_alloc_ret(rettv);
- dict = rettv->vval.v_dict;
-
- tv_dict_add_nr(dict, S_LEN("lnum"), (varnumber_T)curwin->w_cursor.lnum);
- tv_dict_add_nr(dict, S_LEN("col"), (varnumber_T)curwin->w_cursor.col);
- tv_dict_add_nr(dict, S_LEN("coladd"), (varnumber_T)curwin->w_cursor.coladd);
- update_curswant();
- tv_dict_add_nr(dict, S_LEN("curswant"), (varnumber_T)curwin->w_curswant);
-
- tv_dict_add_nr(dict, S_LEN("topline"), (varnumber_T)curwin->w_topline);
- tv_dict_add_nr(dict, S_LEN("topfill"), (varnumber_T)curwin->w_topfill);
- tv_dict_add_nr(dict, S_LEN("leftcol"), (varnumber_T)curwin->w_leftcol);
- tv_dict_add_nr(dict, S_LEN("skipcol"), (varnumber_T)curwin->w_skipcol);
-}
-
/// Write "list" of strings to file "fd".
///
/// @param fp File to write to.
@@ -18955,8 +7477,8 @@ static void f_winsaveview(typval_T *argvars, typval_T *rettv, FunPtr fptr)
/// @param[in] binary Whether to write in binary mode.
///
/// @return true in case of success, false otherwise.
-static bool write_list(FileDescriptor *const fp, const list_T *const list,
- const bool binary)
+bool write_list(FileDescriptor *const fp, const list_T *const list,
+ const bool binary)
FUNC_ATTR_NONNULL_ARG(1)
{
int error = 0;
@@ -19014,7 +7536,7 @@ write_list_error:
/// @param[in] endnl If true, the output will end in a newline (if a list).
/// @returns an allocated string if `tv` represents a VimL string, list, or
/// number; NULL otherwise.
-static char *save_tv_as_string(typval_T *tv, ptrdiff_t *const len, bool endnl)
+char *save_tv_as_string(typval_T *tv, ptrdiff_t *const len, bool endnl)
FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_ALL
{
*len = 0;
@@ -19093,101 +7615,6 @@ static char *save_tv_as_string(typval_T *tv, ptrdiff_t *const len, bool endnl)
return ret;
}
-/// "winwidth(nr)" function
-static void f_winwidth(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- win_T *wp = find_win_by_nr_or_id(&argvars[0]);
- if (wp == NULL) {
- rettv->vval.v_number = -1;
- } else {
- rettv->vval.v_number = wp->w_width;
- }
-}
-
-/// "wordcount()" function
-static void f_wordcount(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- tv_dict_alloc_ret(rettv);
- cursor_pos_info(rettv->vval.v_dict);
-}
-
-/// "writefile()" function
-static void f_writefile(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = -1;
-
- if (check_restricted() || check_secure()) {
- return;
- }
-
- if (argvars[0].v_type != VAR_LIST) {
- EMSG2(_(e_listarg), "writefile()");
- return;
- }
- const list_T *const list = argvars[0].vval.v_list;
- TV_LIST_ITER_CONST(list, li, {
- if (!tv_check_str_or_nr(TV_LIST_ITEM_TV(li))) {
- return;
- }
- });
-
- bool binary = false;
- bool append = false;
- bool do_fsync = !!p_fs;
- if (argvars[2].v_type != VAR_UNKNOWN) {
- const char *const flags = tv_get_string_chk(&argvars[2]);
- if (flags == NULL) {
- return;
- }
- for (const char *p = flags; *p; p++) {
- switch (*p) {
- case 'b': { binary = true; break; }
- case 'a': { append = true; break; }
- case 's': { do_fsync = true; break; }
- case 'S': { do_fsync = false; break; }
- default: {
- // Using %s, p and not %c, *p to preserve multibyte characters
- emsgf(_("E5060: Unknown flag: %s"), p);
- return;
- }
- }
- }
- }
-
- char buf[NUMBUFLEN];
- const char *const fname = tv_get_string_buf_chk(&argvars[1], buf);
- if (fname == NULL) {
- return;
- }
- FileDescriptor fp;
- int error;
- if (*fname == NUL) {
- EMSG(_("E482: Can't open file with an empty name"));
- } else if ((error = file_open(&fp, fname,
- ((append ? kFileAppend : kFileTruncate)
- | kFileCreate), 0666)) != 0) {
- emsgf(_("E482: Can't open file %s for writing: %s"),
- fname, os_strerror(error));
- } else {
- if (write_list(&fp, list, binary)) {
- rettv->vval.v_number = 0;
- }
- if ((error = file_close(&fp, do_fsync)) != 0) {
- emsgf(_("E80: Error when closing file %s: %s"),
- fname, os_strerror(error));
- }
- }
-}
-/*
- * "xor(expr, expr)" function
- */
-static void f_xor(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = tv_get_number_chk(&argvars[0], NULL)
- ^ tv_get_number_chk(&argvars[1], NULL);
-}
-
-
/// Translate a VimL object into a position
///
/// Accepts VAR_LIST and VAR_STRING objects. Does not give an error for invalid
@@ -19280,19 +7707,19 @@ pos_T *var2fpos(const typval_T *const tv, const int dollar_lnum,
if (name[0] == 'w' && dollar_lnum) {
pos.col = 0;
- if (name[1] == '0') { /* "w0": first visible line */
+ if (name[1] == '0') { // "w0": first visible line
update_topline();
// In silent Ex mode topline is zero, but that's not a valid line
// number; use one instead.
pos.lnum = curwin->w_topline > 0 ? curwin->w_topline : 1;
return &pos;
- } else if (name[1] == '$') { /* "w$": last visible line */
+ } else if (name[1] == '$') { // "w$": last visible line
validate_botline();
// In silent Ex mode botline is zero, return zero then.
pos.lnum = curwin->w_botline > 0 ? curwin->w_botline - 1 : 0;
return &pos;
}
- } else if (name[0] == '$') { /* last column or line */
+ } else if (name[0] == '$') { // last column or line
if (dollar_lnum) {
pos.lnum = curbuf->b_ml.ml_line_count;
pos.col = 0;
@@ -19389,7 +7816,7 @@ static int get_env_len(const char_u **arg)
// Get the length of the name of a function or internal variable.
// "arg" is advanced to the first non-white character after the name.
// Return 0 if something is wrong.
-static int get_id_len(const char **const arg)
+int get_id_len(const char **const arg)
{
int len;
@@ -19425,14 +7852,14 @@ static int get_id_len(const char **const arg)
* If the name contains 'magic' {}'s, expand them and return the
* expanded name in an allocated string via 'alias' - caller must free.
*/
-static int get_name_len(const char **const arg,
- char **alias,
- int evaluate,
- int verbose)
+int get_name_len(const char **const arg,
+ char **alias,
+ int evaluate,
+ int verbose)
{
int len;
- *alias = NULL; /* default to no alias */
+ *alias = NULL; // default to no alias
if ((*arg)[0] == (char)K_SPECIAL && (*arg)[1] == (char)KS_EXTRA
&& (*arg)[2] == (char)KE_SNR) {
@@ -19442,7 +7869,7 @@ static int get_name_len(const char **const arg,
}
len = eval_fname_script(*arg);
if (len > 0) {
- /* literal "<SID>", "s:" or "<SNR>" */
+ // literal "<SID>", "s:" or "<SNR>"
*arg += len;
}
@@ -19490,8 +7917,8 @@ static int get_name_len(const char **const arg,
// "flags" can have FNE_INCL_BR and FNE_CHECK_START.
// Return a pointer to just after the name. Equal to "arg" if there is no
// valid name.
-static const char_u *find_name_end(const char_u *arg, const char_u **expr_start,
- const char_u **expr_end, int flags)
+const char_u *find_name_end(const char_u *arg, const char_u **expr_start,
+ const char_u **expr_end, int flags)
{
int mb_nest = 0;
int br_nest = 0;
@@ -19605,7 +8032,7 @@ static char_u *make_expanded_name(const char_u *in_start, char_u *expr_start,
}
xfree(temp_result);
- *in_end = c1; /* put char back for error messages */
+ *in_end = c1; // put char back for error messages
*expr_start = '{';
*expr_end = '}';
@@ -19614,7 +8041,7 @@ static char_u *make_expanded_name(const char_u *in_start, char_u *expr_start,
(const char_u **)&expr_start,
(const char_u **)&expr_end, 0);
if (expr_start != NULL) {
- /* Further expansion! */
+ // Further expansion!
temp_result = make_expanded_name(retval, expr_start,
expr_end, temp_result);
xfree(retval);
@@ -19629,7 +8056,7 @@ static char_u *make_expanded_name(const char_u *in_start, char_u *expr_start,
* Return TRUE if character "c" can be used in a variable or function name.
* Does not include '{' or '}' for magic braces.
*/
-static int eval_isnamec(int c)
+int eval_isnamec(int c)
{
return ASCII_ISALNUM(c) || c == '_' || c == ':' || c == AUTOLOAD_CHAR;
}
@@ -19638,7 +8065,7 @@ static int eval_isnamec(int c)
* Return TRUE if character "c" can be used as the first character in a
* variable or function name (excluding '{' and '}').
*/
-static int eval_isnamec1(int c)
+int eval_isnamec1(int c)
{
return ASCII_ISALPHA(c) || c == '_';
}
@@ -19709,6 +8136,17 @@ void set_vim_var_nr(const VimVarIndex idx, const varnumber_T val)
vimvars[idx].vv_nr = val;
}
+/// Set boolean v: {true, false} to the given value
+///
+/// @param[in] idx Index of variable to set.
+/// @param[in] val Value to set to.
+void set_vim_var_bool(const VimVarIndex idx, const BoolVarValue val)
+{
+ tv_clear(&vimvars[idx].vv_tv);
+ vimvars[idx].vv_type = VAR_BOOL;
+ vimvars[idx].vv_bool = val;
+}
+
/// Set special v: variable to the given value
///
/// @param[in] idx Index of variable to set.
@@ -19772,6 +8210,20 @@ void set_vim_var_dict(const VimVarIndex idx, dict_T *const val)
}
}
+/// 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++) {
+ tv_list_append_string(l, (const char *const)argv[i], -1);
+ TV_LIST_ITEM_TV(tv_list_last(l))->v_lock = VAR_FIXED;
+ }
+ set_vim_var_list(VV_ARGV, l);
+}
+
/*
* Set v:register if needed.
*/
@@ -19828,10 +8280,7 @@ char_u *v_throwpoint(char_u *oldval)
*/
char_u *set_cmdarg(exarg_T *eap, char_u *oldarg)
{
- char_u *oldval;
- char_u *newval;
-
- oldval = vimvars[VV_CMDARG].vv_str;
+ char_u *oldval = vimvars[VV_CMDARG].vv_str;
if (eap == NULL) {
xfree(oldval);
vimvars[VV_CMDARG].vv_str = oldarg;
@@ -19847,14 +8296,18 @@ char_u *set_cmdarg(exarg_T *eap, char_u *oldarg)
if (eap->read_edit)
len += 7;
- if (eap->force_ff != 0)
- len += STRLEN(eap->cmd + eap->force_ff) + 6;
- if (eap->force_enc != 0)
+ if (eap->force_ff != 0) {
+ len += 10; // " ++ff=unix"
+ }
+ if (eap->force_enc != 0) {
len += STRLEN(eap->cmd + eap->force_enc) + 7;
- if (eap->bad_char != 0)
- len += 7 + 4; /* " ++bad=" + "keep" or "drop" */
+ }
+ if (eap->bad_char != 0) {
+ len += 7 + 4; // " ++bad=" + "keep" or "drop"
+ }
- newval = xmalloc(len + 1);
+ const size_t newval_len = len + 1;
+ char_u *newval = xmalloc(newval_len);
if (eap->force_bin == FORCE_BIN)
sprintf((char *)newval, " ++bin");
@@ -19866,18 +8319,23 @@ char_u *set_cmdarg(exarg_T *eap, char_u *oldarg)
if (eap->read_edit)
STRCAT(newval, " ++edit");
- if (eap->force_ff != 0)
- sprintf((char *)newval + STRLEN(newval), " ++ff=%s",
- eap->cmd + eap->force_ff);
- if (eap->force_enc != 0)
- sprintf((char *)newval + STRLEN(newval), " ++enc=%s",
- eap->cmd + eap->force_enc);
- if (eap->bad_char == BAD_KEEP)
+ if (eap->force_ff != 0) {
+ snprintf((char *)newval + STRLEN(newval), newval_len, " ++ff=%s",
+ eap->force_ff == 'u' ? "unix" :
+ eap->force_ff == 'd' ? "dos" : "mac");
+ }
+ if (eap->force_enc != 0) {
+ snprintf((char *)newval + STRLEN(newval), newval_len, " ++enc=%s",
+ eap->cmd + eap->force_enc);
+ }
+ if (eap->bad_char == BAD_KEEP) {
STRCPY(newval + STRLEN(newval), " ++bad=keep");
- else if (eap->bad_char == BAD_DROP)
+ } else if (eap->bad_char == BAD_DROP) {
STRCPY(newval + STRLEN(newval), " ++bad=drop");
- else if (eap->bad_char != 0)
- sprintf((char *)newval + STRLEN(newval), " ++bad=%c", eap->bad_char);
+ } else if (eap->bad_char != 0) {
+ snprintf((char *)newval + STRLEN(newval), newval_len, " ++bad=%c",
+ eap->bad_char);
+ }
vimvars[VV_CMDARG].vv_str = newval;
return oldval;
}
@@ -19886,7 +8344,7 @@ char_u *set_cmdarg(exarg_T *eap, char_u *oldarg)
* Get the value of internal variable "name".
* Return OK or FAIL.
*/
-static int get_var_tv(
+int get_var_tv(
const char *name,
int len, // length of "name"
typval_T *rettv, // NULL when only checking existence
@@ -19920,7 +8378,7 @@ static int get_var_tv(
}
/// Check if variable "name[len]" is a local variable or an argument.
-/// If so, "*eval_lavars_used" is set to TRUE.
+/// If so, "*eval_lavars_used" is set to true.
static void check_vars(const char *name, size_t len)
{
if (eval_lavars_used == NULL) {
@@ -19937,22 +8395,69 @@ static void check_vars(const char *name, size_t len)
}
}
+/// check if special v:lua value for calling lua functions
+bool is_luafunc(partial_T *partial)
+{
+ return partial == vvlua_partial;
+}
+
+/// check if special v:lua value for calling lua functions
+static bool tv_is_luafunc(typval_T *tv)
+{
+ return tv->v_type == VAR_PARTIAL && is_luafunc(tv->vval.v_partial);
+}
+
+/// check the function name after "v:lua."
+int check_luafunc_name(const char *str, bool paren)
+{
+ const char *p = str;
+ while (ASCII_ISALNUM(*p) || *p == '_' || *p == '.') {
+ p++;
+ }
+ if (*p != (paren ? '(' : NUL)) {
+ return 0;
+ } else {
+ return (int)(p-str);
+ }
+}
+
/// Handle expr[expr], expr[expr:expr] subscript and .name lookup.
/// Also handle function call with Funcref variable: func(expr)
/// Can all be combined: dict.func(expr)[idx]['func'](expr)
-static int
+int
handle_subscript(
const char **const arg,
typval_T *rettv,
- int evaluate, /* do more than finding the end */
- int verbose /* give error messages */
+ int evaluate, // do more than finding the end
+ int verbose // give error messages
)
{
int ret = OK;
dict_T *selfdict = NULL;
- char_u *s;
+ const char_u *s;
int len;
typval_T functv;
+ int slen = 0;
+ bool lua = false;
+
+ if (tv_is_luafunc(rettv)) {
+ if (**arg != '.') {
+ tv_clear(rettv);
+ ret = FAIL;
+ } else {
+ (*arg)++;
+
+ lua = true;
+ s = (char_u *)(*arg);
+ slen = check_luafunc_name(*arg, true);
+ if (slen == 0) {
+ tv_clear(rettv);
+ ret = FAIL;
+ }
+ (*arg) += slen;
+ }
+ }
+
while (ret == OK
&& (**arg == '['
@@ -19969,14 +8474,16 @@ handle_subscript(
// Invoke the function. Recursive!
if (functv.v_type == VAR_PARTIAL) {
pt = functv.vval.v_partial;
- s = partial_name(pt);
+ if (!lua) {
+ s = partial_name(pt);
+ }
} else {
s = functv.vval.v_string;
}
} else {
s = (char_u *)"";
}
- ret = get_func_tv(s, (int)STRLEN(s), rettv, (char_u **)arg,
+ ret = get_func_tv(s, lua ? slen : (int)STRLEN(s), rettv, (char_u **)arg,
curwin->w_cursor.lnum, curwin->w_cursor.lnum,
&len, evaluate, pt, selfdict);
@@ -20021,7 +8528,7 @@ handle_subscript(
return ret;
}
-void set_selfdict(typval_T *rettv, dict_T *selfdict)
+void set_selfdict(typval_T *const rettv, dict_T *const selfdict)
{
// Don't do this when "dict.Func" is already a partial that was bound
// explicitly (pt_auto is false).
@@ -20029,61 +8536,7 @@ void set_selfdict(typval_T *rettv, dict_T *selfdict)
&& rettv->vval.v_partial->pt_dict != NULL) {
return;
}
- char_u *fname;
- char_u *tofree = NULL;
- ufunc_T *fp;
- char_u fname_buf[FLEN_FIXED + 1];
- int error;
-
- if (rettv->v_type == VAR_PARTIAL && rettv->vval.v_partial->pt_func != NULL) {
- fp = rettv->vval.v_partial->pt_func;
- } else {
- fname = rettv->v_type == VAR_FUNC || rettv->v_type == VAR_STRING
- ? rettv->vval.v_string
- : rettv->vval.v_partial->pt_name;
- // Translate "s:func" to the stored function name.
- fname = fname_trans_sid(fname, fname_buf, &tofree, &error);
- fp = find_func(fname);
- xfree(tofree);
- }
-
- // Turn "dict.Func" into a partial for "Func" with "dict".
- if (fp != NULL && (fp->uf_flags & FC_DICT)) {
- partial_T *pt = (partial_T *)xcalloc(1, sizeof(partial_T));
- pt->pt_refcount = 1;
- pt->pt_dict = selfdict;
- (selfdict->dv_refcount)++;
- pt->pt_auto = true;
- if (rettv->v_type == VAR_FUNC || rettv->v_type == VAR_STRING) {
- // Just a function: Take over the function name and use selfdict.
- pt->pt_name = rettv->vval.v_string;
- } else {
- partial_T *ret_pt = rettv->vval.v_partial;
- int i;
-
- // Partial: copy the function name, use selfdict and copy
- // args. Can't take over name or args, the partial might
- // be referenced elsewhere.
- if (ret_pt->pt_name != NULL) {
- pt->pt_name = vim_strsave(ret_pt->pt_name);
- func_ref(pt->pt_name);
- } else {
- pt->pt_func = ret_pt->pt_func;
- func_ptr_ref(pt->pt_func);
- }
- if (ret_pt->pt_argc > 0) {
- size_t arg_size = sizeof(typval_T) * ret_pt->pt_argc;
- pt->pt_argv = (typval_T *)xmalloc(arg_size);
- pt->pt_argc = ret_pt->pt_argc;
- for (i = 0; i < pt->pt_argc; i++) {
- tv_copy(&ret_pt->pt_argv[i], &pt->pt_argv[i]);
- }
- }
- partial_unref(ret_pt);
- }
- rettv->v_type = VAR_PARTIAL;
- rettv->vval.v_partial = pt;
- }
+ make_partial(selfdict, rettv);
}
// Find variable "name" in the list of variables.
@@ -20091,8 +8544,8 @@ void set_selfdict(typval_T *rettv, dict_T *selfdict)
// Careful: "a:0" variables don't have a name.
// When "htp" is not NULL we are writing to the variable, set "htp" to the
// hashtab_T used.
-static dictitem_T *find_var(const char *const name, const size_t name_len,
- hashtab_T **htp, int no_autoload)
+dictitem_T *find_var(const char *const name, const size_t name_len,
+ hashtab_T **htp, int no_autoload)
{
const char *varname;
hashtab_T *const ht = find_var_ht(name, name_len, &varname);
@@ -20114,7 +8567,8 @@ static dictitem_T *find_var(const char *const name, const size_t name_len,
return find_var_in_scoped_ht(name, name_len, no_autoload || htp != NULL);
}
-/// Find variable in hashtab
+/// Find variable in hashtab.
+/// When "varname" is empty returns curwin/curtab/etc vars dictionary.
///
/// @param[in] ht Hashtab to find variable in.
/// @param[in] htname Hashtab name (first character).
@@ -20125,11 +8579,11 @@ static dictitem_T *find_var(const char *const name, const size_t name_len,
///
/// @return pointer to the dictionary item with the found variable or NULL if it
/// was not found.
-static dictitem_T *find_var_in_ht(hashtab_T *const ht,
- int htname,
- const char *const varname,
- const size_t varname_len,
- int no_autoload)
+dictitem_T *find_var_in_ht(hashtab_T *const ht,
+ int htname,
+ const char *const varname,
+ const size_t varname_len,
+ int no_autoload)
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
{
hashitem_T *hi;
@@ -20143,10 +8597,8 @@ static dictitem_T *find_var_in_ht(hashtab_T *const ht,
case 'b': return (dictitem_T *)&curbuf->b_bufvar;
case 'w': return (dictitem_T *)&curwin->w_winvar;
case 't': return (dictitem_T *)&curtab->tp_winvar;
- case 'l': return (current_funccal == NULL
- ? NULL : (dictitem_T *)&current_funccal->l_vars_var);
- case 'a': return (current_funccal == NULL
- ? NULL : (dictitem_T *)&get_funccal()->l_avars_var);
+ case 'l': return get_funccal_local_var();
+ case 'a': return get_funccal_args_var();
}
return NULL;
}
@@ -20172,46 +8624,7 @@ static dictitem_T *find_var_in_ht(hashtab_T *const ht,
return TV_DICT_HI2DI(hi);
}
-// Get function call environment based on backtrace debug level
-static funccall_T *get_funccal(void)
-{
- funccall_T *funccal = current_funccal;
- if (debug_backtrace_level > 0) {
- for (int i = 0; i < debug_backtrace_level; i++) {
- funccall_T *temp_funccal = funccal->caller;
- if (temp_funccal) {
- funccal = temp_funccal;
- } else {
- // backtrace level overflow. reset to max
- debug_backtrace_level = i;
- }
- }
- }
-
- return funccal;
-}
-
-/// Return the hashtable used for argument in the current funccal.
-/// Return NULL if there is no current funccal.
-static hashtab_T *get_funccal_args_ht(void)
-{
- if (current_funccal == NULL) {
- return NULL;
- }
- return &get_funccal()->l_avars.dv_hashtab;
-}
-
-/// Return the hashtable used for local variables in the current funccal.
-/// Return NULL if there is no current funccal.
-static hashtab_T *get_funccal_local_ht(void)
-{
- if (current_funccal == NULL) {
- return NULL;
- }
- return &get_funccal()->l_vars.dv_hashtab;
-}
-
-/// Find the dict and hashtable used for a variable
+/// Finds the dict (g:, l:, s:, …) and hashtable used for a variable.
///
/// @param[in] name Variable name, possibly with scope prefix.
/// @param[in] name_len Variable name length.
@@ -20224,6 +8637,7 @@ static hashtab_T *find_var_ht_dict(const char *name, const size_t name_len,
const char **varname, dict_T **d)
{
hashitem_T *hi;
+ funccall_T *funccal = get_funccal();
*d = NULL;
if (name_len == 0) {
@@ -20243,16 +8657,16 @@ static hashtab_T *find_var_ht_dict(const char *name, const size_t name_len,
return &compat_hashtab;
}
- if (current_funccal == NULL) {
+ if (funccal == NULL) { // global variable
*d = &globvardict;
- } else {
- *d = &get_funccal()->l_vars; // l: variable
+ } else { // l: variable
+ *d = &funccal->l_vars;
}
goto end;
}
*varname = name + 2;
- if (*name == 'g') { // global variable
+ if (*name == 'g') { // global variable
*d = &globvardict;
} else if (name_len > 2
&& (memchr(name + 2, ':', name_len - 2) != NULL
@@ -20269,10 +8683,10 @@ static hashtab_T *find_var_ht_dict(const char *name, const size_t name_len,
*d = curtab->tp_vars;
} else if (*name == 'v') { // v: variable
*d = &vimvardict;
- } else if (*name == 'a' && current_funccal != NULL) { // function argument
- *d = &get_funccal()->l_avars;
- } else if (*name == 'l' && current_funccal != NULL) { // local variable
- *d = &get_funccal()->l_vars;
+ } else if (*name == 'a' && funccal != NULL) { // function argument
+ *d = &funccal->l_avars;
+ } else if (*name == 'l' && funccal != NULL) { // local variable
+ *d = &funccal->l_vars;
} else if (*name == 's' // script variable
&& current_sctx.sc_sid > 0
&& current_sctx.sc_sid <= ga_scripts.ga_len) {
@@ -20291,8 +8705,8 @@ end:
/// prefix.
///
/// @return Scope hashtab, NULL if name is not valid.
-static hashtab_T *find_var_ht(const char *name, const size_t name_len,
- const char **varname)
+hashtab_T *find_var_ht(const char *name, const size_t name_len,
+ const char **varname)
{
dict_T *d;
return find_var_ht_dict(name, name_len, varname, &d);
@@ -20387,7 +8801,7 @@ void vars_clear(hashtab_T *ht)
/*
* Like vars_clear(), but only free the value if "free_val" is TRUE.
*/
-static void vars_clear_ext(hashtab_T *ht, int free_val)
+void vars_clear_ext(hashtab_T *ht, int free_val)
{
int todo;
hashitem_T *hi;
@@ -20489,8 +8903,8 @@ static void list_one_var_a(const char *prefix, const char *name,
/// @param[in] name_len Length of the variable name.
/// @param tv Variable value.
/// @param[in] copy True if value in tv is to be copied.
-static void set_var(const char *name, const size_t name_len, typval_T *const tv,
- const bool copy)
+void set_var(const char *name, const size_t name_len, typval_T *const tv,
+ const bool copy)
FUNC_ATTR_NONNULL_ALL
{
set_var_const(name, name_len, tv, copy, false);
@@ -20659,7 +9073,7 @@ bool var_check_ro(const int flags, const char *name,
{
const char *error_message = NULL;
if (flags & DI_FLAGS_RO) {
- error_message = N_(e_readonlyvar);
+ error_message = _(e_readonlyvar);
} else if ((flags & DI_FLAGS_RO_SBX) && sandbox) {
error_message = N_("E794: Cannot set variable in the sandbox: \"%.*s\"");
}
@@ -20697,8 +9111,8 @@ bool var_check_ro(const int flags, const char *name,
/// gettext.
///
/// @return True if variable is fixed, false otherwise.
-static bool var_check_fixed(const int flags, const char *name,
- size_t name_len)
+bool var_check_fixed(const int flags, const char *name,
+ size_t name_len)
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
{
if (flags & DI_FLAGS_FIX) {
@@ -20803,6 +9217,7 @@ int var_item_copy(const vimconv_T *const conv,
case VAR_FLOAT:
case VAR_FUNC:
case VAR_PARTIAL:
+ case VAR_BOOL:
case VAR_SPECIAL:
tv_copy(from, to);
break;
@@ -20840,10 +9255,10 @@ int var_item_copy(const vimconv_T *const conv,
case VAR_DICT:
to->v_type = VAR_DICT;
to->v_lock = 0;
- if (from->vval.v_dict == NULL)
+ if (from->vval.v_dict == NULL) {
to->vval.v_dict = NULL;
- else if (copyID != 0 && from->vval.v_dict->dv_copyID == copyID) {
- /* use the copy made earlier */
+ } else if (copyID != 0 && from->vval.v_dict->dv_copyID == copyID) {
+ // use the copy made earlier
to->vval.v_dict = from->vval.v_dict->dv_copydict;
++to->vval.v_dict->dv_refcount;
} else {
@@ -20871,6 +9286,7 @@ void ex_echo(exarg_T *eap)
char_u *arg = eap->arg;
typval_T rettv;
bool atstart = true;
+ bool need_clear = true;
const int did_emsg_before = did_emsg;
if (eap->skip)
@@ -20913,7 +9329,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);
+ msg_multiline_attr(tofree, echo_attr, true, &need_clear);
}
xfree(tofree);
}
@@ -20926,7 +9342,9 @@ void ex_echo(exarg_T *eap)
emsg_skip--;
} else {
// remove text that may still be there from the command
- msg_clr_eos();
+ if (need_clear) {
+ msg_clr_eos();
+ }
if (eap->cmdidx == CMD_echo) {
msg_end();
}
@@ -20938,13 +9356,7 @@ void ex_echo(exarg_T *eap)
*/
void ex_echohl(exarg_T *eap)
{
- int id;
-
- id = syn_name2id(eap->arg);
- if (id == 0)
- echo_attr = 0;
- else
- echo_attr = syn_id2attr(id);
+ echo_attr = syn_name2attr(eap->arg);
}
/*
@@ -20973,13 +9385,20 @@ void ex_execute(exarg_T *eap)
}
if (!eap->skip) {
- const char *const argstr = tv_get_string(&rettv);
+ 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);
const size_t len = strlen(argstr);
ga_grow(&ga, len + 2);
if (!GA_EMPTY(&ga)) {
((char_u *)(ga.ga_data))[ga.ga_len++] = ' ';
}
memcpy((char_u *)(ga.ga_data) + ga.ga_len, argstr, len + 1);
+ if (eap->cmdidx != CMD_execute) {
+ xfree((void *)argstr);
+ }
ga.ga_len += len;
}
@@ -21000,7 +9419,7 @@ void ex_execute(exarg_T *eap)
MSG_ATTR(ga.ga_data, echo_attr);
ui_flush();
} else if (eap->cmdidx == CMD_echoerr) {
- /* We don't want to abort following commands, restore did_emsg. */
+ // We don't want to abort following commands, restore did_emsg.
save_did_emsg = did_emsg;
msg_ext_set_kind("echoerr");
EMSG((char_u *)ga.ga_data);
@@ -21055,1057 +9474,8 @@ static const char *find_option_end(const char **const arg, int *const opt_flags)
return p;
}
-/*
- * ":function"
- */
-void ex_function(exarg_T *eap)
-{
- char_u *theline;
- char_u *line_to_free = NULL;
- int c;
- int saved_did_emsg;
- int saved_wait_return = need_wait_return;
- char_u *name = NULL;
- char_u *p;
- char_u *arg;
- char_u *line_arg = NULL;
- garray_T newargs;
- garray_T newlines;
- int varargs = false;
- int flags = 0;
- ufunc_T *fp;
- bool overwrite = false;
- int indent;
- int nesting;
- char_u *skip_until = NULL;
- dictitem_T *v;
- funcdict_T fudi;
- static int func_nr = 0; /* number for nameless function */
- int paren;
- hashtab_T *ht;
- int todo;
- hashitem_T *hi;
- int sourcing_lnum_off;
- bool show_block = false;
-
- /*
- * ":function" without argument: list functions.
- */
- if (ends_excmd(*eap->arg)) {
- if (!eap->skip) {
- todo = (int)func_hashtab.ht_used;
- for (hi = func_hashtab.ht_array; todo > 0 && !got_int; ++hi) {
- if (!HASHITEM_EMPTY(hi)) {
- --todo;
- fp = HI2UF(hi);
- if (message_filtered(fp->uf_name)) {
- continue;
- }
- if (!func_name_refcount(fp->uf_name)) {
- list_func_head(fp, false, false);
- }
- }
- }
- }
- eap->nextcmd = check_nextcmd(eap->arg);
- return;
- }
-
- /*
- * ":function /pat": list functions matching pattern.
- */
- if (*eap->arg == '/') {
- p = skip_regexp(eap->arg + 1, '/', TRUE, NULL);
- if (!eap->skip) {
- regmatch_T regmatch;
-
- c = *p;
- *p = NUL;
- regmatch.regprog = vim_regcomp(eap->arg + 1, RE_MAGIC);
- *p = c;
- if (regmatch.regprog != NULL) {
- regmatch.rm_ic = p_ic;
-
- todo = (int)func_hashtab.ht_used;
- for (hi = func_hashtab.ht_array; todo > 0 && !got_int; ++hi) {
- if (!HASHITEM_EMPTY(hi)) {
- --todo;
- fp = HI2UF(hi);
- if (!isdigit(*fp->uf_name)
- && vim_regexec(&regmatch, fp->uf_name, 0))
- list_func_head(fp, false, false);
- }
- }
- vim_regfree(regmatch.regprog);
- }
- }
- if (*p == '/')
- ++p;
- eap->nextcmd = check_nextcmd(p);
- return;
- }
-
- // Get the function name. There are these situations:
- // func function name
- // "name" == func, "fudi.fd_dict" == NULL
- // dict.func new dictionary entry
- // "name" == NULL, "fudi.fd_dict" set,
- // "fudi.fd_di" == NULL, "fudi.fd_newkey" == func
- // dict.func existing dict entry with a Funcref
- // "name" == func, "fudi.fd_dict" set,
- // "fudi.fd_di" set, "fudi.fd_newkey" == NULL
- // dict.func existing dict entry that's not a Funcref
- // "name" == NULL, "fudi.fd_dict" set,
- // "fudi.fd_di" set, "fudi.fd_newkey" == NULL
- // s:func script-local function name
- // g:func global function name, same as "func"
- p = eap->arg;
- name = trans_function_name(&p, eap->skip, TFN_NO_AUTOLOAD, &fudi, NULL);
- paren = (vim_strchr(p, '(') != NULL);
- if (name == NULL && (fudi.fd_dict == NULL || !paren) && !eap->skip) {
- /*
- * Return on an invalid expression in braces, unless the expression
- * evaluation has been cancelled due to an aborting error, an
- * interrupt, or an exception.
- */
- if (!aborting()) {
- if (fudi.fd_newkey != NULL) {
- EMSG2(_(e_dictkey), fudi.fd_newkey);
- }
- xfree(fudi.fd_newkey);
- return;
- } else
- eap->skip = TRUE;
- }
-
- /* An error in a function call during evaluation of an expression in magic
- * braces should not cause the function not to be defined. */
- saved_did_emsg = did_emsg;
- did_emsg = FALSE;
-
- //
- // ":function func" with only function name: list function.
- // If bang is given:
- // - include "!" in function head
- // - exclude line numbers from function body
- //
- if (!paren) {
- if (!ends_excmd(*skipwhite(p))) {
- EMSG(_(e_trailing));
- goto ret_free;
- }
- eap->nextcmd = check_nextcmd(p);
- if (eap->nextcmd != NULL)
- *p = NUL;
- if (!eap->skip && !got_int) {
- fp = find_func(name);
- if (fp != NULL) {
- list_func_head(fp, !eap->forceit, eap->forceit);
- for (int j = 0; j < fp->uf_lines.ga_len && !got_int; j++) {
- if (FUNCLINE(fp, j) == NULL) {
- continue;
- }
- msg_putchar('\n');
- if (!eap->forceit) {
- msg_outnum((long)j + 1);
- if (j < 9) {
- msg_putchar(' ');
- }
- if (j < 99) {
- msg_putchar(' ');
- }
- }
- msg_prt_line(FUNCLINE(fp, j), false);
- ui_flush(); // show a line at a time
- os_breakcheck();
- }
- if (!got_int) {
- msg_putchar('\n');
- msg_puts(eap->forceit ? "endfunction" : " endfunction");
- }
- } else
- emsg_funcname(N_("E123: Undefined function: %s"), name);
- }
- goto ret_free;
- }
-
- /*
- * ":function name(arg1, arg2)" Define function.
- */
- p = skipwhite(p);
- if (*p != '(') {
- if (!eap->skip) {
- EMSG2(_("E124: Missing '(': %s"), eap->arg);
- goto ret_free;
- }
- /* attempt to continue by skipping some text */
- if (vim_strchr(p, '(') != NULL)
- p = vim_strchr(p, '(');
- }
- p = skipwhite(p + 1);
-
- ga_init(&newargs, (int)sizeof(char_u *), 3);
- ga_init(&newlines, (int)sizeof(char_u *), 3);
-
- if (!eap->skip) {
- /* Check the name of the function. Unless it's a dictionary function
- * (that we are overwriting). */
- if (name != NULL)
- arg = name;
- else
- arg = fudi.fd_newkey;
- if (arg != NULL && (fudi.fd_di == NULL || !tv_is_func(fudi.fd_di->di_tv))) {
- int j = (*arg == K_SPECIAL) ? 3 : 0;
- while (arg[j] != NUL && (j == 0 ? eval_isnamec1(arg[j])
- : eval_isnamec(arg[j])))
- ++j;
- if (arg[j] != NUL)
- emsg_funcname((char *)e_invarg2, arg);
- }
- /* Disallow using the g: dict. */
- if (fudi.fd_dict != NULL && fudi.fd_dict->dv_scope == VAR_DEF_SCOPE)
- EMSG(_("E862: Cannot use g: here"));
- }
-
- if (get_function_args(&p, ')', &newargs, &varargs, eap->skip) == FAIL) {
- goto errret_2;
- }
-
- if (KeyTyped && ui_has(kUICmdline)) {
- show_block = true;
- ui_ext_cmdline_block_append(0, (const char *)eap->cmd);
- }
-
- // find extra arguments "range", "dict", "abort" and "closure"
- for (;; ) {
- p = skipwhite(p);
- if (STRNCMP(p, "range", 5) == 0) {
- flags |= FC_RANGE;
- p += 5;
- } else if (STRNCMP(p, "dict", 4) == 0) {
- flags |= FC_DICT;
- p += 4;
- } else if (STRNCMP(p, "abort", 5) == 0) {
- flags |= FC_ABORT;
- p += 5;
- } else if (STRNCMP(p, "closure", 7) == 0) {
- flags |= FC_CLOSURE;
- p += 7;
- if (current_funccal == NULL) {
- emsg_funcname(N_
- ("E932: Closure function should not be at top level: %s"),
- name == NULL ? (char_u *)"" : name);
- goto erret;
- }
- } else {
- break;
- }
- }
-
- /* When there is a line break use what follows for the function body.
- * Makes 'exe "func Test()\n...\nendfunc"' work. */
- if (*p == '\n') {
- line_arg = p + 1;
- } else if (*p != NUL && *p != '"' && !eap->skip && !did_emsg) {
- EMSG(_(e_trailing));
- }
-
- /*
- * Read the body of the function, until ":endfunction" is found.
- */
- if (KeyTyped) {
- /* Check if the function already exists, don't let the user type the
- * whole function before telling him it doesn't work! For a script we
- * need to skip the body to be able to find what follows. */
- if (!eap->skip && !eap->forceit) {
- if (fudi.fd_dict != NULL && fudi.fd_newkey == NULL)
- EMSG(_(e_funcdict));
- else if (name != NULL && find_func(name) != NULL)
- emsg_funcname(e_funcexts, name);
- }
-
- if (!eap->skip && did_emsg)
- goto erret;
-
- if (!ui_has(kUICmdline)) {
- msg_putchar('\n'); // don't overwrite the function name
- }
- cmdline_row = msg_row;
- }
-
- indent = 2;
- nesting = 0;
- for (;; ) {
- if (KeyTyped) {
- msg_scroll = TRUE;
- saved_wait_return = FALSE;
- }
- need_wait_return = FALSE;
- sourcing_lnum_off = sourcing_lnum;
-
- if (line_arg != NULL) {
- /* Use eap->arg, split up in parts by line breaks. */
- theline = line_arg;
- p = vim_strchr(theline, '\n');
- if (p == NULL)
- line_arg += STRLEN(line_arg);
- else {
- *p = NUL;
- line_arg = p + 1;
- }
- } else {
- xfree(line_to_free);
- if (eap->getline == NULL) {
- theline = getcmdline(':', 0L, indent);
- } else {
- theline = eap->getline(':', eap->cookie, indent);
- }
- line_to_free = theline;
- }
- if (KeyTyped) {
- lines_left = Rows - 1;
- }
- if (theline == NULL) {
- EMSG(_("E126: Missing :endfunction"));
- goto erret;
- }
- if (show_block) {
- assert(indent >= 0);
- ui_ext_cmdline_block_append((size_t)indent, (const char *)theline);
- }
-
- /* Detect line continuation: sourcing_lnum increased more than one. */
- if (sourcing_lnum > sourcing_lnum_off + 1)
- sourcing_lnum_off = sourcing_lnum - sourcing_lnum_off - 1;
- else
- sourcing_lnum_off = 0;
-
- if (skip_until != NULL) {
- /* between ":append" and "." and between ":python <<EOF" and "EOF"
- * don't check for ":endfunc". */
- if (STRCMP(theline, skip_until) == 0) {
- XFREE_CLEAR(skip_until);
- }
- } else {
- /* skip ':' and blanks*/
- for (p = theline; ascii_iswhite(*p) || *p == ':'; ++p)
- ;
-
- /* Check for "endfunction". */
- if (checkforcmd(&p, "endfunction", 4) && nesting-- == 0) {
- if (*p == '!') {
- p++;
- }
- char_u *nextcmd = NULL;
- if (*p == '|') {
- nextcmd = p + 1;
- } else if (line_arg != NULL && *skipwhite(line_arg) != NUL) {
- nextcmd = line_arg;
- } else if (*p != NUL && *p != '"' && p_verbose > 0) {
- give_warning2((char_u *)_("W22: Text found after :endfunction: %s"),
- p, true);
- }
- if (nextcmd != NULL) {
- // Another command follows. If the line came from "eap" we
- // can simply point into it, otherwise we need to change
- // "eap->cmdlinep".
- eap->nextcmd = nextcmd;
- if (line_to_free != NULL) {
- xfree(*eap->cmdlinep);
- *eap->cmdlinep = line_to_free;
- line_to_free = NULL;
- }
- }
- break;
- }
-
- /* Increase indent inside "if", "while", "for" and "try", decrease
- * at "end". */
- if (indent > 2 && STRNCMP(p, "end", 3) == 0)
- indent -= 2;
- else if (STRNCMP(p, "if", 2) == 0
- || STRNCMP(p, "wh", 2) == 0
- || STRNCMP(p, "for", 3) == 0
- || STRNCMP(p, "try", 3) == 0)
- indent += 2;
-
- /* Check for defining a function inside this function. */
- if (checkforcmd(&p, "function", 2)) {
- if (*p == '!') {
- p = skipwhite(p + 1);
- }
- p += eval_fname_script((const char *)p);
- xfree(trans_function_name(&p, true, 0, NULL, NULL));
- if (*skipwhite(p) == '(') {
- nesting++;
- indent += 2;
- }
- }
-
- // Check for ":append", ":change", ":insert".
- p = skip_range(p, NULL);
- if ((p[0] == 'a' && (!ASCII_ISALPHA(p[1]) || p[1] == 'p'))
- || (p[0] == 'c'
- && (!ASCII_ISALPHA(p[1])
- || (p[1] == 'h' && (!ASCII_ISALPHA(p[2])
- || (p[2] == 'a'
- && (STRNCMP(&p[3], "nge", 3) != 0
- || !ASCII_ISALPHA(p[6])))))))
- || (p[0] == 'i'
- && (!ASCII_ISALPHA(p[1]) || (p[1] == 'n'
- && (!ASCII_ISALPHA(p[2])
- || (p[2] == 's')))))) {
- skip_until = vim_strsave((char_u *)".");
- }
-
- // heredoc: Check for ":python <<EOF", ":lua <<EOF", etc.
- arg = skipwhite(skiptowhite(p));
- if (arg[0] == '<' && arg[1] =='<'
- && ((p[0] == 'p' && p[1] == 'y'
- && (!ASCII_ISALNUM(p[2]) || p[2] == 't'
- || ((p[2] == '3' || p[2] == 'x')
- && !ASCII_ISALPHA(p[3]))))
- || (p[0] == 'p' && p[1] == 'e'
- && (!ASCII_ISALPHA(p[2]) || p[2] == 'r'))
- || (p[0] == 't' && p[1] == 'c'
- && (!ASCII_ISALPHA(p[2]) || p[2] == 'l'))
- || (p[0] == 'l' && p[1] == 'u' && p[2] == 'a'
- && !ASCII_ISALPHA(p[3]))
- || (p[0] == 'r' && p[1] == 'u' && p[2] == 'b'
- && (!ASCII_ISALPHA(p[3]) || p[3] == 'y'))
- || (p[0] == 'm' && p[1] == 'z'
- && (!ASCII_ISALPHA(p[2]) || p[2] == 's'))
- )) {
- /* ":python <<" continues until a dot, like ":append" */
- p = skipwhite(arg + 2);
- if (*p == NUL)
- skip_until = vim_strsave((char_u *)".");
- else
- skip_until = vim_strsave(p);
- }
- }
-
- /* Add the line to the function. */
- ga_grow(&newlines, 1 + sourcing_lnum_off);
-
- /* Copy the line to newly allocated memory. get_one_sourceline()
- * allocates 250 bytes per line, this saves 80% on average. The cost
- * is an extra alloc/free. */
- p = vim_strsave(theline);
- ((char_u **)(newlines.ga_data))[newlines.ga_len++] = p;
-
- /* Add NULL lines for continuation lines, so that the line count is
- * equal to the index in the growarray. */
- while (sourcing_lnum_off-- > 0)
- ((char_u **)(newlines.ga_data))[newlines.ga_len++] = NULL;
-
- /* Check for end of eap->arg. */
- if (line_arg != NULL && *line_arg == NUL)
- line_arg = NULL;
- }
-
- /* Don't define the function when skipping commands or when an error was
- * detected. */
- if (eap->skip || did_emsg)
- goto erret;
-
- /*
- * If there are no errors, add the function
- */
- if (fudi.fd_dict == NULL) {
- v = find_var((const char *)name, STRLEN(name), &ht, false);
- if (v != NULL && v->di_tv.v_type == VAR_FUNC) {
- emsg_funcname(N_("E707: Function name conflicts with variable: %s"),
- name);
- goto erret;
- }
-
- fp = find_func(name);
- if (fp != NULL) {
- // Function can be replaced with "function!" and when sourcing the
- // same script again, but only once.
- if (!eap->forceit
- && (fp->uf_script_ctx.sc_sid != current_sctx.sc_sid
- || fp->uf_script_ctx.sc_seq == current_sctx.sc_seq)) {
- emsg_funcname(e_funcexts, name);
- goto erret;
- }
- if (fp->uf_calls > 0) {
- emsg_funcname(N_("E127: Cannot redefine function %s: It is in use"),
- name);
- goto erret;
- }
- if (fp->uf_refcount > 1) {
- // This function is referenced somewhere, don't redefine it but
- // create a new one.
- (fp->uf_refcount)--;
- fp->uf_flags |= FC_REMOVED;
- fp = NULL;
- overwrite = true;
- } else {
- // redefine existing function
- XFREE_CLEAR(name);
- func_clear_items(fp);
- fp->uf_profiling = false;
- fp->uf_prof_initialized = false;
- }
- }
- } else {
- char numbuf[20];
-
- fp = NULL;
- if (fudi.fd_newkey == NULL && !eap->forceit) {
- EMSG(_(e_funcdict));
- goto erret;
- }
- if (fudi.fd_di == NULL) {
- if (tv_check_lock(fudi.fd_dict->dv_lock, (const char *)eap->arg,
- TV_CSTRING)) {
- // Can't add a function to a locked dictionary
- goto erret;
- }
- } else if (tv_check_lock(fudi.fd_di->di_tv.v_lock, (const char *)eap->arg,
- TV_CSTRING)) {
- // Can't change an existing function if it is locked
- goto erret;
- }
-
- /* Give the function a sequential number. Can only be used with a
- * Funcref! */
- xfree(name);
- sprintf(numbuf, "%d", ++func_nr);
- name = vim_strsave((char_u *)numbuf);
- }
-
- if (fp == NULL) {
- if (fudi.fd_dict == NULL && vim_strchr(name, AUTOLOAD_CHAR) != NULL) {
- int slen, plen;
- char_u *scriptname;
-
- /* Check that the autoload name matches the script name. */
- int j = FAIL;
- if (sourcing_name != NULL) {
- scriptname = (char_u *)autoload_name((const char *)name, STRLEN(name));
- p = vim_strchr(scriptname, '/');
- plen = (int)STRLEN(p);
- slen = (int)STRLEN(sourcing_name);
- if (slen > plen && fnamecmp(p,
- sourcing_name + slen - plen) == 0)
- j = OK;
- xfree(scriptname);
- }
- if (j == FAIL) {
- EMSG2(_(
- "E746: Function name does not match script file name: %s"),
- name);
- goto erret;
- }
- }
-
- fp = xcalloc(1, offsetof(ufunc_T, uf_name) + STRLEN(name) + 1);
-
- if (fudi.fd_dict != NULL) {
- if (fudi.fd_di == NULL) {
- // Add new dict entry
- fudi.fd_di = tv_dict_item_alloc((const char *)fudi.fd_newkey);
- if (tv_dict_add(fudi.fd_dict, fudi.fd_di) == FAIL) {
- xfree(fudi.fd_di);
- xfree(fp);
- goto erret;
- }
- } else {
- // Overwrite existing dict entry.
- tv_clear(&fudi.fd_di->di_tv);
- }
- fudi.fd_di->di_tv.v_type = VAR_FUNC;
- fudi.fd_di->di_tv.vval.v_string = vim_strsave(name);
-
- /* behave like "dict" was used */
- flags |= FC_DICT;
- }
-
- /* insert the new function in the function list */
- STRCPY(fp->uf_name, name);
- if (overwrite) {
- hi = hash_find(&func_hashtab, name);
- hi->hi_key = UF2HIKEY(fp);
- } else if (hash_add(&func_hashtab, UF2HIKEY(fp)) == FAIL) {
- xfree(fp);
- goto erret;
- }
- fp->uf_refcount = 1;
- }
- fp->uf_args = newargs;
- fp->uf_lines = newlines;
- if ((flags & FC_CLOSURE) != 0) {
- register_closure(fp);
- } else {
- fp->uf_scoped = NULL;
- }
- if (prof_def_func()) {
- func_do_profile(fp);
- }
- fp->uf_varargs = varargs;
- if (sandbox) {
- flags |= FC_SANDBOX;
- }
- fp->uf_flags = flags;
- fp->uf_calls = 0;
- fp->uf_script_ctx = current_sctx;
- fp->uf_script_ctx.sc_lnum += sourcing_lnum - newlines.ga_len - 1;
- goto ret_free;
-
-erret:
- ga_clear_strings(&newargs);
-errret_2:
- ga_clear_strings(&newlines);
-ret_free:
- xfree(skip_until);
- xfree(line_to_free);
- xfree(fudi.fd_newkey);
- xfree(name);
- did_emsg |= saved_did_emsg;
- need_wait_return |= saved_wait_return;
- if (show_block) {
- ui_ext_cmdline_block_leave();
- }
-}
-
-/// Get a function name, translating "<SID>" and "<SNR>".
-/// Also handles a Funcref in a List or Dictionary.
-/// flags:
-/// TFN_INT: internal function name OK
-/// TFN_QUIET: be quiet
-/// TFN_NO_AUTOLOAD: do not use script autoloading
-/// TFN_NO_DEREF: do not dereference a Funcref
-/// Advances "pp" to just after the function name (if no error).
-///
-/// @return the function name in allocated memory, or NULL for failure.
-static char_u *
-trans_function_name(
- char_u **pp,
- int skip, // only find the end, don't evaluate
- int flags,
- funcdict_T *fdp, // return: info about dictionary used
- partial_T **partial // return: partial of a FuncRef
-)
-{
- char_u *name = NULL;
- const char_u *start;
- const char_u *end;
- int lead;
- int len;
- lval_T lv;
-
- if (fdp != NULL)
- memset(fdp, 0, sizeof(funcdict_T));
- start = *pp;
-
- /* Check for hard coded <SNR>: already translated function ID (from a user
- * command). */
- if ((*pp)[0] == K_SPECIAL && (*pp)[1] == KS_EXTRA
- && (*pp)[2] == (int)KE_SNR) {
- *pp += 3;
- len = get_id_len((const char **)pp) + 3;
- return (char_u *)xmemdupz(start, len);
- }
-
- /* A name starting with "<SID>" or "<SNR>" is local to a script. But
- * don't skip over "s:", get_lval() needs it for "s:dict.func". */
- lead = eval_fname_script((const char *)start);
- if (lead > 2) {
- start += lead;
- }
-
- // Note that TFN_ flags use the same values as GLV_ flags.
- end = get_lval((char_u *)start, NULL, &lv, false, skip, flags | GLV_READ_ONLY,
- lead > 2 ? 0 : FNE_CHECK_START);
- if (end == start) {
- if (!skip)
- EMSG(_("E129: Function name required"));
- goto theend;
- }
- if (end == NULL || (lv.ll_tv != NULL && (lead > 2 || lv.ll_range))) {
- /*
- * Report an invalid expression in braces, unless the expression
- * evaluation has been cancelled due to an aborting error, an
- * interrupt, or an exception.
- */
- if (!aborting()) {
- if (end != NULL) {
- emsgf(_(e_invarg2), start);
- }
- } else {
- *pp = (char_u *)find_name_end(start, NULL, NULL, FNE_INCL_BR);
- }
- goto theend;
- }
-
- if (lv.ll_tv != NULL) {
- if (fdp != NULL) {
- fdp->fd_dict = lv.ll_dict;
- fdp->fd_newkey = lv.ll_newkey;
- lv.ll_newkey = NULL;
- fdp->fd_di = lv.ll_di;
- }
- if (lv.ll_tv->v_type == VAR_FUNC && lv.ll_tv->vval.v_string != NULL) {
- name = vim_strsave(lv.ll_tv->vval.v_string);
- *pp = (char_u *)end;
- } else if (lv.ll_tv->v_type == VAR_PARTIAL
- && lv.ll_tv->vval.v_partial != NULL) {
- name = vim_strsave(partial_name(lv.ll_tv->vval.v_partial));
- *pp = (char_u *)end;
- if (partial != NULL) {
- *partial = lv.ll_tv->vval.v_partial;
- }
- } else {
- if (!skip && !(flags & TFN_QUIET) && (fdp == NULL
- || lv.ll_dict == NULL
- || fdp->fd_newkey == NULL)) {
- EMSG(_(e_funcref));
- } else {
- *pp = (char_u *)end;
- }
- name = NULL;
- }
- goto theend;
- }
-
- if (lv.ll_name == NULL) {
- // Error found, but continue after the function name.
- *pp = (char_u *)end;
- goto theend;
- }
-
- /* Check if the name is a Funcref. If so, use the value. */
- if (lv.ll_exp_name != NULL) {
- len = (int)strlen(lv.ll_exp_name);
- name = deref_func_name(lv.ll_exp_name, &len, partial,
- flags & TFN_NO_AUTOLOAD);
- if ((const char *)name == lv.ll_exp_name) {
- name = NULL;
- }
- } else if (!(flags & TFN_NO_DEREF)) {
- len = (int)(end - *pp);
- name = deref_func_name((const char *)(*pp), &len, partial,
- flags & TFN_NO_AUTOLOAD);
- if (name == *pp) {
- name = NULL;
- }
- }
- if (name != NULL) {
- name = vim_strsave(name);
- *pp = (char_u *)end;
- if (strncmp((char *)name, "<SNR>", 5) == 0) {
- // Change "<SNR>" to the byte sequence.
- name[0] = K_SPECIAL;
- name[1] = KS_EXTRA;
- name[2] = (int)KE_SNR;
- memmove(name + 3, name + 5, strlen((char *)name + 5) + 1);
- }
- goto theend;
- }
-
- if (lv.ll_exp_name != NULL) {
- len = (int)strlen(lv.ll_exp_name);
- if (lead <= 2 && lv.ll_name == lv.ll_exp_name
- && lv.ll_name_len >= 2 && memcmp(lv.ll_name, "s:", 2) == 0) {
- // When there was "s:" already or the name expanded to get a
- // leading "s:" then remove it.
- lv.ll_name += 2;
- lv.ll_name_len -= 2;
- len -= 2;
- lead = 2;
- }
- } else {
- // Skip over "s:" and "g:".
- if (lead == 2 || (lv.ll_name[0] == 'g' && lv.ll_name[1] == ':')) {
- lv.ll_name += 2;
- lv.ll_name_len -= 2;
- }
- len = (int)((const char *)end - lv.ll_name);
- }
-
- size_t sid_buf_len = 0;
- char sid_buf[20];
-
- // Copy the function name to allocated memory.
- // Accept <SID>name() inside a script, translate into <SNR>123_name().
- // Accept <SNR>123_name() outside a script.
- if (skip) {
- lead = 0; // do nothing
- } else if (lead > 0) {
- lead = 3;
- if ((lv.ll_exp_name != NULL && eval_fname_sid(lv.ll_exp_name))
- || eval_fname_sid((const char *)(*pp))) {
- // It's "s:" or "<SID>".
- if (current_sctx.sc_sid <= 0) {
- EMSG(_(e_usingsid));
- goto theend;
- }
- sid_buf_len = snprintf(sid_buf, sizeof(sid_buf),
- "%" PRIdSCID "_", current_sctx.sc_sid);
- lead += sid_buf_len;
- }
- } else if (!(flags & TFN_INT)
- && builtin_function(lv.ll_name, lv.ll_name_len)) {
- EMSG2(_("E128: Function name must start with a capital or \"s:\": %s"),
- start);
- goto theend;
- }
-
- if (!skip && !(flags & TFN_QUIET) && !(flags & TFN_NO_DEREF)) {
- char_u *cp = xmemrchr(lv.ll_name, ':', lv.ll_name_len);
-
- if (cp != NULL && cp < end) {
- EMSG2(_("E884: Function name cannot contain a colon: %s"), start);
- goto theend;
- }
- }
-
- name = xmalloc(len + lead + 1);
- if (lead > 0){
- name[0] = K_SPECIAL;
- name[1] = KS_EXTRA;
- name[2] = (int)KE_SNR;
- if (sid_buf_len > 0) { // If it's "<SID>"
- memcpy(name + 3, sid_buf, sid_buf_len);
- }
- }
- memmove(name + lead, lv.ll_name, len);
- name[lead + len] = NUL;
- *pp = (char_u *)end;
-
-theend:
- clear_lval(&lv);
- return name;
-}
-
-/*
- * Return 5 if "p" starts with "<SID>" or "<SNR>" (ignoring case).
- * Return 2 if "p" starts with "s:".
- * Return 0 otherwise.
- */
-static int eval_fname_script(const char *const p)
-{
- // Use mb_strnicmp() because in Turkish comparing the "I" may not work with
- // the standard library function.
- if (p[0] == '<'
- && (mb_strnicmp((char_u *)p + 1, (char_u *)"SID>", 4) == 0
- || mb_strnicmp((char_u *)p + 1, (char_u *)"SNR>", 4) == 0)) {
- return 5;
- }
- if (p[0] == 's' && p[1] == ':') {
- return 2;
- }
- return 0;
-}
-
-/// Check whether function name starts with <SID> or s:
-///
-/// @warning Only works for names previously checked by eval_fname_script(), if
-/// it returned non-zero.
-///
-/// @param[in] name Name to check.
-///
-/// @return true if it starts with <SID> or s:, false otherwise.
-static inline bool eval_fname_sid(const char *const name)
- FUNC_ATTR_PURE FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_WARN_UNUSED_RESULT
- FUNC_ATTR_NONNULL_ALL
-{
- return *name == 's' || TOUPPER_ASC(name[2]) == 'I';
-}
-
-/// List the head of the function: "name(arg1, arg2)".
-///
-/// @param[in] fp Function pointer.
-/// @param[in] indent Indent line.
-/// @param[in] force Include bang "!" (i.e.: "function!").
-static void list_func_head(ufunc_T *fp, int indent, bool force)
-{
- msg_start();
- if (indent)
- MSG_PUTS(" ");
- MSG_PUTS(force ? "function! " : "function ");
- if (fp->uf_name[0] == K_SPECIAL) {
- MSG_PUTS_ATTR("<SNR>", HL_ATTR(HLF_8));
- msg_puts((const char *)fp->uf_name + 3);
- } else {
- msg_puts((const char *)fp->uf_name);
- }
- msg_putchar('(');
- int j;
- for (j = 0; j < fp->uf_args.ga_len; j++) {
- if (j) {
- msg_puts(", ");
- }
- msg_puts((const char *)FUNCARG(fp, j));
- }
- if (fp->uf_varargs) {
- if (j) {
- msg_puts(", ");
- }
- msg_puts("...");
- }
- msg_putchar(')');
- if (fp->uf_flags & FC_ABORT) {
- msg_puts(" abort");
- }
- if (fp->uf_flags & FC_RANGE) {
- msg_puts(" range");
- }
- if (fp->uf_flags & FC_DICT) {
- msg_puts(" dict");
- }
- if (fp->uf_flags & FC_CLOSURE) {
- msg_puts(" closure");
- }
- msg_clr_eos();
- if (p_verbose > 0) {
- last_set_msg(fp->uf_script_ctx);
- }
-}
-
-/// Find a function by name, return pointer to it in ufuncs.
-/// @return NULL for unknown function.
-static ufunc_T *find_func(const char_u *name)
-{
- hashitem_T *hi;
-
- hi = hash_find(&func_hashtab, name);
- if (!HASHITEM_EMPTY(hi))
- return HI2UF(hi);
- return NULL;
-}
-
-#if defined(EXITFREE)
-void free_all_functions(void)
-{
- hashitem_T *hi;
- ufunc_T *fp;
- uint64_t skipped = 0;
- uint64_t todo = 1;
- uint64_t used;
-
- // Clean up the call stack.
- while (current_funccal != NULL) {
- tv_clear(current_funccal->rettv);
- cleanup_function_call(current_funccal);
- }
-
- // First clear what the functions contain. Since this may lower the
- // reference count of a function, it may also free a function and change
- // the hash table. Restart if that happens.
- while (todo > 0) {
- todo = func_hashtab.ht_used;
- for (hi = func_hashtab.ht_array; todo > 0; hi++) {
- if (!HASHITEM_EMPTY(hi)) {
- // Only free functions that are not refcounted, those are
- // supposed to be freed when no longer referenced.
- fp = HI2UF(hi);
- if (func_name_refcount(fp->uf_name)) {
- skipped++;
- } else {
- used = func_hashtab.ht_used;
- func_clear(fp, true);
- if (used != func_hashtab.ht_used) {
- skipped = 0;
- break;
- }
- }
- todo--;
- }
- }
- }
-
- // Now actually free the functions. Need to start all over every time,
- // because func_free() may change the hash table.
- skipped = 0;
- while (func_hashtab.ht_used > skipped) {
- todo = func_hashtab.ht_used;
- for (hi = func_hashtab.ht_array; todo > 0; hi++) {
- if (!HASHITEM_EMPTY(hi)) {
- todo--;
- // Only free functions that are not refcounted, those are
- // supposed to be freed when no longer referenced.
- fp = HI2UF(hi);
- if (func_name_refcount(fp->uf_name)) {
- skipped++;
- } else {
- func_free(fp);
- skipped = 0;
- break;
- }
- }
- }
- }
- if (skipped == 0) {
- hash_clear(&func_hashtab);
- }
-}
-
-#endif
-
-bool translated_function_exists(const char *name)
-{
- if (builtin_function(name, -1)) {
- return find_internal_func((char *)name) != NULL;
- }
- return find_func((const char_u *)name) != NULL;
-}
-
-/// Check whether function with the given name exists
-///
-/// @param[in] name Function name.
-/// @param[in] no_deref Whether to dereference a Funcref.
-///
-/// @return True if it exists, false otherwise.
-static bool function_exists(const char *const name, bool no_deref)
-{
- const char_u *nm = (const char_u *)name;
- bool n = false;
- int flag = TFN_INT | TFN_QUIET | TFN_NO_AUTOLOAD;
-
- if (no_deref) {
- flag |= TFN_NO_DEREF;
- }
- char *const p = (char *)trans_function_name((char_u **)&nm, false, flag, NULL,
- NULL);
- nm = skipwhite(nm);
-
- /* Only accept "funcname", "funcname ", "funcname (..." and
- * "funcname(...", not "funcname!...". */
- if (p != NULL && (*nm == NUL || *nm == '(')) {
- n = translated_function_exists(p);
- }
- xfree(p);
- return n;
-}
-
-/// Checks if a builtin function with the given name exists.
-///
-/// @param[in] name name of the builtin function to check.
-/// @param[in] len length of "name", or -1 for NUL terminated.
-///
-/// @return true if "name" looks like a builtin function name: starts with a
-/// lower case letter and doesn't contain AUTOLOAD_CHAR.
-static bool builtin_function(const char *name, int len)
-{
- if (!ASCII_ISLOWER(name[0])) {
- return false;
- }
-
- const char *p = (len == -1
- ? strchr(name, AUTOLOAD_CHAR)
- : memchr(name, AUTOLOAD_CHAR, (size_t)len));
-
- return p == NULL;
-}
-
-/*
- * Start profiling function "fp".
- */
-static void func_do_profile(ufunc_T *fp)
+/// Start profiling function "fp".
+void func_do_profile(ufunc_T *fp)
{
int len = fp->uf_lines.ga_len;
@@ -22148,8 +9518,9 @@ void func_dump_profile(FILE *fd)
int st_len = 0;
todo = (int)func_hashtab.ht_used;
- if (todo == 0)
- return; /* nothing to dump */
+ if (todo == 0) {
+ return; // nothing to dump
+ }
sorttab = xmalloc(sizeof(ufunc_T *) * todo);
@@ -22172,7 +9543,7 @@ void func_dump_profile(FILE *fd)
.channel_id = 0,
};
char_u *p = get_scriptname(last_set, &should_free);
- fprintf(fd, " Defined: %s line %" PRIdLINENR "\n",
+ fprintf(fd, " Defined: %s:%" PRIdLINENR "\n",
p, fp->uf_script_ctx.sc_lnum);
if (should_free) {
xfree(p);
@@ -22218,7 +9589,7 @@ prof_sort_list(
ufunc_T **sorttab,
int st_len,
char *title,
- int prefer_self /* when equal print only self time */
+ int prefer_self // when equal print only self time
)
{
int i;
@@ -22246,8 +9617,8 @@ static void prof_func_line(
int count,
proftime_T *total,
proftime_T *self,
- int prefer_self /* when equal print only self time */
- )
+ int prefer_self // when equal print only self time
+)
{
if (count > 0) {
fprintf(fd, "%5d ", count);
@@ -22283,6 +9654,33 @@ static int prof_self_cmp(const void *s1, const void *s2)
return profile_cmp(p1->uf_tm_self, p2->uf_tm_self);
}
+/// Return the autoload script name for a function or variable name
+/// Caller must make sure that "name" contains AUTOLOAD_CHAR.
+///
+/// @param[in] name Variable/function name.
+/// @param[in] name_len Name length.
+///
+/// @return [allocated] autoload script name.
+char *autoload_name(const char *const name, const size_t name_len)
+ FUNC_ATTR_MALLOC FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ // Get the script file name: replace '#' with '/', append ".vim".
+ char *const scriptname = xmalloc(name_len + sizeof("autoload/.vim"));
+ memcpy(scriptname, "autoload/", sizeof("autoload/") - 1);
+ memcpy(scriptname + sizeof("autoload/") - 1, name, name_len);
+ size_t auchar_idx = 0;
+ for (size_t i = sizeof("autoload/") - 1;
+ i - sizeof("autoload/") + 1 < name_len;
+ i++) {
+ if (scriptname[i] == AUTOLOAD_CHAR) {
+ scriptname[i] = '/';
+ auchar_idx = i;
+ }
+ }
+ memcpy(scriptname + auchar_idx, ".vim", sizeof(".vim"));
+
+ return scriptname;
+}
/// If name has a package name try autoloading the script for it
///
@@ -22291,8 +9689,8 @@ static int prof_self_cmp(const void *s1, const void *s2)
/// @param[in] reload If true, load script again when already loaded.
///
/// @return true if a package was loaded.
-static bool script_autoload(const char *const name, const size_t name_len,
- const bool reload)
+bool script_autoload(const char *const name, const size_t name_len,
+ const bool reload)
{
// If there is no '#' after name[0] there is no package name.
const char *p = memchr(name, AUTOLOAD_CHAR, name_len);
@@ -22331,1027 +9729,6 @@ static bool script_autoload(const char *const name, const size_t name_len,
return ret;
}
-/// Return the autoload script name for a function or variable name
-/// Caller must make sure that "name" contains AUTOLOAD_CHAR.
-///
-/// @param[in] name Variable/function name.
-/// @param[in] name_len Name length.
-///
-/// @return [allocated] autoload script name.
-static char *autoload_name(const char *const name, const size_t name_len)
- FUNC_ATTR_MALLOC FUNC_ATTR_WARN_UNUSED_RESULT
-{
- // Get the script file name: replace '#' with '/', append ".vim".
- char *const scriptname = xmalloc(name_len + sizeof("autoload/.vim"));
- memcpy(scriptname, "autoload/", sizeof("autoload/") - 1);
- memcpy(scriptname + sizeof("autoload/") - 1, name, name_len);
- size_t auchar_idx = 0;
- for (size_t i = sizeof("autoload/") - 1;
- i - sizeof("autoload/") + 1 < name_len;
- i++) {
- if (scriptname[i] == AUTOLOAD_CHAR) {
- scriptname[i] = '/';
- auchar_idx = i;
- }
- }
- memcpy(scriptname + auchar_idx, ".vim", sizeof(".vim"));
-
- return scriptname;
-}
-
-
-/*
- * Function given to ExpandGeneric() to obtain the list of user defined
- * function names.
- */
-char_u *get_user_func_name(expand_T *xp, int idx)
-{
- static size_t done;
- static hashitem_T *hi;
- ufunc_T *fp;
-
- if (idx == 0) {
- done = 0;
- hi = func_hashtab.ht_array;
- }
- assert(hi);
- if (done < func_hashtab.ht_used) {
- if (done++ > 0)
- ++hi;
- while (HASHITEM_EMPTY(hi))
- ++hi;
- fp = HI2UF(hi);
-
- if ((fp->uf_flags & FC_DICT)
- || STRNCMP(fp->uf_name, "<lambda>", 8) == 0) {
- return (char_u *)""; // don't show dict and lambda functions
- }
-
- if (STRLEN(fp->uf_name) + 4 >= IOSIZE) {
- return fp->uf_name; // Prevent overflow.
- }
-
- cat_func_name(IObuff, fp);
- if (xp->xp_context != EXPAND_USER_FUNC) {
- STRCAT(IObuff, "(");
- if (!fp->uf_varargs && GA_EMPTY(&fp->uf_args))
- STRCAT(IObuff, ")");
- }
- return IObuff;
- }
- return NULL;
-}
-
-
-/*
- * Copy the function name of "fp" to buffer "buf".
- * "buf" must be able to hold the function name plus three bytes.
- * Takes care of script-local function names.
- */
-static void cat_func_name(char_u *buf, ufunc_T *fp)
-{
- if (fp->uf_name[0] == K_SPECIAL) {
- STRCPY(buf, "<SNR>");
- STRCAT(buf, fp->uf_name + 3);
- } else
- STRCPY(buf, fp->uf_name);
-}
-
-/// There are two kinds of function names:
-/// 1. ordinary names, function defined with :function
-/// 2. numbered functions and lambdas
-/// For the first we only count the name stored in func_hashtab as a reference,
-/// using function() does not count as a reference, because the function is
-/// looked up by name.
-static bool func_name_refcount(char_u *name)
-{
- return isdigit(*name) || *name == '<';
-}
-
-/// ":delfunction {name}"
-void ex_delfunction(exarg_T *eap)
-{
- ufunc_T *fp = NULL;
- char_u *p;
- char_u *name;
- funcdict_T fudi;
-
- p = eap->arg;
- name = trans_function_name(&p, eap->skip, 0, &fudi, NULL);
- xfree(fudi.fd_newkey);
- if (name == NULL) {
- if (fudi.fd_dict != NULL && !eap->skip)
- EMSG(_(e_funcref));
- return;
- }
- if (!ends_excmd(*skipwhite(p))) {
- xfree(name);
- EMSG(_(e_trailing));
- return;
- }
- eap->nextcmd = check_nextcmd(p);
- if (eap->nextcmd != NULL)
- *p = NUL;
-
- if (!eap->skip)
- fp = find_func(name);
- xfree(name);
-
- if (!eap->skip) {
- if (fp == NULL) {
- if (!eap->forceit) {
- EMSG2(_(e_nofunc), eap->arg);
- }
- return;
- }
- if (fp->uf_calls > 0) {
- EMSG2(_("E131: Cannot delete function %s: It is in use"), eap->arg);
- return;
- }
- // check `uf_refcount > 2` because deleting a function should also reduce
- // the reference count, and 1 is the initial refcount.
- if (fp->uf_refcount > 2) {
- EMSG2(_("Cannot delete function %s: It is being used internally"),
- eap->arg);
- return;
- }
-
- if (fudi.fd_dict != NULL) {
- // Delete the dict item that refers to the function, it will
- // invoke func_unref() and possibly delete the function.
- tv_dict_item_remove(fudi.fd_dict, fudi.fd_di);
- } else {
- // A normal function (not a numbered function or lambda) has a
- // refcount of 1 for the entry in the hashtable. When deleting
- // it and the refcount is more than one, it should be kept.
- // A numbered function or lambda should be kept if the refcount is
- // one or more.
- if (fp->uf_refcount > (func_name_refcount(fp->uf_name) ? 0 : 1)) {
- // Function is still referenced somewhere. Don't free it but
- // do remove it from the hashtable.
- if (func_remove(fp)) {
- fp->uf_refcount--;
- }
- fp->uf_flags |= FC_DELETED;
- } else {
- func_clear_free(fp, false);
- }
- }
- }
-}
-
-/// Remove the function from the function hashtable. If the function was
-/// deleted while it still has references this was already done.
-///
-/// @return true if the entry was deleted, false if it wasn't found.
-static bool func_remove(ufunc_T *fp)
-{
- hashitem_T *hi = hash_find(&func_hashtab, UF2HIKEY(fp));
-
- if (!HASHITEM_EMPTY(hi)) {
- hash_remove(&func_hashtab, hi);
- return true;
- }
-
- return false;
-}
-
-static void func_clear_items(ufunc_T *fp)
-{
- ga_clear_strings(&(fp->uf_args));
- ga_clear_strings(&(fp->uf_lines));
-
- XFREE_CLEAR(fp->uf_tml_count);
- XFREE_CLEAR(fp->uf_tml_total);
- XFREE_CLEAR(fp->uf_tml_self);
-}
-
-/// Free all things that a function contains. Does not free the function
-/// itself, use func_free() for that.
-///
-/// param[in] force When true, we are exiting.
-static void func_clear(ufunc_T *fp, bool force)
-{
- if (fp->uf_cleared) {
- return;
- }
- fp->uf_cleared = true;
-
- // clear this function
- func_clear_items(fp);
- funccal_unref(fp->uf_scoped, fp, force);
-}
-
-/// Free a function and remove it from the list of functions. Does not free
-/// what a function contains, call func_clear() first.
-///
-/// param[in] fp The function to free.
-static void func_free(ufunc_T *fp)
-{
- // only remove it when not done already, otherwise we would remove a newer
- // version of the function
- if ((fp->uf_flags & (FC_DELETED | FC_REMOVED)) == 0) {
- func_remove(fp);
- }
- xfree(fp);
-}
-
-/// Free all things that a function contains and free the function itself.
-///
-/// param[in] force When true, we are exiting.
-static void func_clear_free(ufunc_T *fp, bool force)
-{
- func_clear(fp, force);
- func_free(fp);
-}
-
-/*
- * Unreference a Function: decrement the reference count and free it when it
- * becomes zero.
- */
-void func_unref(char_u *name)
-{
- ufunc_T *fp = NULL;
-
- if (name == NULL || !func_name_refcount(name)) {
- return;
- }
-
- fp = find_func(name);
- if (fp == NULL && isdigit(*name)) {
-#ifdef EXITFREE
- if (!entered_free_all_mem) {
- internal_error("func_unref()");
- abort();
- }
-#else
- internal_error("func_unref()");
- abort();
-#endif
- }
- func_ptr_unref(fp);
-}
-
-/// Unreference a Function: decrement the reference count and free it when it
-/// becomes zero.
-/// Unreference user function, freeing it if needed
-///
-/// Decrements the reference count and frees when it becomes zero.
-///
-/// @param fp Function to unreference.
-void func_ptr_unref(ufunc_T *fp)
-{
- if (fp != NULL && --fp->uf_refcount <= 0) {
- // Only delete it when it's not being used. Otherwise it's done
- // when "uf_calls" becomes zero.
- if (fp->uf_calls == 0) {
- func_clear_free(fp, false);
- }
- }
-}
-
-/// Count a reference to a Function.
-void func_ref(char_u *name)
-{
- ufunc_T *fp;
-
- if (name == NULL || !func_name_refcount(name)) {
- return;
- }
- fp = find_func(name);
- if (fp != NULL) {
- (fp->uf_refcount)++;
- } else if (isdigit(*name)) {
- // Only give an error for a numbered function.
- // Fail silently, when named or lambda function isn't found.
- internal_error("func_ref()");
- }
-}
-
-/// Count a reference to a Function.
-void func_ptr_ref(ufunc_T *fp)
-{
- if (fp != NULL) {
- (fp->uf_refcount)++;
- }
-}
-
-/// Check whether funccall is still referenced outside
-///
-/// It is supposed to be referenced if either it is referenced itself or if l:,
-/// a: or a:000 are referenced as all these are statically allocated within
-/// funccall structure.
-static inline bool fc_referenced(const funccall_T *const fc)
- FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
- FUNC_ATTR_NONNULL_ALL
-{
- return ((fc->l_varlist.lv_refcount // NOLINT(runtime/deprecated)
- != DO_NOT_FREE_CNT)
- || fc->l_vars.dv_refcount != DO_NOT_FREE_CNT
- || fc->l_avars.dv_refcount != DO_NOT_FREE_CNT
- || fc->fc_refcount > 0);
-}
-
-/// Call a user function
-///
-/// @param fp Function to call.
-/// @param[in] argcount Number of arguments.
-/// @param argvars Arguments.
-/// @param[out] rettv Return value.
-/// @param[in] firstline First line of range.
-/// @param[in] lastline Last line of range.
-/// @param selfdict Dictionary for "self" for dictionary functions.
-void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars,
- typval_T *rettv, linenr_T firstline, linenr_T lastline,
- dict_T *selfdict)
- FUNC_ATTR_NONNULL_ARG(1, 3, 4)
-{
- char_u *save_sourcing_name;
- linenr_T save_sourcing_lnum;
- bool using_sandbox = false;
- funccall_T *fc;
- int save_did_emsg;
- static int depth = 0;
- dictitem_T *v;
- int fixvar_idx = 0; /* index in fixvar[] */
- int ai;
- bool islambda = false;
- char_u numbuf[NUMBUFLEN];
- char_u *name;
- proftime_T wait_start;
- proftime_T call_start;
- int started_profiling = false;
- bool did_save_redo = false;
- save_redo_T save_redo;
-
- /* If depth of calling is getting too high, don't execute the function */
- if (depth >= p_mfd) {
- EMSG(_("E132: Function call depth is higher than 'maxfuncdepth'"));
- rettv->v_type = VAR_NUMBER;
- rettv->vval.v_number = -1;
- return;
- }
- ++depth;
- // Save search patterns and redo buffer.
- save_search_patterns();
- if (!ins_compl_active()) {
- saveRedobuff(&save_redo);
- did_save_redo = true;
- }
- ++fp->uf_calls;
- // check for CTRL-C hit
- line_breakcheck();
- // prepare the funccall_T structure
- fc = xmalloc(sizeof(funccall_T));
- fc->caller = current_funccal;
- current_funccal = fc;
- fc->func = fp;
- fc->rettv = rettv;
- rettv->vval.v_number = 0;
- fc->linenr = 0;
- fc->returned = FALSE;
- fc->level = ex_nesting_level;
- /* Check if this function has a breakpoint. */
- fc->breakpoint = dbg_find_breakpoint(FALSE, fp->uf_name, (linenr_T)0);
- fc->dbg_tick = debug_tick;
-
- // Set up fields for closure.
- fc->fc_refcount = 0;
- fc->fc_copyID = 0;
- ga_init(&fc->fc_funcs, sizeof(ufunc_T *), 1);
- func_ptr_ref(fp);
-
- if (STRNCMP(fp->uf_name, "<lambda>", 8) == 0) {
- islambda = true;
- }
-
- // Note about using fc->fixvar[]: This is an array of FIXVAR_CNT variables
- // with names up to VAR_SHORT_LEN long. This avoids having to alloc/free
- // each argument variable and saves a lot of time.
- //
- // Init l: variables.
- init_var_dict(&fc->l_vars, &fc->l_vars_var, VAR_DEF_SCOPE);
- if (selfdict != NULL) {
- // Set l:self to "selfdict". Use "name" to avoid a warning from
- // some compiler that checks the destination size.
- v = (dictitem_T *)&fc->fixvar[fixvar_idx++];
-#ifndef __clang_analyzer__
- name = v->di_key;
- STRCPY(name, "self");
-#endif
- v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX;
- tv_dict_add(&fc->l_vars, v);
- v->di_tv.v_type = VAR_DICT;
- v->di_tv.v_lock = 0;
- v->di_tv.vval.v_dict = selfdict;
- ++selfdict->dv_refcount;
- }
-
- /*
- * Init a: variables.
- * Set a:0 to "argcount".
- * Set a:000 to a list with room for the "..." arguments.
- */
- init_var_dict(&fc->l_avars, &fc->l_avars_var, VAR_SCOPE);
- add_nr_var(&fc->l_avars, (dictitem_T *)&fc->fixvar[fixvar_idx++], "0",
- (varnumber_T)(argcount - fp->uf_args.ga_len));
- fc->l_avars.dv_lock = VAR_FIXED;
- // Use "name" to avoid a warning from some compiler that checks the
- // destination size.
- v = (dictitem_T *)&fc->fixvar[fixvar_idx++];
-#ifndef __clang_analyzer__
- name = v->di_key;
- STRCPY(name, "000");
-#endif
- v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX;
- tv_dict_add(&fc->l_avars, v);
- v->di_tv.v_type = VAR_LIST;
- v->di_tv.v_lock = VAR_FIXED;
- v->di_tv.vval.v_list = &fc->l_varlist;
- tv_list_init_static(&fc->l_varlist);
- tv_list_set_lock(&fc->l_varlist, VAR_FIXED);
-
- // Set a:firstline to "firstline" and a:lastline to "lastline".
- // Set a:name to named arguments.
- // Set a:N to the "..." arguments.
- add_nr_var(&fc->l_avars, (dictitem_T *)&fc->fixvar[fixvar_idx++],
- "firstline", (varnumber_T)firstline);
- add_nr_var(&fc->l_avars, (dictitem_T *)&fc->fixvar[fixvar_idx++],
- "lastline", (varnumber_T)lastline);
- for (int i = 0; i < argcount; i++) {
- bool addlocal = false;
-
- ai = i - fp->uf_args.ga_len;
- if (ai < 0) {
- // named argument a:name
- name = FUNCARG(fp, i);
- if (islambda) {
- addlocal = true;
- }
- } else {
- // "..." argument a:1, a:2, etc.
- snprintf((char *)numbuf, sizeof(numbuf), "%d", ai + 1);
- name = numbuf;
- }
- if (fixvar_idx < FIXVAR_CNT && STRLEN(name) <= VAR_SHORT_LEN) {
- v = (dictitem_T *)&fc->fixvar[fixvar_idx++];
- v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX;
- } else {
- v = xmalloc(sizeof(dictitem_T) + STRLEN(name));
- v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX | DI_FLAGS_ALLOC;
- }
- STRCPY(v->di_key, name);
-
- // Note: the values are copied directly to avoid alloc/free.
- // "argvars" must have VAR_FIXED for v_lock.
- v->di_tv = argvars[i];
- v->di_tv.v_lock = VAR_FIXED;
-
- if (addlocal) {
- // Named arguments can be accessed without the "a:" prefix in lambda
- // expressions. Add to the l: dict.
- tv_copy(&v->di_tv, &v->di_tv);
- tv_dict_add(&fc->l_vars, v);
- } else {
- tv_dict_add(&fc->l_avars, v);
- }
-
- if (ai >= 0 && ai < MAX_FUNC_ARGS) {
- tv_list_append(&fc->l_varlist, &fc->l_listitems[ai]);
- *TV_LIST_ITEM_TV(&fc->l_listitems[ai]) = argvars[i];
- TV_LIST_ITEM_TV(&fc->l_listitems[ai])->v_lock = VAR_FIXED;
- }
- }
-
- /* Don't redraw while executing the function. */
- ++RedrawingDisabled;
- save_sourcing_name = sourcing_name;
- save_sourcing_lnum = sourcing_lnum;
- sourcing_lnum = 1;
-
- if (fp->uf_flags & FC_SANDBOX) {
- using_sandbox = true;
- sandbox++;
- }
-
- // need space for new sourcing_name:
- // * save_sourcing_name
- // * "["number"].." or "function "
- // * "<SNR>" + fp->uf_name - 3
- // * terminating NUL
- size_t len = (save_sourcing_name == NULL ? 0 : STRLEN(save_sourcing_name))
- + STRLEN(fp->uf_name) + 27;
- sourcing_name = xmalloc(len);
- {
- if (save_sourcing_name != NULL
- && STRNCMP(save_sourcing_name, "function ", 9) == 0) {
- vim_snprintf((char *)sourcing_name,
- len,
- "%s[%" PRId64 "]..",
- save_sourcing_name,
- (int64_t)save_sourcing_lnum);
- } else {
- STRCPY(sourcing_name, "function ");
- }
- cat_func_name(sourcing_name + STRLEN(sourcing_name), fp);
-
- if (p_verbose >= 12) {
- ++no_wait_return;
- verbose_enter_scroll();
-
- smsg(_("calling %s"), sourcing_name);
- if (p_verbose >= 14) {
- msg_puts("(");
- for (int i = 0; i < argcount; i++) {
- if (i > 0) {
- msg_puts(", ");
- }
- if (argvars[i].v_type == VAR_NUMBER) {
- msg_outnum((long)argvars[i].vval.v_number);
- } else {
- // Do not want errors such as E724 here.
- emsg_off++;
- char *tofree = encode_tv2string(&argvars[i], NULL);
- emsg_off--;
- if (tofree != NULL) {
- char *s = tofree;
- char buf[MSG_BUF_LEN];
- if (vim_strsize((char_u *)s) > MSG_BUF_CLEN) {
- trunc_string((char_u *)s, (char_u *)buf, MSG_BUF_CLEN,
- sizeof(buf));
- s = buf;
- }
- msg_puts(s);
- xfree(tofree);
- }
- }
- }
- msg_puts(")");
- }
- msg_puts("\n"); // don't overwrite this either
-
- verbose_leave_scroll();
- --no_wait_return;
- }
- }
-
- const bool do_profiling_yes = do_profiling == PROF_YES;
-
- bool func_not_yet_profiling_but_should =
- do_profiling_yes
- && !fp->uf_profiling && has_profiling(false, fp->uf_name, NULL);
-
- if (func_not_yet_profiling_but_should) {
- started_profiling = true;
- func_do_profile(fp);
- }
-
- bool func_or_func_caller_profiling =
- do_profiling_yes
- && (fp->uf_profiling
- || (fc->caller != NULL && fc->caller->func->uf_profiling));
-
- if (func_or_func_caller_profiling) {
- ++fp->uf_tm_count;
- call_start = profile_start();
- fp->uf_tm_children = profile_zero();
- }
-
- if (do_profiling_yes) {
- script_prof_save(&wait_start);
- }
-
- const sctx_T save_current_sctx = current_sctx;
- current_sctx = fp->uf_script_ctx;
- save_did_emsg = did_emsg;
- did_emsg = FALSE;
-
- /* call do_cmdline() to execute the lines */
- do_cmdline(NULL, get_func_line, (void *)fc,
- DOCMD_NOWAIT|DOCMD_VERBOSE|DOCMD_REPEAT);
-
- --RedrawingDisabled;
-
- // when the function was aborted because of an error, return -1
- if ((did_emsg
- && (fp->uf_flags & FC_ABORT)) || rettv->v_type == VAR_UNKNOWN) {
- tv_clear(rettv);
- rettv->v_type = VAR_NUMBER;
- rettv->vval.v_number = -1;
- }
-
- if (func_or_func_caller_profiling) {
- call_start = profile_end(call_start);
- call_start = profile_sub_wait(wait_start, call_start); // -V614
- fp->uf_tm_total = profile_add(fp->uf_tm_total, call_start);
- fp->uf_tm_self = profile_self(fp->uf_tm_self, call_start,
- fp->uf_tm_children);
- if (fc->caller != NULL && fc->caller->func->uf_profiling) {
- fc->caller->func->uf_tm_children =
- profile_add(fc->caller->func->uf_tm_children, call_start);
- fc->caller->func->uf_tml_children =
- profile_add(fc->caller->func->uf_tml_children, call_start);
- }
- if (started_profiling) {
- // make a ":profdel func" stop profiling the function
- fp->uf_profiling = false;
- }
- }
-
- /* when being verbose, mention the return value */
- if (p_verbose >= 12) {
- ++no_wait_return;
- verbose_enter_scroll();
-
- if (aborting())
- smsg(_("%s aborted"), sourcing_name);
- else if (fc->rettv->v_type == VAR_NUMBER)
- smsg(_("%s returning #%" PRId64 ""),
- sourcing_name, (int64_t)fc->rettv->vval.v_number);
- else {
- char_u buf[MSG_BUF_LEN];
-
- // The value may be very long. Skip the middle part, so that we
- // have some idea how it starts and ends. smsg() would always
- // truncate it at the end. Don't want errors such as E724 here.
- emsg_off++;
- char_u *s = (char_u *) encode_tv2string(fc->rettv, NULL);
- char_u *tofree = s;
- emsg_off--;
- if (s != NULL) {
- if (vim_strsize(s) > MSG_BUF_CLEN) {
- trunc_string(s, buf, MSG_BUF_CLEN, MSG_BUF_LEN);
- s = buf;
- }
- smsg(_("%s returning %s"), sourcing_name, s);
- xfree(tofree);
- }
- }
- msg_puts("\n"); // don't overwrite this either
-
- verbose_leave_scroll();
- --no_wait_return;
- }
-
- xfree(sourcing_name);
- sourcing_name = save_sourcing_name;
- sourcing_lnum = save_sourcing_lnum;
- current_sctx = save_current_sctx;
- if (do_profiling_yes) {
- script_prof_restore(&wait_start);
- }
- if (using_sandbox) {
- sandbox--;
- }
-
- if (p_verbose >= 12 && sourcing_name != NULL) {
- ++no_wait_return;
- verbose_enter_scroll();
-
- smsg(_("continuing in %s"), sourcing_name);
- msg_puts("\n"); // don't overwrite this either
-
- verbose_leave_scroll();
- --no_wait_return;
- }
-
- did_emsg |= save_did_emsg;
- depth--;
-
- cleanup_function_call(fc);
-
- if (--fp->uf_calls <= 0 && fp->uf_refcount <= 0) {
- // Function was unreferenced while being used, free it now.
- func_clear_free(fp, false);
- }
- // restore search patterns and redo buffer
- if (did_save_redo) {
- restoreRedobuff(&save_redo);
- }
- restore_search_patterns();
-}
-
-/// Unreference "fc": decrement the reference count and free it when it
-/// becomes zero. "fp" is detached from "fc".
-///
-/// @param[in] force When true, we are exiting.
-static void funccal_unref(funccall_T *fc, ufunc_T *fp, bool force)
-{
- funccall_T **pfc;
- int i;
-
- if (fc == NULL) {
- return;
- }
-
- fc->fc_refcount--;
- if (force ? fc->fc_refcount <= 0 : !fc_referenced(fc)) {
- for (pfc = &previous_funccal; *pfc != NULL; pfc = &(*pfc)->caller) {
- if (fc == *pfc) {
- *pfc = fc->caller;
- free_funccal(fc, true);
- return;
- }
- }
- }
- for (i = 0; i < fc->fc_funcs.ga_len; i++) {
- if (((ufunc_T **)(fc->fc_funcs.ga_data))[i] == fp) {
- ((ufunc_T **)(fc->fc_funcs.ga_data))[i] = NULL;
- }
- }
-}
-
-/// @return true if items in "fc" do not have "copyID". That means they are not
-/// referenced from anywhere that is in use.
-static int can_free_funccal(funccall_T *fc, int copyID)
-{
- return fc->l_varlist.lv_copyID != copyID
- && fc->l_vars.dv_copyID != copyID
- && fc->l_avars.dv_copyID != copyID
- && fc->fc_copyID != copyID;
-}
-
-/*
- * Free "fc" and what it contains.
- */
-static void
-free_funccal(
- funccall_T *fc,
- int free_val /* a: vars were allocated */
-)
-{
- for (int i = 0; i < fc->fc_funcs.ga_len; i++) {
- ufunc_T *fp = ((ufunc_T **)(fc->fc_funcs.ga_data))[i];
-
- // When garbage collecting a funccall_T may be freed before the
- // function that references it, clear its uf_scoped field.
- // The function may have been redefined and point to another
- // funccal_T, don't clear it then.
- if (fp != NULL && fp->uf_scoped == fc) {
- fp->uf_scoped = NULL;
- }
- }
- ga_clear(&fc->fc_funcs);
-
- // The a: variables typevals may not have been allocated, only free the
- // allocated variables.
- vars_clear_ext(&fc->l_avars.dv_hashtab, free_val);
-
- // Free all l: variables.
- vars_clear(&fc->l_vars.dv_hashtab);
-
- // Free the a:000 variables if they were allocated.
- if (free_val) {
- TV_LIST_ITER(&fc->l_varlist, li, {
- tv_clear(TV_LIST_ITEM_TV(li));
- });
- }
-
- func_ptr_unref(fc->func);
- xfree(fc);
-}
-
-/// Handle the last part of returning from a function: free the local hashtable.
-/// Unless it is still in use by a closure.
-static void cleanup_function_call(funccall_T *fc)
-{
- current_funccal = fc->caller;
-
- // If the a:000 list and the l: and a: dicts are not referenced and there
- // is no closure using it, we can free the funccall_T and what's in it.
- if (!fc_referenced(fc)) {
- free_funccal(fc, false);
- } else {
- // "fc" is still in use. This can happen when returning "a:000",
- // assigning "l:" to a global variable or defining a closure.
- // Link "fc" in the list for garbage collection later.
- fc->caller = previous_funccal;
- previous_funccal = fc;
-
- // Make a copy of the a: variables, since we didn't do that above.
- TV_DICT_ITER(&fc->l_avars, di, {
- tv_copy(&di->di_tv, &di->di_tv);
- });
-
- // Make a copy of the a:000 items, since we didn't do that above.
- TV_LIST_ITER(&fc->l_varlist, li, {
- tv_copy(TV_LIST_ITEM_TV(li), TV_LIST_ITEM_TV(li));
- });
- }
-}
-
-/*
- * Add a number variable "name" to dict "dp" with value "nr".
- */
-static void add_nr_var(dict_T *dp, dictitem_T *v, char *name, varnumber_T nr)
-{
-#ifndef __clang_analyzer__
- STRCPY(v->di_key, name);
-#endif
- v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX;
- tv_dict_add(dp, v);
- v->di_tv.v_type = VAR_NUMBER;
- v->di_tv.v_lock = VAR_FIXED;
- v->di_tv.vval.v_number = nr;
-}
-
-/*
- * ":return [expr]"
- */
-void ex_return(exarg_T *eap)
-{
- char_u *arg = eap->arg;
- typval_T rettv;
- int returning = FALSE;
-
- if (current_funccal == NULL) {
- EMSG(_("E133: :return not inside a function"));
- return;
- }
-
- if (eap->skip)
- ++emsg_skip;
-
- eap->nextcmd = NULL;
- if ((*arg != NUL && *arg != '|' && *arg != '\n')
- && eval0(arg, &rettv, &eap->nextcmd, !eap->skip) != FAIL) {
- if (!eap->skip) {
- returning = do_return(eap, false, true, &rettv);
- } else {
- tv_clear(&rettv);
- }
- } else if (!eap->skip) { // It's safer to return also on error.
- // In return statement, cause_abort should be force_abort.
- update_force_abort();
-
- // Return unless the expression evaluation has been cancelled due to an
- // aborting error, an interrupt, or an exception.
- if (!aborting()) {
- returning = do_return(eap, false, true, NULL);
- }
- }
-
- /* When skipping or the return gets pending, advance to the next command
- * in this line (!returning). Otherwise, ignore the rest of the line.
- * Following lines will be ignored by get_func_line(). */
- if (returning)
- eap->nextcmd = NULL;
- else if (eap->nextcmd == NULL) /* no argument */
- eap->nextcmd = check_nextcmd(arg);
-
- if (eap->skip)
- --emsg_skip;
-}
-
-/*
- * Return from a function. Possibly makes the return pending. Also called
- * for a pending return at the ":endtry" or after returning from an extra
- * do_cmdline(). "reanimate" is used in the latter case. "is_cmd" is set
- * when called due to a ":return" command. "rettv" may point to a typval_T
- * with the return rettv. Returns TRUE when the return can be carried out,
- * FALSE when the return gets pending.
- */
-int do_return(exarg_T *eap, int reanimate, int is_cmd, void *rettv)
-{
- int idx;
- struct condstack *cstack = eap->cstack;
-
- if (reanimate)
- /* Undo the return. */
- current_funccal->returned = FALSE;
-
- /*
- * Cleanup (and inactivate) conditionals, but stop when a try conditional
- * not in its finally clause (which then is to be executed next) is found.
- * In this case, make the ":return" pending for execution at the ":endtry".
- * Otherwise, return normally.
- */
- idx = cleanup_conditionals(eap->cstack, 0, TRUE);
- if (idx >= 0) {
- cstack->cs_pending[idx] = CSTP_RETURN;
-
- if (!is_cmd && !reanimate)
- /* A pending return again gets pending. "rettv" points to an
- * allocated variable with the rettv of the original ":return"'s
- * argument if present or is NULL else. */
- cstack->cs_rettv[idx] = rettv;
- else {
- /* When undoing a return in order to make it pending, get the stored
- * return rettv. */
- if (reanimate) {
- assert(current_funccal->rettv);
- rettv = current_funccal->rettv;
- }
-
- if (rettv != NULL) {
- /* Store the value of the pending return. */
- cstack->cs_rettv[idx] = xcalloc(1, sizeof(typval_T));
- *(typval_T *)cstack->cs_rettv[idx] = *(typval_T *)rettv;
- } else
- cstack->cs_rettv[idx] = NULL;
-
- if (reanimate) {
- /* The pending return value could be overwritten by a ":return"
- * without argument in a finally clause; reset the default
- * return value. */
- current_funccal->rettv->v_type = VAR_NUMBER;
- current_funccal->rettv->vval.v_number = 0;
- }
- }
- report_make_pending(CSTP_RETURN, rettv);
- } else {
- current_funccal->returned = TRUE;
-
- /* If the return is carried out now, store the return value. For
- * a return immediately after reanimation, the value is already
- * there. */
- if (!reanimate && rettv != NULL) {
- tv_clear(current_funccal->rettv);
- *current_funccal->rettv = *(typval_T *)rettv;
- if (!is_cmd)
- xfree(rettv);
- }
- }
-
- return idx < 0;
-}
-
-/*
- * Generate a return command for producing the value of "rettv". The result
- * is an allocated string. Used by report_pending() for verbose messages.
- */
-char_u *get_return_cmd(void *rettv)
-{
- char_u *s = NULL;
- char_u *tofree = NULL;
-
- if (rettv != NULL) {
- tofree = s = (char_u *) encode_tv2echo((typval_T *) rettv, NULL);
- }
- if (s == NULL) {
- s = (char_u *)"";
- }
-
- STRCPY(IObuff, ":return ");
- STRLCPY(IObuff + 8, s, IOSIZE - 8);
- if (STRLEN(s) + 8 >= IOSIZE)
- STRCPY(IObuff + IOSIZE - 4, "...");
- xfree(tofree);
- return vim_strsave(IObuff);
-}
-
-/*
- * Get next function line.
- * Called by do_cmdline() to get the next line.
- * Returns allocated string, or NULL for end of function.
- */
-char_u *get_func_line(int c, void *cookie, int indent)
-{
- funccall_T *fcp = (funccall_T *)cookie;
- ufunc_T *fp = fcp->func;
- char_u *retval;
- garray_T *gap; /* growarray with function lines */
-
- /* If breakpoints have been added/deleted need to check for it. */
- if (fcp->dbg_tick != debug_tick) {
- fcp->breakpoint = dbg_find_breakpoint(FALSE, fp->uf_name,
- sourcing_lnum);
- fcp->dbg_tick = debug_tick;
- }
- if (do_profiling == PROF_YES)
- func_line_end(cookie);
-
- gap = &fp->uf_lines;
- if (((fp->uf_flags & FC_ABORT) && did_emsg && !aborted_in_try())
- || fcp->returned)
- retval = NULL;
- else {
- /* Skip NULL lines (continuation lines). */
- while (fcp->linenr < gap->ga_len
- && ((char_u **)(gap->ga_data))[fcp->linenr] == NULL)
- ++fcp->linenr;
- if (fcp->linenr >= gap->ga_len)
- retval = NULL;
- else {
- retval = vim_strsave(((char_u **)(gap->ga_data))[fcp->linenr++]);
- sourcing_lnum = fcp->linenr;
- if (do_profiling == PROF_YES)
- func_line_start(cookie);
- }
- }
-
- /* Did we encounter a breakpoint? */
- if (fcp->breakpoint != 0 && fcp->breakpoint <= sourcing_lnum) {
- dbg_breakpoint(fp->uf_name, sourcing_lnum);
- /* Find next breakpoint. */
- fcp->breakpoint = dbg_find_breakpoint(FALSE, fp->uf_name,
- sourcing_lnum);
- fcp->dbg_tick = debug_tick;
- }
-
- return retval;
-}
-
/*
* Called when starting to read a function line.
* "sourcing_lnum" must be correct!
@@ -23366,10 +9743,11 @@ void func_line_start(void *cookie)
if (fp->uf_profiling && sourcing_lnum >= 1
&& sourcing_lnum <= fp->uf_lines.ga_len) {
fp->uf_tml_idx = sourcing_lnum - 1;
- /* Skip continuation lines. */
- while (fp->uf_tml_idx > 0 && FUNCLINE(fp, fp->uf_tml_idx) == NULL)
- --fp->uf_tml_idx;
- fp->uf_tml_execed = FALSE;
+ // Skip continuation lines.
+ while (fp->uf_tml_idx > 0 && FUNCLINE(fp, fp->uf_tml_idx) == NULL) {
+ fp->uf_tml_idx--;
+ }
+ fp->uf_tml_execed = false;
fp->uf_tml_start = profile_start();
fp->uf_tml_children = profile_zero();
fp->uf_tml_wait = profile_get_wait();
@@ -23411,28 +9789,6 @@ void func_line_end(void *cookie)
}
}
-/*
- * Return TRUE if the currently active function should be ended, because a
- * return was encountered or an error occurred. Used inside a ":while".
- */
-int func_has_ended(void *cookie)
-{
- funccall_T *fcp = (funccall_T *)cookie;
-
- /* Ignore the "abort" flag if the abortion behavior has been changed due to
- * an error inside a try conditional. */
- return ((fcp->func->uf_flags & FC_ABORT) && did_emsg && !aborted_in_try())
- || fcp->returned;
-}
-
-/*
- * return TRUE if cookie indicates a function which "abort"s on errors.
- */
-int func_has_abort(void *cookie)
-{
- return ((funccall_T *)cookie)->func->uf_flags & FC_ABORT;
-}
-
static var_flavour_T var_flavour(char_u *varname)
{
char_u *p = varname;
@@ -23448,72 +9804,6 @@ static var_flavour_T var_flavour(char_u *varname)
}
}
-/// Search hashitem in parent scope.
-hashitem_T *find_hi_in_scoped_ht(const char *name, hashtab_T **pht)
-{
- if (current_funccal == NULL || current_funccal->func->uf_scoped == NULL) {
- return NULL;
- }
-
- funccall_T *old_current_funccal = current_funccal;
- hashitem_T *hi = NULL;
- const size_t namelen = strlen(name);
- const char *varname;
-
- // Search in parent scope which is possible to reference from lambda
- current_funccal = current_funccal->func->uf_scoped;
- while (current_funccal != NULL) {
- hashtab_T *ht = find_var_ht(name, namelen, &varname);
- if (ht != NULL && *varname != NUL) {
- hi = hash_find_len(ht, varname, namelen - (varname - name));
- if (!HASHITEM_EMPTY(hi)) {
- *pht = ht;
- break;
- }
- }
- if (current_funccal == current_funccal->func->uf_scoped) {
- break;
- }
- current_funccal = current_funccal->func->uf_scoped;
- }
- current_funccal = old_current_funccal;
-
- return hi;
-}
-
-/// Search variable in parent scope.
-dictitem_T *find_var_in_scoped_ht(const char *name, const size_t namelen,
- int no_autoload)
-{
- if (current_funccal == NULL || current_funccal->func->uf_scoped == NULL) {
- return NULL;
- }
-
- dictitem_T *v = NULL;
- funccall_T *old_current_funccal = current_funccal;
- const char *varname;
-
- // Search in parent scope which is possible to reference from lambda
- current_funccal = current_funccal->func->uf_scoped;
- while (current_funccal) {
- hashtab_T *ht = find_var_ht(name, namelen, &varname);
- if (ht != NULL && *varname != NUL) {
- v = find_var_in_ht(ht, *name, varname,
- namelen - (size_t)(varname - name), no_autoload);
- if (v != NULL) {
- break;
- }
- }
- if (current_funccal == current_funccal->func->uf_scoped) {
- break;
- }
- current_funccal = current_funccal->func->uf_scoped;
- }
- current_funccal = old_current_funccal;
-
- return v;
-}
-
/// Iterate over global variables
///
/// @warning No modifications to global variable dictionary must be performed
@@ -23558,10 +9848,11 @@ const void *var_shada_iter(const void *const iter, const char **const name,
void var_set_global(const char *const name, typval_T vartv)
{
- funccall_T *const saved_current_funccal = current_funccal;
- current_funccal = NULL;
+ funccal_entry_T funccall_entry;
+
+ save_funccal(&funccall_entry);
set_var(name, strlen(name), &vartv, false);
- current_funccal = saved_current_funccal;
+ restore_funccal();
}
int store_session_globals(FILE *fd)
@@ -23637,7 +9928,7 @@ void option_last_set_msg(LastSet last_set)
MSG_PUTS(_("\n\tLast set from "));
MSG_PUTS(p);
if (last_set.script_ctx.sc_lnum > 0) {
- MSG_PUTS(_(" line "));
+ MSG_PUTS(_(line_msg));
msg_outnum((long)last_set.script_ctx.sc_lnum);
}
if (should_free) {
@@ -23680,14 +9971,14 @@ modify_fname(
int has_fullname = 0;
repeat:
- /* ":p" - full path/file_name */
+ // ":p" - full path/file_name
if (src[*usedlen] == ':' && src[*usedlen + 1] == 'p') {
has_fullname = 1;
valid |= VALID_PATH;
*usedlen += 2;
- /* Expand "~/path" for all systems and "~user/path" for Unix */
+ // Expand "~/path" for all systems and "~user/path" for Unix
if ((*fnamep)[0] == '~'
#if !defined(UNIX)
&& ((*fnamep)[1] == '/'
@@ -23699,7 +9990,7 @@ repeat:
&& !(tilde_file && (*fnamep)[1] == NUL)
) {
*fnamep = expand_env_save(*fnamep);
- xfree(*bufp); /* free any allocated file name */
+ xfree(*bufp); // free any allocated file name
*bufp = *fnamep;
if (*fnamep == NULL)
return -1;
@@ -23717,20 +10008,20 @@ repeat:
}
}
- /* FullName_save() is slow, don't use it when not needed. */
+ // FullName_save() is slow, don't use it when not needed.
if (*p != NUL || !vim_isAbsName(*fnamep)) {
- *fnamep = (char_u *)FullName_save((char *)*fnamep, *p != NUL);
- xfree(*bufp); /* free any allocated file name */
+ *fnamep = (char_u *)FullName_save((char *)(*fnamep), *p != NUL);
+ xfree(*bufp); // free any allocated file name
*bufp = *fnamep;
if (*fnamep == NULL)
return -1;
}
- /* Append a path separator to a directory. */
+ // Append a path separator to a directory.
if (os_isdir(*fnamep)) {
- /* Make room for one or two extra characters. */
+ // Make room for one or two extra characters.
*fnamep = vim_strnsave(*fnamep, STRLEN(*fnamep) + 2);
- xfree(*bufp); /* free any allocated file name */
+ xfree(*bufp); // free any allocated file name
*bufp = *fnamep;
if (*fnamep == NULL)
return -1;
@@ -23738,9 +10029,9 @@ repeat:
}
}
- /* ":." - path relative to the current directory */
- /* ":~" - path relative to the home directory */
- /* ":8" - shortname path - postponed till after */
+ // ":." - path relative to the current directory
+ // ":~" - path relative to the home directory
+ // ":8" - shortname path - postponed till after
while (src[*usedlen] == ':'
&& ((c = src[*usedlen + 1]) == '.' || c == '~' || c == '8')) {
*usedlen += 2;
@@ -23748,7 +10039,7 @@ repeat:
continue;
}
pbuf = NULL;
- /* Need full path first (use expand_env() to remove a "~/") */
+ // Need full path first (use expand_env() to remove a "~/")
if (!has_fullname) {
if (c == '.' && **fnamep == '~')
p = pbuf = expand_env_save(*fnamep);
@@ -23766,14 +10057,14 @@ repeat:
if (s != NULL) {
*fnamep = s;
if (pbuf != NULL) {
- xfree(*bufp); /* free any allocated file name */
+ xfree(*bufp); // free any allocated file name
*bufp = pbuf;
pbuf = NULL;
}
}
} else {
- home_replace(NULL, p, dirname, MAXPATHL, TRUE);
- /* Only replace it when it starts with '~' */
+ home_replace(NULL, p, dirname, MAXPATHL, true);
+ // Only replace it when it starts with '~'
if (*dirname == '~') {
s = vim_strsave(dirname);
*fnamep = s;
@@ -23788,8 +10079,8 @@ repeat:
tail = path_tail(*fnamep);
*fnamelen = STRLEN(*fnamep);
- /* ":h" - head, remove "/file_name", can be repeated */
- /* Don't remove the first "/" or "c:\" */
+ // ":h" - head, remove "/file_name", can be repeated
+ // Don't remove the first "/" or "c:\"
while (src[*usedlen] == ':' && src[*usedlen + 1] == 'h') {
valid |= VALID_HEAD;
*usedlen += 2;
@@ -23799,7 +10090,7 @@ repeat:
}
*fnamelen = (size_t)(tail - *fnamep);
if (*fnamelen == 0) {
- /* Result is empty. Turn it into "." to make ":cd %:h" work. */
+ // Result is empty. Turn it into "." to make ":cd %:h" work.
xfree(*bufp);
*bufp = *fnamep = tail = vim_strsave((char_u *)".");
*fnamelen = 1;
@@ -23810,49 +10101,74 @@ repeat:
}
}
- /* ":8" - shortname */
+ // ":8" - shortname
if (src[*usedlen] == ':' && src[*usedlen + 1] == '8') {
*usedlen += 2;
}
- /* ":t" - tail, just the basename */
+ // ":t" - tail, just the basename
if (src[*usedlen] == ':' && src[*usedlen + 1] == 't') {
*usedlen += 2;
*fnamelen -= (size_t)(tail - *fnamep);
*fnamep = tail;
}
- /* ":e" - extension, can be repeated */
- /* ":r" - root, without extension, can be repeated */
+ // ":e" - extension, can be repeated
+ // ":r" - root, without extension, can be repeated
while (src[*usedlen] == ':'
&& (src[*usedlen + 1] == 'e' || src[*usedlen + 1] == 'r')) {
/* find a '.' in the tail:
* - for second :e: before the current fname
* - otherwise: The last '.'
*/
- if (src[*usedlen + 1] == 'e' && *fnamep > tail)
+ const bool is_second_e = *fnamep > tail;
+ if (src[*usedlen + 1] == 'e' && is_second_e) {
s = *fnamep - 2;
- else
+ } else {
s = *fnamep + *fnamelen - 1;
- for (; s > tail; --s)
- if (s[0] == '.')
+ }
+
+ for (; s > tail; s--) {
+ if (s[0] == '.') {
break;
- if (src[*usedlen + 1] == 'e') { /* :e */
- if (s > tail) {
- *fnamelen += (size_t)(*fnamep - (s + 1));
- *fnamep = s + 1;
- } else if (*fnamep <= tail)
+ }
+ }
+ if (src[*usedlen + 1] == 'e') {
+ if (s > tail || (0 && is_second_e && s == tail)) {
+ // we stopped at a '.' (so anchor to &'.' + 1)
+ char_u *newstart = s + 1;
+ size_t distance_stepped_back = *fnamep - newstart;
+ *fnamelen += distance_stepped_back;
+ *fnamep = newstart;
+ } else if (*fnamep <= tail) {
*fnamelen = 0;
- } else { /* :r */
- if (s > tail) /* remove one extension */
+ }
+ } else {
+ // :r - Remove one extension
+ //
+ // Ensure that `s` doesn't go before `*fnamep`,
+ // since then we're taking too many roots:
+ //
+ // "path/to/this.file.ext" :e:e:r:r
+ // ^ ^-------- *fnamep
+ // +------------- tail
+ //
+ // Also ensure `s` doesn't go before `tail`,
+ // since then we're taking too many roots again:
+ //
+ // "path/to/this.file.ext" :r:r:r
+ // ^ ^------------- tail
+ // +--------------------- *fnamep
+ if (s > MAX(tail, *fnamep)) {
*fnamelen = (size_t)(s - *fnamep);
+ }
}
*usedlen += 2;
}
- /* ":s?pat?foo?" - substitute */
- /* ":gs?pat?foo?" - global substitute */
+ // ":s?pat?foo?" - substitute
+ // ":gs?pat?foo?" - global substitute
if (src[*usedlen] == ':'
&& (src[*usedlen + 1] == 's'
|| (src[*usedlen + 1] == 'g' && src[*usedlen + 2] == 's'))) {
@@ -23872,12 +10188,12 @@ repeat:
sep = *s++;
if (sep) {
- /* find end of pattern */
+ // find end of pattern
p = vim_strchr(s, sep);
if (p != NULL) {
pat = vim_strnsave(s, (int)(p - s));
s = p + 1;
- /* find end of substitution */
+ // find end of substitution
p = vim_strchr(s, sep);
if (p != NULL) {
sub = vim_strnsave(s, (int)(p - s));
@@ -23894,9 +10210,10 @@ repeat:
}
xfree(pat);
}
- /* after using ":s", repeat all the modifiers */
- if (didit)
+ // after using ":s", repeat all the modifiers
+ if (didit) {
goto repeat;
+ }
}
}
@@ -23935,7 +10252,7 @@ char_u *do_string_sub(char_u *str, char_u *pat, char_u *sub,
char_u *save_cpo;
char_u *zero_width = NULL;
- /* Make 'cpoptions' empty, so that the 'l' flag doesn't work here */
+ // Make 'cpoptions' empty, so that the 'l' flag doesn't work here
save_cpo = p_cpo;
p_cpo = empty_option;
@@ -23949,11 +10266,11 @@ char_u *do_string_sub(char_u *str, char_u *pat, char_u *sub,
tail = str;
end = str + STRLEN(str);
while (vim_regexec_nl(&regmatch, str, (colnr_T)(tail - str))) {
- /* Skip empty match except for first match. */
+ // Skip empty match except for first match.
if (regmatch.startp[0] == regmatch.endp[0]) {
if (zero_width == regmatch.startp[0]) {
- /* avoid getting stuck on a match with an empty string */
- int i = MB_PTR2LEN(tail);
+ // 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);
ga.ga_len += i;
tail += i;
@@ -23971,7 +10288,7 @@ char_u *do_string_sub(char_u *str, char_u *pat, char_u *sub,
ga_grow(&ga, (int)((end - tail) + sublen -
(regmatch.endp[0] - regmatch.startp[0])));
- /* copy the text up to where the match is */
+ // copy the text up to where the match is
int i = (int)(regmatch.startp[0] - tail);
memmove((char_u *)ga.ga_data + ga.ga_len, tail, (size_t)i);
// add the substituted text
@@ -24006,10 +10323,10 @@ char_u *do_string_sub(char_u *str, char_u *pat, char_u *sub,
/// common code for getting job callbacks for jobstart, termopen and rpcstart
///
/// @return true/false on success/failure.
-static inline bool common_job_callbacks(dict_T *vopts,
- CallbackReader *on_stdout,
- CallbackReader *on_stderr,
- Callback *on_exit)
+bool common_job_callbacks(dict_T *vopts,
+ CallbackReader *on_stdout,
+ CallbackReader *on_stderr,
+ Callback *on_exit)
{
if (tv_dict_get_callback(vopts, S_LEN("on_stdout"), &on_stdout->cb)
&&tv_dict_get_callback(vopts, S_LEN("on_stderr"), &on_stderr->cb)
@@ -24033,7 +10350,7 @@ static inline bool common_job_callbacks(dict_T *vopts,
}
-static Channel *find_job(uint64_t id, bool show_error)
+Channel *find_job(uint64_t id, bool show_error)
{
Channel *data = find_channel(id);
if (!data || data->streamtype != kChannelStreamProc
@@ -24051,7 +10368,7 @@ static Channel *find_job(uint64_t id, bool show_error)
}
-static void script_host_eval(char *name, typval_T *argvars, typval_T *rettv)
+void script_host_eval(char *name, typval_T *argvars, typval_T *rettv)
{
if (check_restricted() || check_secure()) {
return;
@@ -24091,8 +10408,10 @@ typval_T eval_call_provider(char *provider, char *method, list_T *arguments)
.autocmd_fname = autocmd_fname,
.autocmd_match = autocmd_match,
.autocmd_bufnr = autocmd_bufnr,
- .funccalp = save_funccal()
+ .funccalp = (void *)get_current_funccal()
};
+ funccal_entry_T funccal_entry;
+ save_funccal(&funccal_entry);
provider_call_nesting++;
typval_T argvars[3] = {
@@ -24119,7 +10438,7 @@ typval_T eval_call_provider(char *provider, char *method, list_T *arguments)
tv_list_unref(arguments);
// Restore caller scope information
- restore_funccal(provider_caller_scope.funccalp);
+ restore_funccal();
provider_caller_scope = saved_provider_caller_scope;
provider_call_nesting--;
assert(provider_call_nesting >= 0);
@@ -24137,6 +10456,7 @@ bool eval_has_provider(const char *feat)
&& !strequal(feat, "python_dynamic")
&& !strequal(feat, "python3_compiled")
&& !strequal(feat, "python3_dynamic")
+ && !strequal(feat, "perl")
&& !strequal(feat, "ruby")
&& !strequal(feat, "node")) {
// Avoid autoload for non-provider has() features.
@@ -24227,3 +10547,51 @@ void ex_checkhealth(exarg_T *eap)
xfree(buf);
}
+
+void invoke_prompt_callback(void)
+{
+ typval_T rettv;
+ typval_T argv[2];
+ char_u *text;
+ char_u *prompt;
+ linenr_T lnum = curbuf->b_ml.ml_line_count;
+
+ // Add a new line for the prompt before invoking the callback, so that
+ // text can always be inserted above the last line.
+ ml_append(lnum, (char_u *)"", 0, false);
+ curwin->w_cursor.lnum = lnum + 1;
+ curwin->w_cursor.col = 0;
+
+ if (curbuf->b_prompt_callback.type == kCallbackNone) {
+ return;
+ }
+ text = ml_get(lnum);
+ prompt = prompt_text();
+ if (STRLEN(text) >= STRLEN(prompt)) {
+ text += STRLEN(prompt);
+ }
+ argv[0].v_type = VAR_STRING;
+ argv[0].vval.v_string = vim_strsave(text);
+ argv[1].v_type = VAR_UNKNOWN;
+
+ callback_call(&curbuf->b_prompt_callback, 1, argv, &rettv);
+ tv_clear(&argv[0]);
+ tv_clear(&rettv);
+}
+
+// Return true When the interrupt callback was invoked.
+bool invoke_prompt_interrupt(void)
+{
+ typval_T rettv;
+ typval_T argv[1];
+
+ if (curbuf->b_prompt_interrupt.type == kCallbackNone) {
+ return false;
+ }
+ argv[0].v_type = VAR_UNKNOWN;
+
+ got_int = false; // don't skip executing commands
+ callback_call(&curbuf->b_prompt_interrupt, 0, argv, &rettv);
+ tv_clear(&rettv);
+ return true;
+}
diff --git a/src/nvim/eval.h b/src/nvim/eval.h
index e099de831a..0b4cbb3b4d 100644
--- a/src/nvim/eval.h
+++ b/src/nvim/eval.h
@@ -1,16 +1,13 @@
#ifndef NVIM_EVAL_H
#define NVIM_EVAL_H
-#include "nvim/hashtab.h" // For hashtab_T
#include "nvim/buffer_defs.h"
-#include "nvim/ex_cmds_defs.h" // For exarg_T
-#include "nvim/eval/typval.h"
-#include "nvim/profile.h"
-#include "nvim/garray.h"
-#include "nvim/event/rstream.h"
-#include "nvim/event/wstream.h"
#include "nvim/channel.h"
-#include "nvim/os/stdpaths_defs.h"
+#include "nvim/eval/funcs.h" // For FunPtr
+#include "nvim/event/time.h" // For TimeWatcher
+#include "nvim/ex_cmds_defs.h" // For exarg_T
+#include "nvim/os/fileio.h" // For FileDescriptor
+#include "nvim/os/stdpaths_defs.h" // For XDGVarType
#define COPYID_INC 2
#define COPYID_MASK (~0x1)
@@ -24,6 +21,50 @@ EXTERN ufunc_T dumuf;
#define HIKEY2UF(p) ((ufunc_T *)(p - offsetof(ufunc_T, uf_name)))
#define HI2UF(hi) HIKEY2UF((hi)->hi_key)
+/*
+ * Structure returned by get_lval() and used by set_var_lval().
+ * For a plain name:
+ * "name" points to the variable name.
+ * "exp_name" is NULL.
+ * "tv" is NULL
+ * For a magic braces name:
+ * "name" points to the expanded variable name.
+ * "exp_name" is non-NULL, to be freed later.
+ * "tv" is NULL
+ * For an index in a list:
+ * "name" points to the (expanded) variable name.
+ * "exp_name" NULL or non-NULL, to be freed later.
+ * "tv" points to the (first) list item value
+ * "li" points to the (first) list item
+ * "range", "n1", "n2" and "empty2" indicate what items are used.
+ * For an existing Dict item:
+ * "name" points to the (expanded) variable name.
+ * "exp_name" NULL or non-NULL, to be freed later.
+ * "tv" points to the dict item value
+ * "newkey" is NULL
+ * For a non-existing Dict item:
+ * "name" points to the (expanded) variable name.
+ * "exp_name" NULL or non-NULL, to be freed later.
+ * "tv" points to the Dictionary typval_T
+ * "newkey" is the key for the new item.
+ */
+typedef struct lval_S {
+ const char *ll_name; ///< Start of variable name (can be NULL).
+ size_t ll_name_len; ///< Length of the .ll_name.
+ char *ll_exp_name; ///< NULL or expanded name in allocated memory.
+ typval_T *ll_tv; ///< Typeval of item being used. If "newkey"
+ ///< isn't NULL it's the Dict to which to add the item.
+ listitem_T *ll_li; ///< The list item or NULL.
+ list_T *ll_list; ///< The list or NULL.
+ int ll_range; ///< TRUE when a [i:j] range was used.
+ long ll_n1; ///< First index for list.
+ long ll_n2; ///< Second index for list range.
+ int ll_empty2; ///< Second index is empty: [i:].
+ dict_T *ll_dict; ///< The Dictionary or NULL.
+ dictitem_T *ll_di; ///< The dictitem or NULL.
+ char_u *ll_newkey; ///< New key for Dict in allocated memory or NULL.
+} lval_T;
+
/// enum used by var_flavour()
typedef enum {
VAR_FLAVOUR_DEFAULT = 1, // doesn't start with uppercase
@@ -117,6 +158,8 @@ typedef enum {
VV_TYPE_BOOL,
VV_ECHOSPACE,
VV_EXITING,
+ VV_LUA,
+ VV_ARGV,
} VimVarIndex;
/// All recognized msgpack types
@@ -138,8 +181,60 @@ extern const list_T *eval_msgpack_type_lists[LAST_MSGPACK_TYPE + 1];
#undef LAST_MSGPACK_TYPE
-typedef int (*ArgvFunc)(int current_argcount, typval_T *argv,
- int called_func_argcount);
+/// trans_function_name() flags
+typedef enum {
+ TFN_INT = 1, ///< May use internal function name
+ TFN_QUIET = 2, ///< Do not emit error messages.
+ TFN_NO_AUTOLOAD = 4, ///< Do not use script autoloading.
+ TFN_NO_DEREF = 8, ///< Do not dereference a Funcref.
+ TFN_READ_ONLY = 16, ///< Will not change the variable.
+} TransFunctionNameFlags;
+
+/// get_lval() flags
+typedef enum {
+ GLV_QUIET = TFN_QUIET, ///< Do not emit error messages.
+ GLV_NO_AUTOLOAD = TFN_NO_AUTOLOAD, ///< Do not use script autoloading.
+ GLV_READ_ONLY = TFN_READ_ONLY, ///< Indicates that caller will not change
+ ///< the value (prevents error message).
+} GetLvalFlags;
+
+/// flags for find_name_end()
+#define FNE_INCL_BR 1 /* find_name_end(): include [] in name */
+#define FNE_CHECK_START 2 /* find_name_end(): check name starts with
+ valid character */
+
+typedef struct {
+ TimeWatcher tw;
+ int timer_id;
+ int repeat_count;
+ int refcount;
+ int emsg_count; ///< Errors in a repeating timer.
+ long timeout;
+ bool stopped;
+ bool paused;
+ Callback callback;
+} timer_T;
+
+/// Type of assert_* check being performed
+typedef enum
+{
+ ASSERT_EQUAL,
+ ASSERT_NOTEQUAL,
+ ASSERT_MATCH,
+ ASSERT_NOTMATCH,
+ ASSERT_INRANGE,
+ ASSERT_OTHER,
+} assert_type_T;
+
+/// Type for dict_list function
+typedef enum {
+ kDictListKeys, ///< List dictionary keys.
+ kDictListValues, ///< List dictionary values.
+ kDictListItems, ///< List dictionary contents: [keys, values].
+} DictListType;
+
+// Used for checking if local variables or arguments used in a lambda.
+extern bool *eval_lavars_used;
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "eval.h.generated.h"
diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua
index ab5ff57c2f..023c60f118 100644
--- a/src/nvim/eval.lua
+++ b/src/nvim/eval.lua
@@ -28,8 +28,9 @@ return {
asin={args=1, func="float_op_wrapper", data="&asin"}, -- WJMc
assert_beeps={args={1, 2}},
assert_equal={args={2, 3}},
+ assert_equalfile={args={2, 3}},
assert_exception={args={1, 2}},
- assert_fails={args={1, 2}},
+ assert_fails={args={1, 3}},
assert_false={args={1, 2}},
assert_inrange={args={3, 4}},
assert_match={args={2, 3}},
@@ -63,7 +64,7 @@ return {
chansend={args=2},
char2nr={args={1, 2}},
cindent={args=1},
- clearmatches={},
+ clearmatches={args={0, 1}},
col={args=1},
complete={args=2},
complete_add={args=1},
@@ -81,6 +82,7 @@ return {
ctxset={args={1, 2}},
ctxsize={},
cursor={args={1, 3}},
+ debugbreak={args={1, 1}},
deepcopy={args={1, 2}},
delete={args={1,2}},
deletebufline={args={2,3}},
@@ -100,6 +102,7 @@ return {
exists={args=1},
exp={args=1, func="float_op_wrapper", data="&exp"},
expand={args={1, 3}},
+ expandcmd={args=1},
extend={args={2, 3}},
feedkeys={args={1, 2}},
file_readable={args=1, func='f_filereadable'}, -- obsolete
@@ -108,6 +111,7 @@ return {
filter={args=2},
finddir={args={1, 3}},
findfile={args={1, 3}},
+ flatten={args={1, 2}},
float2nr={args=1},
floor={args=1, func="float_op_wrapper", data="&floor"},
fmod={args=2},
@@ -146,7 +150,7 @@ return {
getjumplist={args={0, 2}},
getline={args={1, 2}},
getloclist={args={1, 2}},
- getmatches={},
+ getmatches={args={0, 1}},
getpid={},
getpos={args=1},
getqflist={args={0, 1}},
@@ -212,6 +216,7 @@ return {
line={args=1},
line2byte={args=1},
lispindent={args=1},
+ list2str={args={1, 2}},
localtime={},
log={args=1, func="float_op_wrapper", data="&log"},
log10={args=1, func="float_op_wrapper", data="&log10"},
@@ -223,7 +228,7 @@ return {
matchadd={args={2, 5}},
matchaddpos={args={2, 5}},
matcharg={args=1},
- matchdelete={args=1},
+ matchdelete={args={1, 2}},
matchend={args={2, 4}},
matchlist={args={2, 4}},
matchstr={args={2, 4}},
@@ -242,11 +247,16 @@ return {
pow={args=2},
prevnonblank={args=1},
printf={args=varargs(1)},
+ prompt_setcallback={args={2, 2}},
+ prompt_setinterrupt={args={2, 2}},
+ prompt_setprompt={args={2, 2}},
+ pum_getpos={},
pumvisible={},
py3eval={args=1},
pyeval={args=1},
pyxeval={args=1},
range={args={1, 3}},
+ readdir={args={1, 2}},
readfile={args={1, 3}},
reg_executing={},
reg_recording={},
@@ -284,7 +294,7 @@ return {
setfperm={args=2},
setline={args=2},
setloclist={args={2, 4}},
- setmatches={args=1},
+ setmatches={args={1, 2}},
setpos={args=2},
setqflist={args={1, 3}},
setreg={args={2, 3}},
@@ -315,6 +325,7 @@ return {
sqrt={args=1, func="float_op_wrapper", data="&sqrt"},
stdpath={args=1},
str2float={args=1},
+ str2list={args={1, 2}},
str2nr={args={1, 2}},
strcharpart={args={2, 3}},
strchars={args={1,2}},
diff --git a/src/nvim/eval/decode.c b/src/nvim/eval/decode.c
index 42999ddd62..daba304f00 100644
--- a/src/nvim/eval/decode.c
+++ b/src/nvim/eval/decode.c
@@ -795,9 +795,9 @@ json_decode_string_cycle_start:
}
p += 3;
POP(((typval_T) {
- .v_type = VAR_SPECIAL,
+ .v_type = VAR_BOOL,
.v_lock = VAR_UNLOCKED,
- .vval = { .v_special = kSpecialVarTrue },
+ .vval = { .v_bool = kBoolVarTrue },
}), false);
break;
}
@@ -808,9 +808,9 @@ json_decode_string_cycle_start:
}
p += 4;
POP(((typval_T) {
- .v_type = VAR_SPECIAL,
+ .v_type = VAR_BOOL,
.v_lock = VAR_UNLOCKED,
- .vval = { .v_special = kSpecialVarFalse },
+ .vval = { .v_bool = kBoolVarFalse },
}), false);
break;
}
@@ -954,10 +954,10 @@ int msgpack_to_vim(const msgpack_object mobj, typval_T *const rettv)
}
case MSGPACK_OBJECT_BOOLEAN: {
*rettv = (typval_T) {
- .v_type = VAR_SPECIAL,
+ .v_type = VAR_BOOL,
.v_lock = VAR_UNLOCKED,
.vval = {
- .v_special = mobj.via.boolean ? kSpecialVarTrue : kSpecialVarFalse
+ .v_bool = mobj.via.boolean ? kBoolVarTrue : kBoolVarFalse
},
};
break;
diff --git a/src/nvim/eval/encode.c b/src/nvim/eval/encode.c
index 6074e4ee69..137f099df6 100644
--- a/src/nvim/eval/encode.c
+++ b/src/nvim/eval/encode.c
@@ -34,10 +34,13 @@
#define utf_ptr2len(b) ((size_t)utf_ptr2len((char_u *)b))
#define utf_char2len(b) ((size_t)utf_char2len(b))
+const char *const encode_bool_var_names[] = {
+ [kBoolVarTrue] = "true",
+ [kBoolVarFalse] = "false",
+};
+
const char *const encode_special_var_names[] = {
[kSpecialVarNull] = "null",
- [kSpecialVarTrue] = "true",
- [kSpecialVarFalse] = "false",
};
#ifdef INCLUDE_GENERATED_DECLARATIONS
@@ -248,7 +251,7 @@ bool encode_vim_list_to_buf(const list_T *const list, size_t *const ret_len,
/// @param[out] read_bytes Is set to amount of bytes read.
///
/// @return OK when reading was finished, FAIL in case of error (i.e. list item
-/// was not a string), NOTDONE if reading was successfull, but there are
+/// was not a string), NOTDONE if reading was successful, but there are
/// more bytes to read.
int encode_read_from_list(ListReaderState *const state, char *const buf,
const size_t nbuf, size_t *const read_bytes)
diff --git a/src/nvim/eval/encode.h b/src/nvim/eval/encode.h
index ccea245ab3..596bb49ae0 100644
--- a/src/nvim/eval/encode.h
+++ b/src/nvim/eval/encode.h
@@ -55,6 +55,7 @@ static inline ListReaderState encode_init_lrstate(const list_T *const list)
}
/// Array mapping values from SpecialVarValue enum to names
+extern const char *const encode_bool_var_names[];
extern const char *const encode_special_var_names[];
/// First codepoint in high surrogates block
diff --git a/src/nvim/eval/executor.c b/src/nvim/eval/executor.c
index 8cd21f8d62..da05ecda43 100644
--- a/src/nvim/eval/executor.c
+++ b/src/nvim/eval/executor.c
@@ -28,11 +28,13 @@ int eexe_mod_op(typval_T *const tv1, const typval_T *const tv2,
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NO_SANITIZE_UNDEFINED
{
// Can't do anything with a Funcref, a Dict or special value on the right.
- if (tv2->v_type != VAR_FUNC && tv2->v_type != VAR_DICT) {
+ if (tv2->v_type != VAR_FUNC && tv2->v_type != VAR_DICT
+ && tv2->v_type != VAR_BOOL && tv2->v_type != VAR_SPECIAL) {
switch (tv1->v_type) {
case VAR_DICT:
case VAR_FUNC:
case VAR_PARTIAL:
+ case VAR_BOOL:
case VAR_SPECIAL: {
break;
}
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c
new file mode 100644
index 0000000000..1a80d4d4bd
--- /dev/null
+++ b/src/nvim/eval/funcs.c
@@ -0,0 +1,11270 @@
+// This is an open source non-commercial project. Dear PVS-Studio, please check
+// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+
+#include <float.h>
+#include <math.h>
+
+#include "nvim/api/private/helpers.h"
+#include "nvim/api/vim.h"
+#include "nvim/ascii.h"
+#include "nvim/assert.h"
+#include "nvim/buffer.h"
+#include "nvim/change.h"
+#include "nvim/channel.h"
+#include "nvim/charset.h"
+#include "nvim/context.h"
+#include "nvim/cursor.h"
+#include "nvim/diff.h"
+#include "nvim/edit.h"
+#include "nvim/eval.h"
+#include "nvim/eval/decode.h"
+#include "nvim/eval/encode.h"
+#include "nvim/eval/executor.h"
+#include "nvim/eval/funcs.h"
+#include "nvim/eval/userfunc.h"
+#include "nvim/ex_cmds2.h"
+#include "nvim/ex_docmd.h"
+#include "nvim/ex_getln.h"
+#include "nvim/file_search.h"
+#include "nvim/fileio.h"
+#include "nvim/fold.h"
+#include "nvim/if_cscope.h"
+#include "nvim/indent.h"
+#include "nvim/indent_c.h"
+#include "nvim/lua/executor.h"
+#include "nvim/mark.h"
+#include "nvim/math.h"
+#include "nvim/memline.h"
+#include "nvim/misc1.h"
+#include "nvim/mouse.h"
+#include "nvim/move.h"
+#include "nvim/msgpack_rpc/channel.h"
+#include "nvim/msgpack_rpc/server.h"
+#include "nvim/ops.h"
+#include "nvim/option.h"
+#include "nvim/os/dl.h"
+#include "nvim/os/input.h"
+#include "nvim/os/shell.h"
+#include "nvim/path.h"
+#include "nvim/popupmnu.h"
+#include "nvim/quickfix.h"
+#include "nvim/regexp.h"
+#include "nvim/screen.h"
+#include "nvim/search.h"
+#include "nvim/sha256.h"
+#include "nvim/sign.h"
+#include "nvim/spell.h"
+#include "nvim/state.h"
+#include "nvim/syntax.h"
+#include "nvim/tag.h"
+#include "nvim/ui.h"
+#include "nvim/undo.h"
+#include "nvim/version.h"
+#include "nvim/vim.h"
+
+
+/// Describe data to return from find_some_match()
+typedef enum {
+ kSomeMatch, ///< Data for match().
+ kSomeMatchEnd, ///< Data for matchend().
+ kSomeMatchList, ///< Data for matchlist().
+ kSomeMatchStr, ///< Data for matchstr().
+ kSomeMatchStrPos, ///< Data for matchstrpos().
+} SomeMatchType;
+
+KHASH_MAP_INIT_STR(functions, VimLFuncDef)
+
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "eval/funcs.c.generated.h"
+
+#ifdef _MSC_VER
+// This prevents MSVC from replacing the functions with intrinsics,
+// and causing errors when trying to get their addresses in funcs.generated.h
+#pragma function(ceil)
+#pragma function(floor)
+#endif
+
+PRAGMA_DIAG_PUSH_IGNORE_MISSING_PROTOTYPES
+#include "funcs.generated.h"
+PRAGMA_DIAG_POP
+#endif
+
+
+static char *e_listarg = N_("E686: Argument of %s must be a List");
+static char *e_stringreq = N_("E928: String required");
+static char *e_invalwindow = N_("E957: Invalid window number");
+
+/// Dummy va_list for passing to vim_snprintf
+///
+/// Used because:
+/// - passing a NULL pointer doesn't work when va_list isn't a pointer
+/// - locally in the function results in a "used before set" warning
+/// - using va_start() to initialize it gives "function with fixed args" error
+static va_list dummy_ap;
+
+
+/// Function given to ExpandGeneric() to obtain the list of internal
+/// or user defined function names.
+char_u *get_function_name(expand_T *xp, int idx)
+{
+ static int intidx = -1;
+ char_u *name;
+
+ if (idx == 0)
+ intidx = -1;
+ if (intidx < 0) {
+ name = get_user_func_name(xp, idx);
+ if (name != NULL)
+ return name;
+ }
+ while ((size_t)++intidx < ARRAY_SIZE(functions)
+ && functions[intidx].name[0] == '\0') {
+ }
+
+ if ((size_t)intidx >= ARRAY_SIZE(functions)) {
+ return NULL;
+ }
+
+ const char *const key = functions[intidx].name;
+ const size_t key_len = strlen(key);
+ memcpy(IObuff, key, key_len);
+ IObuff[key_len] = '(';
+ if (functions[intidx].max_argc == 0) {
+ IObuff[key_len + 1] = ')';
+ IObuff[key_len + 2] = NUL;
+ } else {
+ IObuff[key_len + 1] = NUL;
+ }
+ return IObuff;
+}
+
+/// Function given to ExpandGeneric() to obtain the list of internal or
+/// user defined variable or function names.
+char_u *get_expr_name(expand_T *xp, int idx)
+{
+ static int intidx = -1;
+ char_u *name;
+
+ if (idx == 0)
+ intidx = -1;
+ if (intidx < 0) {
+ name = get_function_name(xp, idx);
+ if (name != NULL)
+ return name;
+ }
+ return get_user_var_name(xp, ++intidx);
+}
+
+/// Find internal function in hash functions
+///
+/// @param[in] name Name of the function.
+///
+/// Returns pointer to the function definition or NULL if not found.
+const VimLFuncDef *find_internal_func(const char *const name)
+ FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE FUNC_ATTR_NONNULL_ALL
+{
+ size_t len = strlen(name);
+ return find_internal_func_gperf(name, len);
+}
+
+/*
+ * Return TRUE for a non-zero Number and a non-empty String.
+ */
+static int non_zero_arg(typval_T *argvars)
+{
+ return ((argvars[0].v_type == VAR_NUMBER
+ && argvars[0].vval.v_number != 0)
+ || (argvars[0].v_type == VAR_BOOL
+ && argvars[0].vval.v_bool == kBoolVarTrue)
+ || (argvars[0].v_type == VAR_STRING
+ && argvars[0].vval.v_string != NULL
+ && *argvars[0].vval.v_string != NUL));
+}
+
+// Apply a floating point C function on a typval with one float_T.
+//
+// Some versions of glibc on i386 have an optimization that makes it harder to
+// call math functions indirectly from inside an inlined function, causing
+// compile-time errors. Avoid `inline` in that case. #3072
+static void float_op_wrapper(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ float_T f;
+ float_T (*function)(float_T) = (float_T (*)(float_T))fptr;
+
+ rettv->v_type = VAR_FLOAT;
+ if (tv_get_float_chk(argvars, &f)) {
+ rettv->vval.v_float = function(f);
+ } else {
+ rettv->vval.v_float = 0.0;
+ }
+}
+
+static void api_wrapper(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ if (check_restricted() || check_secure()) {
+ return;
+ }
+
+ ApiDispatchWrapper fn = (ApiDispatchWrapper)fptr;
+
+ Array args = ARRAY_DICT_INIT;
+
+ for (typval_T *tv = argvars; tv->v_type != VAR_UNKNOWN; tv++) {
+ ADD(args, vim_to_object(tv));
+ }
+
+ Error err = ERROR_INIT;
+ Object result = fn(VIML_INTERNAL_CALL, args, &err);
+
+ if (ERROR_SET(&err)) {
+ emsgf_multiline((const char *)e_api_error, err.msg);
+ goto end;
+ }
+
+ if (!object_to_vim(result, rettv, &err)) {
+ EMSG2(_("Error converting the call result: %s"), err.msg);
+ }
+
+end:
+ api_free_array(args);
+ api_free_object(result);
+ api_clear_error(&err);
+}
+
+/*
+ * "abs(expr)" function
+ */
+static void f_abs(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ if (argvars[0].v_type == VAR_FLOAT) {
+ float_op_wrapper(argvars, rettv, (FunPtr)&fabs);
+ } else {
+ varnumber_T n;
+ bool error = false;
+
+ n = tv_get_number_chk(&argvars[0], &error);
+ if (error) {
+ rettv->vval.v_number = -1;
+ } else if (n > 0) {
+ rettv->vval.v_number = n;
+ } else {
+ rettv->vval.v_number = -n;
+ }
+ }
+}
+
+/*
+ * "add(list, item)" function
+ */
+static void f_add(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = 1; // Default: failed.
+ if (argvars[0].v_type == VAR_LIST) {
+ list_T *const l = argvars[0].vval.v_list;
+ if (!tv_check_lock(tv_list_locked(l), N_("add() argument"), TV_TRANSLATE)) {
+ tv_list_append_tv(l, &argvars[1]);
+ tv_copy(&argvars[0], rettv);
+ }
+ } else {
+ EMSG(_(e_listreq));
+ }
+}
+
+/*
+ * "and(expr, expr)" function
+ */
+static void f_and(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = tv_get_number_chk(&argvars[0], NULL)
+ & tv_get_number_chk(&argvars[1], NULL);
+}
+
+
+/// "api_info()" function
+static void f_api_info(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ Dictionary metadata = api_metadata();
+ (void)object_to_vim(DICTIONARY_OBJ(metadata), rettv, NULL);
+ api_free_dictionary(metadata);
+}
+
+// "append(lnum, string/list)" function
+static void f_append(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const linenr_T lnum = tv_get_lnum(&argvars[0]);
+
+ set_buffer_lines(curbuf, lnum, true, &argvars[1], rettv);
+}
+
+// "appendbufline(buf, lnum, string/list)" function
+static void f_appendbufline(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ buf_T *const buf = tv_get_buf(&argvars[0], false);
+ if (buf == NULL) {
+ rettv->vval.v_number = 1; // FAIL
+ } else {
+ const linenr_T lnum = tv_get_lnum_buf(&argvars[1], buf);
+ set_buffer_lines(buf, lnum, true, &argvars[2], rettv);
+ }
+}
+
+static void f_argc(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ if (argvars[0].v_type == VAR_UNKNOWN) {
+ // use the current window
+ rettv->vval.v_number = ARGCOUNT;
+ } else if (argvars[0].v_type == VAR_NUMBER
+ && tv_get_number(&argvars[0]) == -1) {
+ // use the global argument list
+ rettv->vval.v_number = GARGCOUNT;
+ } else {
+ // use the argument list of the specified window
+ win_T *wp = find_win_by_nr_or_id(&argvars[0]);
+ if (wp != NULL) {
+ rettv->vval.v_number = WARGCOUNT(wp);
+ } else {
+ rettv->vval.v_number = -1;
+ }
+ }
+}
+
+/*
+ * "argidx()" function
+ */
+static void f_argidx(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = curwin->w_arg_idx;
+}
+
+/// "arglistid" function
+static void f_arglistid(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = -1;
+ win_T *wp = find_tabwin(&argvars[0], &argvars[1]);
+ if (wp != NULL) {
+ rettv->vval.v_number = wp->w_alist->id;
+ }
+}
+
+/*
+ * "argv(nr)" function
+ */
+static void f_argv(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ aentry_T *arglist = NULL;
+ int argcount = -1;
+
+ if (argvars[0].v_type != VAR_UNKNOWN) {
+ if (argvars[1].v_type == VAR_UNKNOWN) {
+ arglist = ARGLIST;
+ argcount = ARGCOUNT;
+ } else if (argvars[1].v_type == VAR_NUMBER
+ && tv_get_number(&argvars[1]) == -1) {
+ arglist = GARGLIST;
+ argcount = GARGCOUNT;
+ } else {
+ win_T *wp = find_win_by_nr_or_id(&argvars[1]);
+ if (wp != NULL) {
+ // Use the argument list of the specified window
+ arglist = WARGLIST(wp);
+ argcount = WARGCOUNT(wp);
+ }
+ }
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = NULL;
+ int idx = tv_get_number_chk(&argvars[0], NULL);
+ if (arglist != NULL && idx >= 0 && idx < argcount) {
+ rettv->vval.v_string = (char_u *)xstrdup(
+ (const char *)alist_name(&arglist[idx]));
+ } else if (idx == -1) {
+ get_arglist_as_rettv(arglist, argcount, rettv);
+ }
+ } else {
+ get_arglist_as_rettv(ARGLIST, ARGCOUNT, rettv);
+ }
+}
+
+static void f_assert_beeps(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const char *const cmd = tv_get_string_chk(&argvars[0]);
+ garray_T ga;
+ int ret = 0;
+
+ called_vim_beep = false;
+ suppress_errthrow = true;
+ emsg_silent = false;
+ do_cmdline_cmd(cmd);
+ if (!called_vim_beep) {
+ prepare_assert_error(&ga);
+ ga_concat(&ga, (const char_u *)"command did not beep: ");
+ ga_concat(&ga, (const char_u *)cmd);
+ assert_error(&ga);
+ ga_clear(&ga);
+ ret = 1;
+ }
+
+ suppress_errthrow = false;
+ emsg_on_display = false;
+ rettv->vval.v_number = ret;
+}
+
+// "assert_equal(expected, actual[, msg])" function
+static void f_assert_equal(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = assert_equal_common(argvars, ASSERT_EQUAL);
+}
+
+// "assert_equalfile(fname-one, fname-two[, msg])" function
+static void f_assert_equalfile(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = assert_equalfile(argvars);
+}
+
+// "assert_notequal(expected, actual[, msg])" function
+static void f_assert_notequal(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = assert_equal_common(argvars, ASSERT_NOTEQUAL);
+}
+
+/// "assert_report(msg)
+static void f_assert_report(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ garray_T ga;
+
+ prepare_assert_error(&ga);
+ ga_concat(&ga, (const char_u *)tv_get_string(&argvars[0]));
+ assert_error(&ga);
+ ga_clear(&ga);
+ rettv->vval.v_number = 1;
+}
+
+/// "assert_exception(string[, msg])" function
+static void f_assert_exception(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = assert_exception(argvars);
+}
+
+/// "assert_fails(cmd [, error [, msg]])" function
+static void f_assert_fails(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = assert_fails(argvars);
+}
+
+// "assert_false(actual[, msg])" function
+static void f_assert_false(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = assert_bool(argvars, false);
+}
+
+/// "assert_inrange(lower, upper[, msg])" function
+static void f_assert_inrange(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = assert_inrange(argvars);
+}
+
+/// "assert_match(pattern, actual[, msg])" function
+static void f_assert_match(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = assert_match_common(argvars, ASSERT_MATCH);
+}
+
+/// "assert_notmatch(pattern, actual[, msg])" function
+static void f_assert_notmatch(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = assert_match_common(argvars, ASSERT_NOTMATCH);
+}
+
+// "assert_true(actual[, msg])" function
+static void f_assert_true(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = assert_bool(argvars, true);
+}
+
+/*
+ * "atan2()" function
+ */
+static void f_atan2(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ float_T fx;
+ float_T fy;
+
+ rettv->v_type = VAR_FLOAT;
+ if (tv_get_float_chk(argvars, &fx) && tv_get_float_chk(&argvars[1], &fy)) {
+ rettv->vval.v_float = atan2(fx, fy);
+ } else {
+ rettv->vval.v_float = 0.0;
+ }
+}
+
+/*
+ * "browse(save, title, initdir, default)" function
+ */
+static void f_browse(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_string = NULL;
+ rettv->v_type = VAR_STRING;
+}
+
+/*
+ * "browsedir(title, initdir)" function
+ */
+static void f_browsedir(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ f_browse(argvars, rettv, NULL);
+}
+
+
+/*
+ * Find a buffer by number or exact name.
+ */
+static buf_T *find_buffer(typval_T *avar)
+{
+ buf_T *buf = NULL;
+
+ if (avar->v_type == VAR_NUMBER)
+ buf = buflist_findnr((int)avar->vval.v_number);
+ else if (avar->v_type == VAR_STRING && avar->vval.v_string != NULL) {
+ buf = buflist_findname_exp(avar->vval.v_string);
+ if (buf == NULL) {
+ /* No full path name match, try a match with a URL or a "nofile"
+ * buffer, these don't use the full path. */
+ FOR_ALL_BUFFERS(bp) {
+ if (bp->b_fname != NULL
+ && (path_with_url((char *)bp->b_fname)
+ || bt_nofile(bp)
+ )
+ && STRCMP(bp->b_fname, avar->vval.v_string) == 0) {
+ buf = bp;
+ break;
+ }
+ }
+ }
+ }
+ return buf;
+}
+
+// "bufadd(expr)" function
+static void f_bufadd(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ char_u *name = (char_u *)tv_get_string(&argvars[0]);
+
+ rettv->vval.v_number = buflist_add(*name == NUL ? NULL : name, 0);
+}
+
+/*
+ * "bufexists(expr)" function
+ */
+static void f_bufexists(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = (find_buffer(&argvars[0]) != NULL);
+}
+
+/*
+ * "buflisted(expr)" function
+ */
+static void f_buflisted(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ buf_T *buf;
+
+ buf = find_buffer(&argvars[0]);
+ rettv->vval.v_number = (buf != NULL && buf->b_p_bl);
+}
+
+// "bufload(expr)" function
+static void f_bufload(typval_T *argvars, typval_T *unused, FunPtr fptr)
+{
+ buf_T *buf = get_buf_arg(&argvars[0]);
+
+ if (buf != NULL && buf->b_ml.ml_mfp == NULL) {
+ aco_save_T aco;
+
+ aucmd_prepbuf(&aco, buf);
+ swap_exists_action = SEA_NONE;
+ open_buffer(false, NULL, 0);
+ aucmd_restbuf(&aco);
+ }
+}
+
+/*
+ * "bufloaded(expr)" function
+ */
+static void f_bufloaded(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ buf_T *buf;
+
+ buf = find_buffer(&argvars[0]);
+ rettv->vval.v_number = (buf != NULL && buf->b_ml.ml_mfp != NULL);
+}
+
+/*
+ * "bufname(expr)" function
+ */
+static void f_bufname(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const buf_T *buf;
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = NULL;
+ if (argvars[0].v_type == VAR_UNKNOWN) {
+ buf = curbuf;
+ } else {
+ if (!tv_check_str_or_nr(&argvars[0])) {
+ return;
+ }
+ emsg_off++;
+ buf = tv_get_buf(&argvars[0], false);
+ emsg_off--;
+ }
+ if (buf != NULL && buf->b_fname != NULL) {
+ rettv->vval.v_string = (char_u *)xstrdup((char *)buf->b_fname);
+ }
+}
+
+/*
+ * "bufnr(expr)" function
+ */
+static void f_bufnr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const buf_T *buf;
+ bool error = false;
+
+ rettv->vval.v_number = -1;
+
+ if (argvars[0].v_type == VAR_UNKNOWN) {
+ buf = curbuf;
+ } else {
+ if (!tv_check_str_or_nr(&argvars[0])) {
+ return;
+ }
+ emsg_off++;
+ buf = tv_get_buf(&argvars[0], false);
+ emsg_off--;
+ }
+
+ // If the buffer isn't found and the second argument is not zero create a
+ // new buffer.
+ const char *name;
+ if (buf == NULL
+ && argvars[1].v_type != VAR_UNKNOWN
+ && tv_get_number_chk(&argvars[1], &error) != 0
+ && !error
+ && (name = tv_get_string_chk(&argvars[0])) != NULL) {
+ buf = buflist_new((char_u *)name, NULL, 1, 0);
+ }
+
+ if (buf != NULL) {
+ rettv->vval.v_number = buf->b_fnum;
+ }
+}
+
+static void buf_win_common(typval_T *argvars, typval_T *rettv, bool get_nr)
+{
+ if (!tv_check_str_or_nr(&argvars[0])) {
+ rettv->vval.v_number = -1;
+ return;
+ }
+
+ emsg_off++;
+ buf_T *buf = tv_get_buf(&argvars[0], true);
+ if (buf == NULL) { // no need to search if buffer was not found
+ rettv->vval.v_number = -1;
+ goto end;
+ }
+
+ int winnr = 0;
+ int winid;
+ bool found_buf = false;
+ FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
+ winnr++;
+ if (wp->w_buffer == buf) {
+ found_buf = true;
+ winid = wp->handle;
+ break;
+ }
+ }
+ rettv->vval.v_number = (found_buf ? (get_nr ? winnr : winid) : -1);
+end:
+ emsg_off--;
+}
+
+/// "bufwinid(nr)" function
+static void f_bufwinid(typval_T *argvars, typval_T *rettv, FunPtr fptr) {
+ buf_win_common(argvars, rettv, false);
+}
+
+/// "bufwinnr(nr)" function
+static void f_bufwinnr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ buf_win_common(argvars, rettv, true);
+}
+
+/*
+ * Get buffer by number or pattern.
+ */
+buf_T *tv_get_buf(typval_T *tv, int curtab_only)
+{
+ char_u *name = tv->vval.v_string;
+ int save_magic;
+ char_u *save_cpo;
+ buf_T *buf;
+
+ if (tv->v_type == VAR_NUMBER)
+ return buflist_findnr((int)tv->vval.v_number);
+ if (tv->v_type != VAR_STRING)
+ return NULL;
+ if (name == NULL || *name == NUL)
+ return curbuf;
+ if (name[0] == '$' && name[1] == NUL)
+ return lastbuf;
+
+ // Ignore 'magic' and 'cpoptions' here to make scripts portable
+ save_magic = p_magic;
+ p_magic = TRUE;
+ save_cpo = p_cpo;
+ p_cpo = (char_u *)"";
+
+ buf = buflist_findnr(buflist_findpat(name, name + STRLEN(name),
+ TRUE, FALSE, curtab_only));
+
+ p_magic = save_magic;
+ p_cpo = save_cpo;
+
+ // If not found, try expanding the name, like done for bufexists().
+ if (buf == NULL) {
+ buf = find_buffer(tv);
+ }
+
+ return buf;
+}
+
+/// Get the buffer from "arg" and give an error and return NULL if it is not
+/// valid.
+buf_T * get_buf_arg(typval_T *arg)
+{
+ buf_T *buf;
+
+ emsg_off++;
+ buf = tv_get_buf(arg, false);
+ emsg_off--;
+ if (buf == NULL) {
+ EMSG2(_("E158: Invalid buffer name: %s"), tv_get_string(arg));
+ }
+ return buf;
+}
+
+/*
+ * "byte2line(byte)" function
+ */
+static void f_byte2line(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ long boff = tv_get_number(&argvars[0]) - 1;
+ if (boff < 0) {
+ rettv->vval.v_number = -1;
+ } else {
+ rettv->vval.v_number = (varnumber_T)ml_find_line_or_offset(curbuf, 0,
+ &boff, false);
+ }
+}
+
+static void byteidx(typval_T *argvars, typval_T *rettv, int comp)
+{
+ const char *const str = tv_get_string_chk(&argvars[0]);
+ varnumber_T idx = tv_get_number_chk(&argvars[1], NULL);
+ rettv->vval.v_number = -1;
+ if (str == NULL || idx < 0) {
+ return;
+ }
+
+ const char *t = str;
+ for (; idx > 0; idx--) {
+ if (*t == NUL) { // EOL reached.
+ return;
+ }
+ if (enc_utf8 && comp) {
+ t += utf_ptr2len((const char_u *)t);
+ } else {
+ t += (*mb_ptr2len)((const char_u *)t);
+ }
+ }
+ rettv->vval.v_number = (varnumber_T)(t - str);
+}
+
+/*
+ * "byteidx()" function
+ */
+static void f_byteidx(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ byteidx(argvars, rettv, FALSE);
+}
+
+/*
+ * "byteidxcomp()" function
+ */
+static void f_byteidxcomp(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ byteidx(argvars, rettv, TRUE);
+}
+
+/// "call(func, arglist [, dict])" function
+static void f_call(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ if (argvars[1].v_type != VAR_LIST) {
+ EMSG(_(e_listreq));
+ return;
+ }
+ if (argvars[1].vval.v_list == NULL) {
+ return;
+ }
+
+ char_u *func;
+ partial_T *partial = NULL;
+ dict_T *selfdict = NULL;
+ if (argvars[0].v_type == VAR_FUNC) {
+ func = argvars[0].vval.v_string;
+ } else if (argvars[0].v_type == VAR_PARTIAL) {
+ partial = argvars[0].vval.v_partial;
+ func = partial_name(partial);
+ } else if (nlua_is_table_from_lua(&argvars[0])) {
+ func = nlua_register_table_as_callable(&argvars[0]);
+ } else {
+ func = (char_u *)tv_get_string(&argvars[0]);
+ }
+
+ if (*func == NUL) {
+ return; // type error or empty name
+ }
+
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ if (argvars[2].v_type != VAR_DICT) {
+ EMSG(_(e_dictreq));
+ return;
+ }
+ selfdict = argvars[2].vval.v_dict;
+ }
+
+ func_call(func, &argvars[1], partial, selfdict, rettv);
+}
+
+/*
+ * "changenr()" function
+ */
+static void f_changenr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = curbuf->b_u_seq_cur;
+}
+
+// "chanclose(id[, stream])" function
+static void f_chanclose(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->v_type = VAR_NUMBER;
+ rettv->vval.v_number = 0;
+
+ if (check_restricted() || check_secure()) {
+ return;
+ }
+
+ if (argvars[0].v_type != VAR_NUMBER || (argvars[1].v_type != VAR_STRING
+ && argvars[1].v_type != VAR_UNKNOWN)) {
+ EMSG(_(e_invarg));
+ return;
+ }
+
+ ChannelPart part = kChannelPartAll;
+ if (argvars[1].v_type == VAR_STRING) {
+ char *stream = (char *)argvars[1].vval.v_string;
+ if (!strcmp(stream, "stdin")) {
+ part = kChannelPartStdin;
+ } else if (!strcmp(stream, "stdout")) {
+ part = kChannelPartStdout;
+ } else if (!strcmp(stream, "stderr")) {
+ part = kChannelPartStderr;
+ } else if (!strcmp(stream, "rpc")) {
+ part = kChannelPartRpc;
+ } else {
+ EMSG2(_("Invalid channel stream \"%s\""), stream);
+ return;
+ }
+ }
+ const char *error;
+ rettv->vval.v_number = channel_close(argvars[0].vval.v_number, part, &error);
+ if (!rettv->vval.v_number) {
+ EMSG(error);
+ }
+}
+
+// "chansend(id, data)" function
+static void f_chansend(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->v_type = VAR_NUMBER;
+ rettv->vval.v_number = 0;
+
+ if (check_restricted() || check_secure()) {
+ return;
+ }
+
+ if (argvars[0].v_type != VAR_NUMBER || argvars[1].v_type == VAR_UNKNOWN) {
+ // First argument is the channel id and second is the data to write
+ EMSG(_(e_invarg));
+ return;
+ }
+
+ ptrdiff_t input_len = 0;
+ char *input = save_tv_as_string(&argvars[1], &input_len, false);
+ if (!input) {
+ // Either the error has been handled by save_tv_as_string(),
+ // or there is no input to send.
+ return;
+ }
+ uint64_t id = argvars[0].vval.v_number;
+ const char *error = NULL;
+ rettv->vval.v_number = channel_send(id, input, input_len, &error);
+ if (error) {
+ EMSG(error);
+ }
+}
+
+/*
+ * "char2nr(string)" function
+ */
+static void f_char2nr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ if (!tv_check_num(&argvars[1])) {
+ return;
+ }
+ }
+
+ rettv->vval.v_number = utf_ptr2char(
+ (const char_u *)tv_get_string(&argvars[0]));
+}
+
+/*
+ * "cindent(lnum)" function
+ */
+static void f_cindent(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ pos_T pos;
+ linenr_T lnum;
+
+ pos = curwin->w_cursor;
+ lnum = tv_get_lnum(argvars);
+ if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count) {
+ curwin->w_cursor.lnum = lnum;
+ rettv->vval.v_number = get_c_indent();
+ curwin->w_cursor = pos;
+ } else
+ rettv->vval.v_number = -1;
+}
+
+static win_T * get_optional_window(typval_T *argvars, int idx)
+{
+ win_T *win = curwin;
+
+ if (argvars[idx].v_type != VAR_UNKNOWN) {
+ win = find_win_by_nr_or_id(&argvars[idx]);
+ if (win == NULL) {
+ EMSG(_(e_invalwindow));
+ return NULL;
+ }
+ }
+ return win;
+}
+
+/*
+ * "clearmatches()" function
+ */
+static void f_clearmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ win_T *win = get_optional_window(argvars, 0);
+
+ if (win != NULL) {
+ clear_matches(win);
+ }
+}
+
+/*
+ * "col(string)" function
+ */
+static void f_col(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ colnr_T col = 0;
+ pos_T *fp;
+ int fnum = curbuf->b_fnum;
+
+ fp = var2fpos(&argvars[0], FALSE, &fnum);
+ if (fp != NULL && fnum == curbuf->b_fnum) {
+ if (fp->col == MAXCOL) {
+ // '> can be MAXCOL, get the length of the line then
+ if (fp->lnum <= curbuf->b_ml.ml_line_count) {
+ col = (colnr_T)STRLEN(ml_get(fp->lnum)) + 1;
+ } else {
+ col = MAXCOL;
+ }
+ } else {
+ col = fp->col + 1;
+ // col(".") when the cursor is on the NUL at the end of the line
+ // because of "coladd" can be seen as an extra column.
+ if (virtual_active() && fp == &curwin->w_cursor) {
+ char_u *p = get_cursor_pos_ptr();
+
+ if (curwin->w_cursor.coladd >= (colnr_T)chartabsize(p,
+ curwin->w_virtcol - curwin->w_cursor.coladd)) {
+ int l;
+
+ if (*p != NUL && p[(l = (*mb_ptr2len)(p))] == NUL)
+ col += l;
+ }
+ }
+ }
+ }
+ rettv->vval.v_number = col;
+}
+
+/*
+ * "complete()" function
+ */
+static void f_complete(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ if ((State & INSERT) == 0) {
+ EMSG(_("E785: complete() can only be used in Insert mode"));
+ return;
+ }
+
+ /* Check for undo allowed here, because if something was already inserted
+ * the line was already saved for undo and this check isn't done. */
+ if (!undo_allowed())
+ return;
+
+ if (argvars[1].v_type != VAR_LIST) {
+ EMSG(_(e_invarg));
+ return;
+ }
+
+ const colnr_T startcol = tv_get_number_chk(&argvars[0], NULL);
+ if (startcol <= 0) {
+ return;
+ }
+
+ set_completion(startcol - 1, argvars[1].vval.v_list);
+}
+
+/*
+ * "complete_add()" function
+ */
+static void f_complete_add(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = ins_compl_add_tv(&argvars[0], 0);
+}
+
+/*
+ * "complete_check()" function
+ */
+static void f_complete_check(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ int saved = RedrawingDisabled;
+
+ RedrawingDisabled = 0;
+ ins_compl_check_keys(0, true);
+ rettv->vval.v_number = compl_interrupted;
+ RedrawingDisabled = saved;
+}
+
+// "complete_info()" function
+static void f_complete_info(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ tv_dict_alloc_ret(rettv);
+
+ list_T *what_list = NULL;
+
+ if (argvars[0].v_type != VAR_UNKNOWN) {
+ if (argvars[0].v_type != VAR_LIST) {
+ EMSG(_(e_listreq));
+ return;
+ }
+ what_list = argvars[0].vval.v_list;
+ }
+ get_complete_info(what_list, rettv->vval.v_dict);
+}
+
+/*
+ * "confirm(message, buttons[, default [, type]])" function
+ */
+static void f_confirm(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ char buf[NUMBUFLEN];
+ char buf2[NUMBUFLEN];
+ const char *message;
+ const char *buttons = NULL;
+ int def = 1;
+ int type = VIM_GENERIC;
+ const char *typestr;
+ bool error = false;
+
+ message = tv_get_string_chk(&argvars[0]);
+ if (message == NULL) {
+ error = true;
+ }
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ buttons = tv_get_string_buf_chk(&argvars[1], buf);
+ if (buttons == NULL) {
+ error = true;
+ }
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ def = tv_get_number_chk(&argvars[2], &error);
+ if (argvars[3].v_type != VAR_UNKNOWN) {
+ typestr = tv_get_string_buf_chk(&argvars[3], buf2);
+ if (typestr == NULL) {
+ error = true;
+ } else {
+ switch (TOUPPER_ASC(*typestr)) {
+ case 'E': type = VIM_ERROR; break;
+ case 'Q': type = VIM_QUESTION; break;
+ case 'I': type = VIM_INFO; break;
+ case 'W': type = VIM_WARNING; break;
+ case 'G': type = VIM_GENERIC; break;
+ }
+ }
+ }
+ }
+ }
+
+ if (buttons == NULL || *buttons == NUL) {
+ buttons = _("&Ok");
+ }
+
+ if (!error) {
+ rettv->vval.v_number = do_dialog(
+ type, NULL, (char_u *)message, (char_u *)buttons, def, NULL, false);
+ }
+}
+
+/*
+ * "copy()" function
+ */
+static void f_copy(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ var_item_copy(NULL, &argvars[0], rettv, false, 0);
+}
+
+/*
+ * "count()" function
+ */
+static void f_count(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ long n = 0;
+ int ic = 0;
+ bool error = false;
+
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ ic = tv_get_number_chk(&argvars[2], &error);
+ }
+
+ if (argvars[0].v_type == VAR_STRING) {
+ const char_u *expr = (char_u *)tv_get_string_chk(&argvars[1]);
+ const char_u *p = argvars[0].vval.v_string;
+
+ if (!error && expr != NULL && *expr != NUL && p != NULL) {
+ if (ic) {
+ const size_t len = STRLEN(expr);
+
+ while (*p != NUL) {
+ if (mb_strnicmp(p, expr, len) == 0) {
+ n++;
+ p += len;
+ } else {
+ MB_PTR_ADV(p);
+ }
+ }
+ } else {
+ char_u *next;
+ while ((next = (char_u *)strstr((char *)p, (char *)expr)) != NULL) {
+ n++;
+ p = next + STRLEN(expr);
+ }
+ }
+ }
+ } else if (argvars[0].v_type == VAR_LIST) {
+ listitem_T *li;
+ list_T *l;
+ long idx;
+
+ if ((l = argvars[0].vval.v_list) != NULL) {
+ li = tv_list_first(l);
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ if (argvars[3].v_type != VAR_UNKNOWN) {
+ idx = tv_get_number_chk(&argvars[3], &error);
+ if (!error) {
+ li = tv_list_find(l, idx);
+ if (li == NULL) {
+ EMSGN(_(e_listidx), idx);
+ }
+ }
+ }
+ if (error)
+ li = NULL;
+ }
+
+ for (; li != NULL; li = TV_LIST_ITEM_NEXT(l, li)) {
+ if (tv_equal(TV_LIST_ITEM_TV(li), &argvars[1], ic, false)) {
+ n++;
+ }
+ }
+ }
+ } else if (argvars[0].v_type == VAR_DICT) {
+ int todo;
+ dict_T *d;
+ hashitem_T *hi;
+
+ if ((d = argvars[0].vval.v_dict) != NULL) {
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ if (argvars[3].v_type != VAR_UNKNOWN) {
+ EMSG(_(e_invarg));
+ }
+ }
+
+ todo = error ? 0 : (int)d->dv_hashtab.ht_used;
+ for (hi = d->dv_hashtab.ht_array; todo > 0; ++hi) {
+ if (!HASHITEM_EMPTY(hi)) {
+ todo--;
+ if (tv_equal(&TV_DICT_HI2DI(hi)->di_tv, &argvars[1], ic, false)) {
+ n++;
+ }
+ }
+ }
+ }
+ } else {
+ EMSG2(_(e_listdictarg), "count()");
+ }
+ rettv->vval.v_number = n;
+}
+
+/*
+ * "cscope_connection([{num} , {dbpath} [, {prepend}]])" function
+ *
+ * Checks the existence of a cscope connection.
+ */
+static void f_cscope_connection(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ int num = 0;
+ const char *dbpath = NULL;
+ const char *prepend = NULL;
+ char buf[NUMBUFLEN];
+
+ if (argvars[0].v_type != VAR_UNKNOWN
+ && argvars[1].v_type != VAR_UNKNOWN) {
+ num = (int)tv_get_number(&argvars[0]);
+ dbpath = tv_get_string(&argvars[1]);
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ prepend = tv_get_string_buf(&argvars[2], buf);
+ }
+ }
+
+ rettv->vval.v_number = cs_connection(num, (char_u *)dbpath,
+ (char_u *)prepend);
+}
+
+/// "ctxget([{index}])" function
+static void f_ctxget(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ size_t index = 0;
+ if (argvars[0].v_type == VAR_NUMBER) {
+ index = argvars[0].vval.v_number;
+ } else if (argvars[0].v_type != VAR_UNKNOWN) {
+ EMSG2(_(e_invarg2), "expected nothing or a Number as an argument");
+ return;
+ }
+
+ Context *ctx = ctx_get(index);
+ if (ctx == NULL) {
+ EMSG3(_(e_invargNval), "index", "out of bounds");
+ return;
+ }
+
+ Dictionary ctx_dict = ctx_to_dict(ctx);
+ Error err = ERROR_INIT;
+ object_to_vim(DICTIONARY_OBJ(ctx_dict), rettv, &err);
+ api_free_dictionary(ctx_dict);
+ api_clear_error(&err);
+}
+
+/// "ctxpop()" function
+static void f_ctxpop(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ if (!ctx_restore(NULL, kCtxAll)) {
+ EMSG(_("Context stack is empty"));
+ }
+}
+
+/// "ctxpush([{types}])" function
+static void f_ctxpush(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ int types = kCtxAll;
+ if (argvars[0].v_type == VAR_LIST) {
+ types = 0;
+ TV_LIST_ITER(argvars[0].vval.v_list, li, {
+ typval_T *tv_li = TV_LIST_ITEM_TV(li);
+ if (tv_li->v_type == VAR_STRING) {
+ if (strequal((char *)tv_li->vval.v_string, "regs")) {
+ types |= kCtxRegs;
+ } else if (strequal((char *)tv_li->vval.v_string, "jumps")) {
+ types |= kCtxJumps;
+ } else if (strequal((char *)tv_li->vval.v_string, "bufs")) {
+ types |= kCtxBufs;
+ } else if (strequal((char *)tv_li->vval.v_string, "gvars")) {
+ types |= kCtxGVars;
+ } else if (strequal((char *)tv_li->vval.v_string, "sfuncs")) {
+ types |= kCtxSFuncs;
+ } else if (strequal((char *)tv_li->vval.v_string, "funcs")) {
+ types |= kCtxFuncs;
+ }
+ }
+ });
+ } else if (argvars[0].v_type != VAR_UNKNOWN) {
+ EMSG2(_(e_invarg2), "expected nothing or a List as an argument");
+ return;
+ }
+ ctx_save(NULL, types);
+}
+
+/// "ctxset({context}[, {index}])" function
+static void f_ctxset(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ if (argvars[0].v_type != VAR_DICT) {
+ EMSG2(_(e_invarg2), "expected dictionary as first argument");
+ return;
+ }
+
+ size_t index = 0;
+ if (argvars[1].v_type == VAR_NUMBER) {
+ index = argvars[1].vval.v_number;
+ } else if (argvars[1].v_type != VAR_UNKNOWN) {
+ EMSG2(_(e_invarg2), "expected nothing or a Number as second argument");
+ return;
+ }
+
+ Context *ctx = ctx_get(index);
+ if (ctx == NULL) {
+ EMSG3(_(e_invargNval), "index", "out of bounds");
+ return;
+ }
+
+ int save_did_emsg = did_emsg;
+ did_emsg = false;
+
+ Dictionary dict = vim_to_object(&argvars[0]).data.dictionary;
+ Context tmp = CONTEXT_INIT;
+ ctx_from_dict(dict, &tmp);
+
+ if (did_emsg) {
+ ctx_free(&tmp);
+ } else {
+ ctx_free(ctx);
+ *ctx = tmp;
+ }
+
+ api_free_dictionary(dict);
+ did_emsg = save_did_emsg;
+}
+
+/// "ctxsize()" function
+static void f_ctxsize(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->v_type = VAR_NUMBER;
+ rettv->vval.v_number = ctx_size();
+}
+
+/// "cursor(lnum, col)" function, or
+/// "cursor(list)"
+///
+/// Moves the cursor to the specified line and column.
+///
+/// @returns 0 when the position could be set, -1 otherwise.
+static void f_cursor(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ long line, col;
+ long coladd = 0;
+ bool set_curswant = true;
+
+ rettv->vval.v_number = -1;
+ if (argvars[1].v_type == VAR_UNKNOWN) {
+ pos_T pos;
+ colnr_T curswant = -1;
+
+ if (list2fpos(argvars, &pos, NULL, &curswant) == FAIL) {
+ EMSG(_(e_invarg));
+ return;
+ }
+
+ line = pos.lnum;
+ col = pos.col;
+ coladd = pos.coladd;
+ if (curswant >= 0) {
+ curwin->w_curswant = curswant - 1;
+ set_curswant = false;
+ }
+ } else {
+ line = tv_get_lnum(argvars);
+ col = (long)tv_get_number_chk(&argvars[1], NULL);
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ coladd = (long)tv_get_number_chk(&argvars[2], NULL);
+ }
+ }
+ if (line < 0 || col < 0
+ || coladd < 0) {
+ return; // type error; errmsg already given
+ }
+ if (line > 0) {
+ curwin->w_cursor.lnum = line;
+ }
+ if (col > 0) {
+ curwin->w_cursor.col = col - 1;
+ }
+ curwin->w_cursor.coladd = coladd;
+
+ // Make sure the cursor is in a valid position.
+ check_cursor();
+ // Correct cursor for multi-byte character.
+ if (has_mbyte) {
+ mb_adjust_cursor();
+ }
+
+ curwin->w_set_curswant = set_curswant;
+ rettv->vval.v_number = 0;
+}
+
+// "debugbreak()" function
+static void f_debugbreak(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ int pid;
+
+ rettv->vval.v_number = FAIL;
+ pid = (int)tv_get_number(&argvars[0]);
+ if (pid == 0) {
+ EMSG(_(e_invarg));
+ } else {
+#ifdef WIN32
+ HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, 0, pid);
+
+ if (hProcess != NULL) {
+ DebugBreakProcess(hProcess);
+ CloseHandle(hProcess);
+ rettv->vval.v_number = OK;
+ }
+#else
+ uv_kill(pid, SIGINT);
+#endif
+ }
+}
+
+// "deepcopy()" function
+static void f_deepcopy(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ int noref = 0;
+
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ noref = tv_get_number_chk(&argvars[1], NULL);
+ }
+ if (noref < 0 || noref > 1) {
+ EMSG(_(e_invarg));
+ } else {
+ var_item_copy(NULL, &argvars[0], rettv, true, (noref == 0
+ ? get_copyID()
+ : 0));
+ }
+}
+
+// "delete()" function
+static void f_delete(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = -1;
+ if (check_restricted() || check_secure()) {
+ return;
+ }
+
+ const char *const name = tv_get_string(&argvars[0]);
+ if (*name == NUL) {
+ EMSG(_(e_invarg));
+ return;
+ }
+
+ char nbuf[NUMBUFLEN];
+ const char *flags;
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ flags = tv_get_string_buf(&argvars[1], nbuf);
+ } else {
+ flags = "";
+ }
+
+ if (*flags == NUL) {
+ // delete a file
+ rettv->vval.v_number = os_remove(name) == 0 ? 0 : -1;
+ } else if (strcmp(flags, "d") == 0) {
+ // delete an empty directory
+ rettv->vval.v_number = os_rmdir(name) == 0 ? 0 : -1;
+ } else if (strcmp(flags, "rf") == 0) {
+ // delete a directory recursively
+ rettv->vval.v_number = delete_recursive(name);
+ } else {
+ emsgf(_(e_invexpr2), flags);
+ }
+}
+
+// dictwatcheradd(dict, key, funcref) function
+static void f_dictwatcheradd(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ if (check_restricted() || check_secure()) {
+ return;
+ }
+
+ if (argvars[0].v_type != VAR_DICT) {
+ emsgf(_(e_invarg2), "dict");
+ return;
+ } else if (argvars[0].vval.v_dict == NULL) {
+ const char *const arg_errmsg = _("dictwatcheradd() argument");
+ const size_t arg_errmsg_len = strlen(arg_errmsg);
+ emsgf(_(e_readonlyvar), (int)arg_errmsg_len, arg_errmsg);
+ return;
+ }
+
+ if (argvars[1].v_type != VAR_STRING && argvars[1].v_type != VAR_NUMBER) {
+ emsgf(_(e_invarg2), "key");
+ return;
+ }
+
+ const char *const key_pattern = tv_get_string_chk(argvars + 1);
+ if (key_pattern == NULL) {
+ return;
+ }
+ const size_t key_pattern_len = strlen(key_pattern);
+
+ Callback callback;
+ if (!callback_from_typval(&callback, &argvars[2])) {
+ emsgf(_(e_invarg2), "funcref");
+ return;
+ }
+
+ tv_dict_watcher_add(argvars[0].vval.v_dict, key_pattern, key_pattern_len,
+ callback);
+}
+
+// dictwatcherdel(dict, key, funcref) function
+static void f_dictwatcherdel(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ if (check_restricted() || check_secure()) {
+ return;
+ }
+
+ if (argvars[0].v_type != VAR_DICT) {
+ emsgf(_(e_invarg2), "dict");
+ return;
+ }
+
+ if (argvars[2].v_type != VAR_FUNC && argvars[2].v_type != VAR_STRING) {
+ emsgf(_(e_invarg2), "funcref");
+ return;
+ }
+
+ const char *const key_pattern = tv_get_string_chk(argvars + 1);
+ if (key_pattern == NULL) {
+ return;
+ }
+
+ Callback callback;
+ if (!callback_from_typval(&callback, &argvars[2])) {
+ return;
+ }
+
+ if (!tv_dict_watcher_remove(argvars[0].vval.v_dict, key_pattern,
+ strlen(key_pattern), callback)) {
+ EMSG("Couldn't find a watcher matching key and callback");
+ }
+
+ callback_free(&callback);
+}
+
+/// "deletebufline()" function
+static void f_deletebufline(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ linenr_T last;
+ buf_T *curbuf_save = NULL;
+ win_T *curwin_save = NULL;
+
+ buf_T *const buf = tv_get_buf(&argvars[0], false);
+ if (buf == NULL) {
+ rettv->vval.v_number = 1; // FAIL
+ return;
+ }
+ const bool is_curbuf = buf == curbuf;
+
+ const linenr_T first = tv_get_lnum_buf(&argvars[1], buf);
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ last = tv_get_lnum_buf(&argvars[2], buf);
+ } else {
+ last = first;
+ }
+
+ if (buf->b_ml.ml_mfp == NULL || first < 1
+ || first > buf->b_ml.ml_line_count || last < first) {
+ rettv->vval.v_number = 1; // FAIL
+ return;
+ }
+
+ if (!is_curbuf) {
+ curbuf_save = curbuf;
+ curwin_save = curwin;
+ curbuf = buf;
+ find_win_for_curbuf();
+ }
+ if (last > curbuf->b_ml.ml_line_count) {
+ last = curbuf->b_ml.ml_line_count;
+ }
+ const long count = last - first + 1;
+
+ // When coming here from Insert mode, sync undo, so that this can be
+ // undone separately from what was previously inserted.
+ if (u_sync_once == 2) {
+ u_sync_once = 1; // notify that u_sync() was called
+ u_sync(true);
+ }
+
+ if (u_save(first - 1, last + 1) == FAIL) {
+ rettv->vval.v_number = 1; // FAIL
+ return;
+ }
+
+ for (linenr_T lnum = first; lnum <= last; lnum++) {
+ ml_delete(first, true);
+ }
+
+ FOR_ALL_TAB_WINDOWS(tp, wp) {
+ if (wp->w_buffer == buf) {
+ if (wp->w_cursor.lnum > last) {
+ wp->w_cursor.lnum -= count;
+ } else if (wp->w_cursor.lnum> first) {
+ wp->w_cursor.lnum = first;
+ }
+ if (wp->w_cursor.lnum > wp->w_buffer->b_ml.ml_line_count) {
+ wp->w_cursor.lnum = wp->w_buffer->b_ml.ml_line_count;
+ }
+ }
+ }
+ check_cursor_col();
+ deleted_lines_mark(first, count);
+
+ if (!is_curbuf) {
+ curbuf = curbuf_save;
+ curwin = curwin_save;
+ }
+}
+
+/*
+ * "did_filetype()" function
+ */
+static void f_did_filetype(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = did_filetype;
+}
+
+/*
+ * "diff_filler()" function
+ */
+static void f_diff_filler(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = diff_check_fill(curwin, tv_get_lnum(argvars));
+}
+
+/*
+ * "diff_hlID()" function
+ */
+static void f_diff_hlID(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ linenr_T lnum = tv_get_lnum(argvars);
+ static linenr_T prev_lnum = 0;
+ static int changedtick = 0;
+ static int fnum = 0;
+ static int change_start = 0;
+ static int change_end = 0;
+ static hlf_T hlID = (hlf_T)0;
+ int filler_lines;
+ int col;
+
+ if (lnum < 0) { // ignore type error in {lnum} arg
+ lnum = 0;
+ }
+ if (lnum != prev_lnum
+ || changedtick != buf_get_changedtick(curbuf)
+ || fnum != curbuf->b_fnum) {
+ // New line, buffer, change: need to get the values.
+ filler_lines = diff_check(curwin, lnum);
+ if (filler_lines < 0) {
+ if (filler_lines == -1) {
+ change_start = MAXCOL;
+ change_end = -1;
+ if (diff_find_change(curwin, lnum, &change_start, &change_end)) {
+ hlID = HLF_ADD; // added line
+ } else {
+ hlID = HLF_CHD; // changed line
+ }
+ } else {
+ hlID = HLF_ADD; // added line
+ }
+ } else {
+ hlID = (hlf_T)0;
+ }
+ prev_lnum = lnum;
+ changedtick = buf_get_changedtick(curbuf);
+ fnum = curbuf->b_fnum;
+ }
+
+ if (hlID == HLF_CHD || hlID == HLF_TXD) {
+ col = tv_get_number(&argvars[1]) - 1; // Ignore type error in {col}.
+ if (col >= change_start && col <= change_end) {
+ hlID = HLF_TXD; // Changed text.
+ } else {
+ hlID = HLF_CHD; // Changed line.
+ }
+ }
+ rettv->vval.v_number = hlID == (hlf_T)0 ? 0 : (int)(hlID + 1);
+}
+
+/*
+ * "empty({expr})" function
+ */
+static void f_empty(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ bool n = true;
+
+ switch (argvars[0].v_type) {
+ case VAR_STRING:
+ case VAR_FUNC: {
+ n = argvars[0].vval.v_string == NULL
+ || *argvars[0].vval.v_string == NUL;
+ break;
+ }
+ case VAR_PARTIAL: {
+ n = false;
+ break;
+ }
+ case VAR_NUMBER: {
+ n = argvars[0].vval.v_number == 0;
+ break;
+ }
+ case VAR_FLOAT: {
+ n = argvars[0].vval.v_float == 0.0;
+ break;
+ }
+ case VAR_LIST: {
+ n = (tv_list_len(argvars[0].vval.v_list) == 0);
+ break;
+ }
+ case VAR_DICT: {
+ n = (tv_dict_len(argvars[0].vval.v_dict) == 0);
+ break;
+ }
+ case VAR_BOOL: {
+ switch (argvars[0].vval.v_bool) {
+ case kBoolVarTrue: {
+ n = false;
+ break;
+ }
+ case kBoolVarFalse: {
+ n = true;
+ break;
+ }
+ }
+ break;
+ }
+ case VAR_SPECIAL: {
+ n = argvars[0].vval.v_special == kSpecialVarNull;
+ break;
+ }
+ case VAR_UNKNOWN: {
+ internal_error("f_empty(UNKNOWN)");
+ break;
+ }
+ }
+
+ rettv->vval.v_number = n;
+}
+
+/// "environ()" function
+static void f_environ(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ tv_dict_alloc_ret(rettv);
+
+ size_t env_size = os_get_fullenv_size();
+ char **env = xmalloc(sizeof(*env) * (env_size + 1));
+ env[env_size] = NULL;
+
+ os_copy_fullenv(env, env_size);
+
+ for (size_t i = 0; i < env_size; i++) {
+ const char * str = env[i];
+ const char * const end = strchr(str + (str[0] == '=' ? 1 : 0),
+ '=');
+ assert(end != NULL);
+ ptrdiff_t len = end - str;
+ assert(len > 0);
+ const char * value = str + len + 1;
+ tv_dict_add_str(rettv->vval.v_dict,
+ str, len,
+ value);
+ }
+ os_free_fullenv(env);
+}
+
+/*
+ * "escape({string}, {chars})" function
+ */
+static void f_escape(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ char buf[NUMBUFLEN];
+
+ rettv->vval.v_string = vim_strsave_escaped(
+ (const char_u *)tv_get_string(&argvars[0]),
+ (const char_u *)tv_get_string_buf(&argvars[1], buf));
+ rettv->v_type = VAR_STRING;
+}
+
+/// "getenv()" function
+static void f_getenv(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ char_u *p = (char_u *)vim_getenv(tv_get_string(&argvars[0]));
+
+ if (p == NULL) {
+ rettv->v_type = VAR_SPECIAL;
+ rettv->vval.v_special = kSpecialVarNull;
+ return;
+ }
+ rettv->vval.v_string = p;
+ rettv->v_type = VAR_STRING;
+}
+
+/*
+ * "eval()" function
+ */
+static void f_eval(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const char *s = tv_get_string_chk(&argvars[0]);
+ if (s != NULL) {
+ s = (const char *)skipwhite((const char_u *)s);
+ }
+
+ const char *const expr_start = s;
+ if (s == NULL || eval1((char_u **)&s, rettv, true) == FAIL) {
+ if (expr_start != NULL && !aborting()) {
+ EMSG2(_(e_invexpr2), expr_start);
+ }
+ need_clr_eos = FALSE;
+ rettv->v_type = VAR_NUMBER;
+ rettv->vval.v_number = 0;
+ } else if (*s != NUL) {
+ EMSG(_(e_trailing));
+ }
+}
+
+/*
+ * "eventhandler()" function
+ */
+static void f_eventhandler(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = vgetc_busy;
+}
+
+/*
+ * "executable()" function
+ */
+static void f_executable(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const char *name = tv_get_string(&argvars[0]);
+
+ // Check in $PATH and also check directly if there is a directory name
+ rettv->vval.v_number = os_can_exe(name, NULL, true);
+}
+
+typedef struct {
+ const list_T *const l;
+ const listitem_T *li;
+} GetListLineCookie;
+
+static char_u *get_list_line(int c, void *cookie, int indent, bool do_concat)
+{
+ GetListLineCookie *const p = (GetListLineCookie *)cookie;
+
+ const listitem_T *const item = p->li;
+ if (item == NULL) {
+ return NULL;
+ }
+ char buf[NUMBUFLEN];
+ const char *const s = tv_get_string_buf_chk(TV_LIST_ITEM_TV(item), buf);
+ p->li = TV_LIST_ITEM_NEXT(p->l, item);
+ return (char_u *)(s == NULL ? NULL : xstrdup(s));
+}
+
+// "execute(command)" function
+static void f_execute(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const int save_msg_silent = msg_silent;
+ const int save_emsg_silent = emsg_silent;
+ const bool save_emsg_noredir = emsg_noredir;
+ const bool save_redir_off = redir_off;
+ garray_T *const save_capture_ga = capture_ga;
+ const int save_msg_col = msg_col;
+ bool echo_output = false;
+
+ if (check_secure()) {
+ return;
+ }
+
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ char buf[NUMBUFLEN];
+ const char *const s = tv_get_string_buf_chk(&argvars[1], buf);
+
+ if (s == NULL) {
+ return;
+ }
+ if (*s == NUL) {
+ echo_output = true;
+ }
+ if (strncmp(s, "silent", 6) == 0) {
+ msg_silent++;
+ }
+ if (strcmp(s, "silent!") == 0) {
+ emsg_silent = true;
+ emsg_noredir = true;
+ }
+ } else {
+ msg_silent++;
+ }
+
+ garray_T capture_local;
+ ga_init(&capture_local, (int)sizeof(char), 80);
+ capture_ga = &capture_local;
+ redir_off = false;
+ if (!echo_output) {
+ msg_col = 0; // prevent leading spaces
+ }
+
+ if (argvars[0].v_type != VAR_LIST) {
+ do_cmdline_cmd(tv_get_string(&argvars[0]));
+ } else if (argvars[0].vval.v_list != NULL) {
+ list_T *const list = argvars[0].vval.v_list;
+ tv_list_ref(list);
+ GetListLineCookie cookie = {
+ .l = list,
+ .li = tv_list_first(list),
+ };
+ do_cmdline(NULL, get_list_line, (void *)&cookie,
+ DOCMD_NOWAIT|DOCMD_VERBOSE|DOCMD_REPEAT|DOCMD_KEYTYPED);
+ tv_list_unref(list);
+ }
+ msg_silent = save_msg_silent;
+ emsg_silent = save_emsg_silent;
+ emsg_noredir = save_emsg_noredir;
+ redir_off = save_redir_off;
+ // "silent reg" or "silent echo x" leaves msg_col somewhere in the line.
+ if (echo_output) {
+ // When not working silently: put it in column zero. A following
+ // "echon" will overwrite the message, unavoidably.
+ msg_col = 0;
+ } else {
+ // When working silently: Put it back where it was, since nothing
+ // should have been written.
+ msg_col = save_msg_col;
+ }
+
+ ga_append(capture_ga, NUL);
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = capture_ga->ga_data;
+
+ capture_ga = save_capture_ga;
+}
+
+/// "exepath()" function
+static void f_exepath(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const char *arg = tv_get_string(&argvars[0]);
+ char *path = NULL;
+
+ (void)os_can_exe(arg, &path, true);
+
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = (char_u *)path;
+}
+
+/*
+ * "exists()" function
+ */
+static void f_exists(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ int n = false;
+ int len = 0;
+
+ const char *p = tv_get_string(&argvars[0]);
+ if (*p == '$') { // Environment variable.
+ // First try "normal" environment variables (fast).
+ if (os_env_exists(p + 1)) {
+ n = true;
+ } else {
+ // Try expanding things like $VIM and ${HOME}.
+ char_u *const exp = expand_env_save((char_u *)p);
+ if (exp != NULL && *exp != '$') {
+ n = true;
+ }
+ xfree(exp);
+ }
+ } else if (*p == '&' || *p == '+') { // Option.
+ n = (get_option_tv(&p, NULL, true) == OK);
+ if (*skipwhite((const char_u *)p) != NUL) {
+ n = false; // Trailing garbage.
+ }
+ } else if (*p == '*') { // Internal or user defined function.
+ n = function_exists(p + 1, false);
+ } else if (*p == ':') {
+ n = cmd_exists(p + 1);
+ } else if (*p == '#') {
+ if (p[1] == '#') {
+ n = autocmd_supported(p + 2);
+ } else {
+ n = au_exists(p + 1);
+ }
+ } else { // Internal variable.
+ typval_T tv;
+
+ // get_name_len() takes care of expanding curly braces
+ const char *name = p;
+ char *tofree;
+ len = get_name_len((const char **)&p, &tofree, true, false);
+ if (len > 0) {
+ if (tofree != NULL) {
+ name = tofree;
+ }
+ n = (get_var_tv(name, len, &tv, NULL, false, true) == OK);
+ if (n) {
+ // Handle d.key, l[idx], f(expr).
+ n = (handle_subscript(&p, &tv, true, false) == OK);
+ if (n) {
+ tv_clear(&tv);
+ }
+ }
+ }
+ if (*p != NUL)
+ n = FALSE;
+
+ xfree(tofree);
+ }
+
+ rettv->vval.v_number = n;
+}
+
+/*
+ * "expand()" function
+ */
+static void f_expand(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ size_t len;
+ char_u *errormsg;
+ int options = WILD_SILENT|WILD_USE_NL|WILD_LIST_NOTFOUND;
+ expand_T xpc;
+ bool error = false;
+ char_u *result;
+
+ rettv->v_type = VAR_STRING;
+ if (argvars[1].v_type != VAR_UNKNOWN
+ && argvars[2].v_type != VAR_UNKNOWN
+ && tv_get_number_chk(&argvars[2], &error)
+ && !error) {
+ tv_list_set_ret(rettv, NULL);
+ }
+
+ const char *s = tv_get_string(&argvars[0]);
+ if (*s == '%' || *s == '#' || *s == '<') {
+ emsg_off++;
+ result = eval_vars((char_u *)s, (char_u *)s, &len, NULL, &errormsg, NULL);
+ emsg_off--;
+ if (rettv->v_type == VAR_LIST) {
+ tv_list_alloc_ret(rettv, (result != NULL));
+ if (result != NULL) {
+ tv_list_append_string(rettv->vval.v_list, (const char *)result, -1);
+ }
+ XFREE_CLEAR(result);
+ } else {
+ rettv->vval.v_string = result;
+ }
+ } else {
+ // When the optional second argument is non-zero, don't remove matches
+ // for 'wildignore' and don't put matches for 'suffixes' at the end.
+ if (argvars[1].v_type != VAR_UNKNOWN
+ && tv_get_number_chk(&argvars[1], &error)) {
+ options |= WILD_KEEP_ALL;
+ }
+ if (!error) {
+ ExpandInit(&xpc);
+ xpc.xp_context = EXPAND_FILES;
+ if (p_wic) {
+ options += WILD_ICASE;
+ }
+ if (rettv->v_type == VAR_STRING) {
+ rettv->vval.v_string = ExpandOne(&xpc, (char_u *)s, NULL, options,
+ WILD_ALL);
+ } else {
+ ExpandOne(&xpc, (char_u *)s, NULL, options, WILD_ALL_KEEP);
+ tv_list_alloc_ret(rettv, xpc.xp_numfiles);
+ for (int i = 0; i < xpc.xp_numfiles; i++) {
+ tv_list_append_string(rettv->vval.v_list,
+ (const char *)xpc.xp_files[i], -1);
+ }
+ ExpandCleanup(&xpc);
+ }
+ } else {
+ rettv->vval.v_string = NULL;
+ }
+ }
+}
+
+
+/// "menu_get(path [, modes])" function
+static void f_menu_get(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ tv_list_alloc_ret(rettv, kListLenMayKnow);
+ int modes = MENU_ALL_MODES;
+ if (argvars[1].v_type == VAR_STRING) {
+ const char_u *const strmodes = (char_u *)tv_get_string(&argvars[1]);
+ modes = get_menu_cmd_modes(strmodes, false, NULL, NULL);
+ }
+ menu_get((char_u *)tv_get_string(&argvars[0]), modes, rettv->vval.v_list);
+}
+
+// "expandcmd()" function
+// Expand all the special characters in a command string.
+static void f_expandcmd(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ char_u *errormsg = NULL;
+
+ rettv->v_type = VAR_STRING;
+ char_u *cmdstr = (char_u *)xstrdup(tv_get_string(&argvars[0]));
+
+ exarg_T eap = {
+ .cmd = cmdstr,
+ .arg = cmdstr,
+ .usefilter = false,
+ .nextcmd = NULL,
+ .cmdidx = CMD_USER,
+ };
+ eap.argt |= NOSPC;
+
+ expand_filename(&eap, &cmdstr, &errormsg);
+ if (errormsg != NULL && *errormsg != NUL) {
+ EMSG(errormsg);
+ }
+ rettv->vval.v_string = cmdstr;
+}
+
+
+/// "flatten(list[, {maxdepth}])" function
+static void f_flatten(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ list_T *list;
+ long maxdepth;
+ bool error = false;
+
+ if (argvars[0].v_type != VAR_LIST) {
+ EMSG2(_(e_listarg), "flatten()");
+ return;
+ }
+
+ if (argvars[1].v_type == VAR_UNKNOWN) {
+ maxdepth = 999999;
+ } else {
+ maxdepth = (long)tv_get_number_chk(&argvars[1], &error);
+ if (error) {
+ return;
+ }
+ if (maxdepth < 0) {
+ EMSG(_("E900: maxdepth must be non-negative number"));
+ return;
+ }
+ }
+
+ list = argvars[0].vval.v_list;
+ if (list != NULL
+ && !tv_check_lock(tv_list_locked(list),
+ N_("flatten() argument"),
+ TV_TRANSLATE)
+ && tv_list_flatten(list, maxdepth) == OK) {
+ tv_copy(&argvars[0], rettv);
+ }
+}
+
+/*
+ * "extend(list, list [, idx])" function
+ * "extend(dict, dict [, action])" function
+ */
+static void f_extend(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const char *const arg_errmsg = N_("extend() argument");
+
+ if (argvars[0].v_type == VAR_LIST && argvars[1].v_type == VAR_LIST) {
+ long before;
+ bool error = false;
+
+ list_T *const l1 = argvars[0].vval.v_list;
+ list_T *const l2 = argvars[1].vval.v_list;
+ if (!tv_check_lock(tv_list_locked(l1), arg_errmsg, TV_TRANSLATE)) {
+ listitem_T *item;
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ before = (long)tv_get_number_chk(&argvars[2], &error);
+ if (error) {
+ return; // Type error; errmsg already given.
+ }
+
+ if (before == tv_list_len(l1)) {
+ item = NULL;
+ } else {
+ item = tv_list_find(l1, before);
+ if (item == NULL) {
+ EMSGN(_(e_listidx), before);
+ return;
+ }
+ }
+ } else {
+ item = NULL;
+ }
+ tv_list_extend(l1, l2, item);
+
+ tv_copy(&argvars[0], rettv);
+ }
+ } else if (argvars[0].v_type == VAR_DICT && argvars[1].v_type ==
+ VAR_DICT) {
+ dict_T *const d1 = argvars[0].vval.v_dict;
+ dict_T *const d2 = argvars[1].vval.v_dict;
+ if (d1 == NULL) {
+ const bool locked = tv_check_lock(VAR_FIXED, arg_errmsg, TV_TRANSLATE);
+ (void)locked;
+ assert(locked == true);
+ } else if (d2 == NULL) {
+ // Do nothing
+ tv_copy(&argvars[0], rettv);
+ } else if (!tv_check_lock(d1->dv_lock, arg_errmsg, TV_TRANSLATE)) {
+ const char *action = "force";
+ // Check the third argument.
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ const char *const av[] = { "keep", "force", "error" };
+
+ action = tv_get_string_chk(&argvars[2]);
+ if (action == NULL) {
+ return; // Type error; error message already given.
+ }
+ size_t i;
+ for (i = 0; i < ARRAY_SIZE(av); i++) {
+ if (strcmp(action, av[i]) == 0) {
+ break;
+ }
+ }
+ if (i == 3) {
+ EMSG2(_(e_invarg2), action);
+ return;
+ }
+ }
+
+ tv_dict_extend(d1, d2, action);
+
+ tv_copy(&argvars[0], rettv);
+ }
+ } else {
+ EMSG2(_(e_listdictarg), "extend()");
+ }
+}
+
+/*
+ * "feedkeys()" function
+ */
+static void f_feedkeys(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ // This is not allowed in the sandbox. If the commands would still be
+ // executed in the sandbox it would be OK, but it probably happens later,
+ // when "sandbox" is no longer set.
+ if (check_secure()) {
+ return;
+ }
+
+ const char *const keys = tv_get_string(&argvars[0]);
+ char nbuf[NUMBUFLEN];
+ const char *flags = NULL;
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ flags = tv_get_string_buf(&argvars[1], nbuf);
+ }
+
+ nvim_feedkeys(cstr_as_string((char *)keys),
+ cstr_as_string((char *)flags), true);
+}
+
+/// "filereadable()" function
+static void f_filereadable(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const char *const p = tv_get_string(&argvars[0]);
+ rettv->vval.v_number =
+ (*p && !os_isdir((const char_u *)p) && os_file_is_readable(p));
+}
+
+/*
+ * Return 0 for not writable, 1 for writable file, 2 for a dir which we have
+ * rights to write into.
+ */
+static void f_filewritable(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const char *filename = tv_get_string(&argvars[0]);
+ rettv->vval.v_number = os_file_is_writable(filename);
+}
+
+
+static void findfilendir(typval_T *argvars, typval_T *rettv, int find_what)
+{
+ char_u *fresult = NULL;
+ char_u *path = *curbuf->b_p_path == NUL ? p_path : curbuf->b_p_path;
+ int count = 1;
+ bool first = true;
+ bool error = false;
+
+ rettv->vval.v_string = NULL;
+ rettv->v_type = VAR_STRING;
+
+ const char *fname = tv_get_string(&argvars[0]);
+
+ char pathbuf[NUMBUFLEN];
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ const char *p = tv_get_string_buf_chk(&argvars[1], pathbuf);
+ if (p == NULL) {
+ error = true;
+ } else {
+ if (*p != NUL) {
+ path = (char_u *)p;
+ }
+
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ count = tv_get_number_chk(&argvars[2], &error);
+ }
+ }
+ }
+
+ if (count < 0) {
+ tv_list_alloc_ret(rettv, kListLenUnknown);
+ }
+
+ if (*fname != NUL && !error) {
+ do {
+ if (rettv->v_type == VAR_STRING || rettv->v_type == VAR_LIST)
+ xfree(fresult);
+ fresult = find_file_in_path_option(first ? (char_u *)fname : NULL,
+ first ? strlen(fname) : 0,
+ 0, first, path,
+ find_what, curbuf->b_ffname,
+ (find_what == FINDFILE_DIR
+ ? (char_u *)""
+ : curbuf->b_p_sua));
+ first = false;
+
+ if (fresult != NULL && rettv->v_type == VAR_LIST) {
+ tv_list_append_string(rettv->vval.v_list, (const char *)fresult, -1);
+ }
+ } while ((rettv->v_type == VAR_LIST || --count > 0) && fresult != NULL);
+ }
+
+ if (rettv->v_type == VAR_STRING)
+ rettv->vval.v_string = fresult;
+}
+
+
+/*
+ * "filter()" function
+ */
+static void f_filter(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ filter_map(argvars, rettv, FALSE);
+}
+
+/*
+ * "finddir({fname}[, {path}[, {count}]])" function
+ */
+static void f_finddir(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ findfilendir(argvars, rettv, FINDFILE_DIR);
+}
+
+/*
+ * "findfile({fname}[, {path}[, {count}]])" function
+ */
+static void f_findfile(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ findfilendir(argvars, rettv, FINDFILE_FILE);
+}
+
+/*
+ * "float2nr({float})" function
+ */
+static void f_float2nr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ float_T f;
+
+ if (tv_get_float_chk(argvars, &f)) {
+ if (f <= -VARNUMBER_MAX + DBL_EPSILON) {
+ rettv->vval.v_number = -VARNUMBER_MAX;
+ } else if (f >= VARNUMBER_MAX - DBL_EPSILON) {
+ rettv->vval.v_number = VARNUMBER_MAX;
+ } else {
+ rettv->vval.v_number = (varnumber_T)f;
+ }
+ }
+}
+
+/*
+ * "fmod()" function
+ */
+static void f_fmod(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ float_T fx;
+ float_T fy;
+
+ rettv->v_type = VAR_FLOAT;
+ if (tv_get_float_chk(argvars, &fx) && tv_get_float_chk(&argvars[1], &fy)) {
+ rettv->vval.v_float = fmod(fx, fy);
+ } else {
+ rettv->vval.v_float = 0.0;
+ }
+}
+
+/*
+ * "fnameescape({string})" function
+ */
+static void f_fnameescape(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_string = (char_u *)vim_strsave_fnameescape(
+ tv_get_string(&argvars[0]), false);
+ rettv->v_type = VAR_STRING;
+}
+
+/*
+ * "fnamemodify({fname}, {mods})" function
+ */
+static void f_fnamemodify(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ char_u *fbuf = NULL;
+ size_t len;
+ char buf[NUMBUFLEN];
+ const char *fname = tv_get_string_chk(&argvars[0]);
+ const char *const mods = tv_get_string_buf_chk(&argvars[1], buf);
+ if (fname == NULL || mods == NULL) {
+ fname = NULL;
+ } else {
+ len = strlen(fname);
+ size_t usedlen = 0;
+ (void)modify_fname((char_u *)mods, false, &usedlen,
+ (char_u **)&fname, &fbuf, &len);
+ }
+
+ rettv->v_type = VAR_STRING;
+ if (fname == NULL) {
+ rettv->vval.v_string = NULL;
+ } else {
+ rettv->vval.v_string = (char_u *)xmemdupz(fname, len);
+ }
+ xfree(fbuf);
+}
+
+
+/*
+ * "foldclosed()" function
+ */
+static void foldclosed_both(typval_T *argvars, typval_T *rettv, int end)
+{
+ const linenr_T lnum = tv_get_lnum(argvars);
+ if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count) {
+ linenr_T first;
+ linenr_T last;
+ if (hasFoldingWin(curwin, lnum, &first, &last, false, NULL)) {
+ if (end) {
+ rettv->vval.v_number = (varnumber_T)last;
+ } else {
+ rettv->vval.v_number = (varnumber_T)first;
+ }
+ return;
+ }
+ }
+ rettv->vval.v_number = -1;
+}
+
+/*
+ * "foldclosed()" function
+ */
+static void f_foldclosed(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ foldclosed_both(argvars, rettv, FALSE);
+}
+
+/*
+ * "foldclosedend()" function
+ */
+static void f_foldclosedend(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ foldclosed_both(argvars, rettv, TRUE);
+}
+
+/*
+ * "foldlevel()" function
+ */
+static void f_foldlevel(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const linenr_T lnum = tv_get_lnum(argvars);
+ if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count) {
+ rettv->vval.v_number = foldLevel(lnum);
+ }
+}
+
+/*
+ * "foldtext()" function
+ */
+static void f_foldtext(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ linenr_T foldstart;
+ linenr_T foldend;
+ char_u *dashes;
+ linenr_T lnum;
+ char_u *s;
+ char_u *r;
+ int len;
+ char *txt;
+
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = NULL;
+
+ foldstart = (linenr_T)get_vim_var_nr(VV_FOLDSTART);
+ foldend = (linenr_T)get_vim_var_nr(VV_FOLDEND);
+ dashes = get_vim_var_str(VV_FOLDDASHES);
+ if (foldstart > 0 && foldend <= curbuf->b_ml.ml_line_count) {
+ // Find first non-empty line in the fold.
+ for (lnum = foldstart; lnum < foldend; lnum++) {
+ if (!linewhite(lnum)) {
+ break;
+ }
+ }
+
+ // Find interesting text in this line.
+ s = skipwhite(ml_get(lnum));
+ // skip C comment-start
+ if (s[0] == '/' && (s[1] == '*' || s[1] == '/')) {
+ s = skipwhite(s + 2);
+ if (*skipwhite(s) == NUL && lnum + 1 < foldend) {
+ s = skipwhite(ml_get(lnum + 1));
+ if (*s == '*')
+ s = skipwhite(s + 1);
+ }
+ }
+ unsigned long count = (unsigned long)(foldend - foldstart + 1);
+ txt = NGETTEXT("+-%s%3ld line: ", "+-%s%3ld lines: ", count);
+ r = xmalloc(STRLEN(txt)
+ + STRLEN(dashes) // for %s
+ + 20 // for %3ld
+ + STRLEN(s)); // concatenated
+ sprintf((char *)r, txt, dashes, count);
+ len = (int)STRLEN(r);
+ STRCAT(r, s);
+ // remove 'foldmarker' and 'commentstring'
+ foldtext_cleanup(r + len);
+ rettv->vval.v_string = r;
+ }
+}
+
+/*
+ * "foldtextresult(lnum)" function
+ */
+static void f_foldtextresult(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ char_u *text;
+ char_u buf[FOLD_TEXT_LEN];
+ foldinfo_T foldinfo;
+ int fold_count;
+ static bool entered = false;
+
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = NULL;
+ if (entered) {
+ return; // reject recursive use
+ }
+ entered = true;
+ linenr_T lnum = tv_get_lnum(argvars);
+ // Treat illegal types and illegal string values for {lnum} the same.
+ if (lnum < 0) {
+ lnum = 0;
+ }
+ fold_count = foldedCount(curwin, lnum, &foldinfo);
+ if (fold_count > 0) {
+ text = get_foldtext(curwin, lnum, lnum + fold_count - 1, &foldinfo, buf);
+ if (text == buf) {
+ text = vim_strsave(text);
+ }
+ rettv->vval.v_string = text;
+ }
+
+ entered = false;
+}
+
+/*
+ * "foreground()" function
+ */
+static void f_foreground(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+}
+
+static void f_funcref(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ common_function(argvars, rettv, true, fptr);
+}
+
+static void f_function(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ common_function(argvars, rettv, false, fptr);
+}
+
+/// "garbagecollect()" function
+static void f_garbagecollect(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ // This is postponed until we are back at the toplevel, because we may be
+ // using Lists and Dicts internally. E.g.: ":echo [garbagecollect()]".
+ want_garbage_collect = true;
+
+ if (argvars[0].v_type != VAR_UNKNOWN && tv_get_number(&argvars[0]) == 1) {
+ garbage_collect_at_exit = true;
+ }
+}
+
+/*
+ * "get()" function
+ */
+static void f_get(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ listitem_T *li;
+ list_T *l;
+ dictitem_T *di;
+ dict_T *d;
+ typval_T *tv = NULL;
+ bool what_is_dict = false;
+
+ if (argvars[0].v_type == VAR_LIST) {
+ if ((l = argvars[0].vval.v_list) != NULL) {
+ bool error = false;
+
+ li = tv_list_find(l, tv_get_number_chk(&argvars[1], &error));
+ if (!error && li != NULL) {
+ tv = TV_LIST_ITEM_TV(li);
+ }
+ }
+ } else if (argvars[0].v_type == VAR_DICT) {
+ if ((d = argvars[0].vval.v_dict) != NULL) {
+ di = tv_dict_find(d, tv_get_string(&argvars[1]), -1);
+ if (di != NULL) {
+ tv = &di->di_tv;
+ }
+ }
+ } else if (tv_is_func(argvars[0])) {
+ partial_T *pt;
+ partial_T fref_pt;
+
+ if (argvars[0].v_type == VAR_PARTIAL) {
+ pt = argvars[0].vval.v_partial;
+ } else {
+ memset(&fref_pt, 0, sizeof(fref_pt));
+ fref_pt.pt_name = argvars[0].vval.v_string;
+ pt = &fref_pt;
+ }
+
+ if (pt != NULL) {
+ const char *const what = tv_get_string(&argvars[1]);
+
+ if (strcmp(what, "func") == 0 || strcmp(what, "name") == 0) {
+ rettv->v_type = (*what == 'f' ? VAR_FUNC : VAR_STRING);
+ const char *const n = (const char *)partial_name(pt);
+ assert(n != NULL);
+ rettv->vval.v_string = (char_u *)xstrdup(n);
+ if (rettv->v_type == VAR_FUNC) {
+ func_ref(rettv->vval.v_string);
+ }
+ } else if (strcmp(what, "dict") == 0) {
+ what_is_dict = true;
+ if (pt->pt_dict != NULL) {
+ tv_dict_set_ret(rettv, pt->pt_dict);
+ }
+ } else if (strcmp(what, "args") == 0) {
+ rettv->v_type = VAR_LIST;
+ if (tv_list_alloc_ret(rettv, pt->pt_argc) != NULL) {
+ for (int i = 0; i < pt->pt_argc; i++) {
+ tv_list_append_tv(rettv->vval.v_list, &pt->pt_argv[i]);
+ }
+ }
+ } else {
+ EMSG2(_(e_invarg2), what);
+ }
+
+ // When {what} == "dict" and pt->pt_dict == NULL, evaluate the
+ // third argument
+ if (!what_is_dict) {
+ return;
+ }
+ }
+ } else {
+ EMSG2(_(e_listdictarg), "get()");
+ }
+
+ if (tv == NULL) {
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ tv_copy(&argvars[2], rettv);
+ }
+ } else {
+ tv_copy(tv, rettv);
+ }
+}
+
+/// "getbufinfo()" function
+static void f_getbufinfo(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ buf_T *argbuf = NULL;
+ bool filtered = false;
+ bool sel_buflisted = false;
+ bool sel_bufloaded = false;
+ bool sel_bufmodified = false;
+
+ tv_list_alloc_ret(rettv, kListLenMayKnow);
+
+ // List of all the buffers or selected buffers
+ if (argvars[0].v_type == VAR_DICT) {
+ dict_T *sel_d = argvars[0].vval.v_dict;
+
+ if (sel_d != NULL) {
+ dictitem_T *di;
+
+ filtered = true;
+
+ di = tv_dict_find(sel_d, S_LEN("buflisted"));
+ if (di != NULL && tv_get_number(&di->di_tv)) {
+ sel_buflisted = true;
+ }
+
+ di = tv_dict_find(sel_d, S_LEN("bufloaded"));
+ if (di != NULL && tv_get_number(&di->di_tv)) {
+ sel_bufloaded = true;
+ }
+ di = tv_dict_find(sel_d, S_LEN("bufmodified"));
+ if (di != NULL && tv_get_number(&di->di_tv)) {
+ sel_bufmodified = true;
+ }
+ }
+ } else if (argvars[0].v_type != VAR_UNKNOWN) {
+ // Information about one buffer. Argument specifies the buffer
+ if (tv_check_num(&argvars[0])) { // issue errmsg if type error
+ emsg_off++;
+ argbuf = tv_get_buf(&argvars[0], false);
+ emsg_off--;
+ if (argbuf == NULL) {
+ return;
+ }
+ }
+ }
+
+ // Return information about all the buffers or a specified buffer
+ FOR_ALL_BUFFERS(buf) {
+ if (argbuf != NULL && argbuf != buf) {
+ continue;
+ }
+ if (filtered && ((sel_bufloaded && buf->b_ml.ml_mfp == NULL)
+ || (sel_buflisted && !buf->b_p_bl)
+ || (sel_bufmodified && !buf->b_changed))) {
+ continue;
+ }
+
+ dict_T *const d = get_buffer_info(buf);
+ tv_list_append_dict(rettv->vval.v_list, d);
+ if (argbuf != NULL) {
+ return;
+ }
+ }
+}
+
+/*
+ * Get line or list of lines from buffer "buf" into "rettv".
+ * Return a range (from start to end) of lines in rettv from the specified
+ * buffer.
+ * If 'retlist' is TRUE, then the lines are returned as a Vim List.
+ */
+static void get_buffer_lines(buf_T *buf,
+ linenr_T start,
+ linenr_T end,
+ int retlist,
+ typval_T *rettv)
+{
+ rettv->v_type = (retlist ? VAR_LIST : VAR_STRING);
+ rettv->vval.v_string = NULL;
+
+ if (buf == NULL || buf->b_ml.ml_mfp == NULL || start < 0 || end < start) {
+ if (retlist) {
+ tv_list_alloc_ret(rettv, 0);
+ }
+ return;
+ }
+
+ if (retlist) {
+ if (start < 1) {
+ start = 1;
+ }
+ if (end > buf->b_ml.ml_line_count) {
+ end = buf->b_ml.ml_line_count;
+ }
+ tv_list_alloc_ret(rettv, end - start + 1);
+ while (start <= end) {
+ tv_list_append_string(rettv->vval.v_list,
+ (const char *)ml_get_buf(buf, start++, false), -1);
+ }
+ } else {
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = ((start >= 1 && start <= buf->b_ml.ml_line_count)
+ ? vim_strsave(ml_get_buf(buf, start, false))
+ : NULL);
+ }
+}
+
+/*
+ * "getbufline()" function
+ */
+static void f_getbufline(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ buf_T *buf = NULL;
+
+ if (tv_check_str_or_nr(&argvars[0])) {
+ emsg_off++;
+ buf = tv_get_buf(&argvars[0], false);
+ emsg_off--;
+ }
+
+ const linenr_T lnum = tv_get_lnum_buf(&argvars[1], buf);
+ const linenr_T end = (argvars[2].v_type == VAR_UNKNOWN
+ ? lnum
+ : tv_get_lnum_buf(&argvars[2], buf));
+
+ get_buffer_lines(buf, lnum, end, true, rettv);
+}
+
+/*
+ * "getbufvar()" function
+ */
+static void f_getbufvar(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ bool done = false;
+
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = NULL;
+
+ if (!tv_check_str_or_nr(&argvars[0])) {
+ goto f_getbufvar_end;
+ }
+
+ const char *varname = tv_get_string_chk(&argvars[1]);
+ emsg_off++;
+ buf_T *const buf = tv_get_buf(&argvars[0], false);
+
+ if (buf != NULL && varname != NULL) {
+ if (*varname == '&') { // buffer-local-option
+ buf_T *const save_curbuf = curbuf;
+
+ // set curbuf to be our buf, temporarily
+ curbuf = buf;
+
+ if (varname[1] == NUL) {
+ // get all buffer-local options in a dict
+ dict_T *opts = get_winbuf_options(true);
+
+ if (opts != NULL) {
+ tv_dict_set_ret(rettv, opts);
+ done = true;
+ }
+ } else if (get_option_tv(&varname, rettv, true) == OK) {
+ // buffer-local-option
+ done = true;
+ }
+
+ // restore previous notion of curbuf
+ curbuf = save_curbuf;
+ } else {
+ // Look up the variable.
+ // Let getbufvar({nr}, "") return the "b:" dictionary.
+ dictitem_T *const v = *varname == NUL
+ ? (dictitem_T *)&buf->b_bufvar
+ : find_var_in_ht(&buf->b_vars->dv_hashtab, 'b',
+ varname, strlen(varname), false);
+ if (v != NULL) {
+ tv_copy(&v->di_tv, rettv);
+ done = true;
+ }
+ }
+ }
+ emsg_off--;
+
+f_getbufvar_end:
+ if (!done && argvars[2].v_type != VAR_UNKNOWN) {
+ // use the default value
+ tv_copy(&argvars[2], rettv);
+ }
+}
+
+// "getchangelist()" function
+static void f_getchangelist(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ tv_list_alloc_ret(rettv, 2);
+ vim_ignored = tv_get_number(&argvars[0]); // issue errmsg if type error
+ emsg_off++;
+ const buf_T *const buf = tv_get_buf(&argvars[0], false);
+ emsg_off--;
+ if (buf == NULL) {
+ return;
+ }
+
+ list_T *const l = tv_list_alloc(buf->b_changelistlen);
+ tv_list_append_list(rettv->vval.v_list, l);
+ // The current window change list index tracks only the position in the
+ // current buffer change list. For other buffers, use the change list
+ // length as the current index.
+ tv_list_append_number(rettv->vval.v_list,
+ (buf == curwin->w_buffer)
+ ? curwin->w_changelistidx
+ : buf->b_changelistlen);
+
+ for (int i = 0; i < buf->b_changelistlen; i++) {
+ if (buf->b_changelist[i].mark.lnum == 0) {
+ continue;
+ }
+ dict_T *const d = tv_dict_alloc();
+ tv_list_append_dict(l, d);
+ tv_dict_add_nr(d, S_LEN("lnum"), buf->b_changelist[i].mark.lnum);
+ tv_dict_add_nr(d, S_LEN("col"), buf->b_changelist[i].mark.col);
+ tv_dict_add_nr(d, S_LEN("coladd"), buf->b_changelist[i].mark.coladd);
+ }
+}
+
+/*
+ * "getchar()" function
+ */
+static void f_getchar(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ varnumber_T n;
+ bool error = false;
+
+ no_mapping++;
+ for (;; ) {
+ // Position the cursor. Needed after a message that ends in a space,
+ // or if event processing caused a redraw.
+ ui_cursor_goto(msg_row, msg_col);
+
+ if (argvars[0].v_type == VAR_UNKNOWN) {
+ // getchar(): blocking wait.
+ if (!(char_avail() || using_script() || input_available())) {
+ (void)os_inchar(NULL, 0, -1, 0, main_loop.events);
+ if (!multiqueue_empty(main_loop.events)) {
+ multiqueue_process_events(main_loop.events);
+ continue;
+ }
+ }
+ n = safe_vgetc();
+ } else if (tv_get_number_chk(&argvars[0], &error) == 1) {
+ // getchar(1): only check if char avail
+ n = vpeekc_any();
+ } else if (error || vpeekc_any() == NUL) {
+ // illegal argument or getchar(0) and no char avail: return zero
+ n = 0;
+ } else {
+ // getchar(0) and char avail: return char
+ n = safe_vgetc();
+ }
+
+ if (n == K_IGNORE) {
+ continue;
+ }
+ break;
+ }
+ no_mapping--;
+
+ set_vim_var_nr(VV_MOUSE_WIN, 0);
+ set_vim_var_nr(VV_MOUSE_WINID, 0);
+ set_vim_var_nr(VV_MOUSE_LNUM, 0);
+ set_vim_var_nr(VV_MOUSE_COL, 0);
+
+ rettv->vval.v_number = n;
+ if (IS_SPECIAL(n) || mod_mask != 0) {
+ char_u temp[10]; // modifier: 3, mbyte-char: 6, NUL: 1
+ int i = 0;
+
+ // Turn a special key into three bytes, plus modifier.
+ if (mod_mask != 0) {
+ temp[i++] = K_SPECIAL;
+ temp[i++] = KS_MODIFIER;
+ temp[i++] = mod_mask;
+ }
+ if (IS_SPECIAL(n)) {
+ temp[i++] = K_SPECIAL;
+ temp[i++] = K_SECOND(n);
+ temp[i++] = K_THIRD(n);
+ } else {
+ i += utf_char2bytes(n, temp + i);
+ }
+ temp[i++] = NUL;
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = vim_strsave(temp);
+
+ if (is_mouse_key(n)) {
+ int row = mouse_row;
+ int col = mouse_col;
+ int grid = mouse_grid;
+ win_T *win;
+ linenr_T lnum;
+ win_T *wp;
+ int winnr = 1;
+
+ if (row >= 0 && col >= 0) {
+ /* Find the window at the mouse coordinates and compute the
+ * text position. */
+ win = mouse_find_win(&grid, &row, &col);
+ if (win == NULL) {
+ return;
+ }
+ (void)mouse_comp_pos(win, &row, &col, &lnum);
+ for (wp = firstwin; wp != win; wp = wp->w_next)
+ ++winnr;
+ set_vim_var_nr(VV_MOUSE_WIN, winnr);
+ set_vim_var_nr(VV_MOUSE_WINID, wp->handle);
+ set_vim_var_nr(VV_MOUSE_LNUM, lnum);
+ set_vim_var_nr(VV_MOUSE_COL, col + 1);
+ }
+ }
+ }
+}
+
+/*
+ * "getcharmod()" function
+ */
+static void f_getcharmod(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = mod_mask;
+}
+
+/*
+ * "getcharsearch()" function
+ */
+static void f_getcharsearch(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ tv_dict_alloc_ret(rettv);
+
+ dict_T *dict = rettv->vval.v_dict;
+
+ tv_dict_add_str(dict, S_LEN("char"), last_csearch());
+ tv_dict_add_nr(dict, S_LEN("forward"), last_csearch_forward());
+ tv_dict_add_nr(dict, S_LEN("until"), last_csearch_until());
+}
+
+/*
+ * "getcmdline()" function
+ */
+static void f_getcmdline(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = get_cmdline_str();
+}
+
+/*
+ * "getcmdpos()" function
+ */
+static void f_getcmdpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = get_cmdline_pos() + 1;
+}
+
+/*
+ * "getcmdtype()" function
+ */
+static void f_getcmdtype(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = xmallocz(1);
+ rettv->vval.v_string[0] = get_cmdline_type();
+}
+
+/*
+ * "getcmdwintype()" function
+ */
+static void f_getcmdwintype(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = NULL;
+ rettv->vval.v_string = xmallocz(1);
+ rettv->vval.v_string[0] = cmdwin_type;
+}
+
+// "getcompletion()" function
+static void f_getcompletion(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ char_u *pat;
+ expand_T xpc;
+ bool filtered = false;
+ int options = WILD_SILENT | WILD_USE_NL | WILD_ADD_SLASH
+ | WILD_NO_BEEP;
+
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ filtered = (bool)tv_get_number_chk(&argvars[2], NULL);
+ }
+
+ if (p_wic) {
+ options |= WILD_ICASE;
+ }
+
+ // For filtered results, 'wildignore' is used
+ if (!filtered) {
+ options |= WILD_KEEP_ALL;
+ }
+
+ if (argvars[0].v_type != VAR_STRING || argvars[1].v_type != VAR_STRING) {
+ EMSG(_(e_invarg));
+ return;
+ }
+
+ if (strcmp(tv_get_string(&argvars[1]), "cmdline") == 0) {
+ set_one_cmd_context(&xpc, tv_get_string(&argvars[0]));
+ xpc.xp_pattern_len = STRLEN(xpc.xp_pattern);
+ goto theend;
+ }
+
+ ExpandInit(&xpc);
+ xpc.xp_pattern = (char_u *)tv_get_string(&argvars[0]);
+ xpc.xp_pattern_len = STRLEN(xpc.xp_pattern);
+ xpc.xp_context = cmdcomplete_str_to_type(
+ (char_u *)tv_get_string(&argvars[1]));
+ if (xpc.xp_context == EXPAND_NOTHING) {
+ EMSG2(_(e_invarg2), argvars[1].vval.v_string);
+ return;
+ }
+
+ if (xpc.xp_context == EXPAND_MENUS) {
+ set_context_in_menu_cmd(&xpc, (char_u *)"menu", xpc.xp_pattern, false);
+ xpc.xp_pattern_len = STRLEN(xpc.xp_pattern);
+ }
+
+ if (xpc.xp_context == EXPAND_CSCOPE) {
+ set_context_in_cscope_cmd(&xpc, (const char *)xpc.xp_pattern, CMD_cscope);
+ xpc.xp_pattern_len = STRLEN(xpc.xp_pattern);
+ }
+
+ if (xpc.xp_context == EXPAND_SIGN) {
+ set_context_in_sign_cmd(&xpc, xpc.xp_pattern);
+ xpc.xp_pattern_len = STRLEN(xpc.xp_pattern);
+ }
+
+theend:
+ pat = addstar(xpc.xp_pattern, xpc.xp_pattern_len, xpc.xp_context);
+ ExpandOne(&xpc, pat, NULL, options, WILD_ALL_KEEP);
+ tv_list_alloc_ret(rettv, xpc.xp_numfiles);
+
+ for (int i = 0; i < xpc.xp_numfiles; i++) {
+ tv_list_append_string(rettv->vval.v_list, (const char *)xpc.xp_files[i],
+ -1);
+ }
+ xfree(pat);
+ ExpandCleanup(&xpc);
+}
+
+/// `getcwd([{win}[, {tab}]])` function
+///
+/// Every scope not specified implies the currently selected scope object.
+///
+/// @pre The arguments must be of type number.
+/// @pre There may not be more than two arguments.
+/// @pre An argument may not be -1 if preceding arguments are not all -1.
+///
+/// @post The return value will be a string.
+static void f_getcwd(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ // Possible scope of working directory to return.
+ CdScope scope = kCdScopeInvalid;
+
+ // Numbers of the scope objects (window, tab) we want the working directory
+ // of. A `-1` means to skip this scope, a `0` means the current object.
+ int scope_number[] = {
+ [kCdScopeWindow] = 0, // Number of window to look at.
+ [kCdScopeTab ] = 0, // Number of tab to look at.
+ };
+
+ char_u *cwd = NULL; // Current working directory to print
+ char_u *from = NULL; // The original string to copy
+
+ tabpage_T *tp = curtab; // The tabpage to look at.
+ win_T *win = curwin; // The window to look at.
+
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = NULL;
+
+ // Pre-conditions and scope extraction together
+ for (int i = MIN_CD_SCOPE; i < MAX_CD_SCOPE; i++) {
+ // If there is no argument there are no more scopes after it, break out.
+ if (argvars[i].v_type == VAR_UNKNOWN) {
+ break;
+ }
+ if (argvars[i].v_type != VAR_NUMBER) {
+ EMSG(_(e_invarg));
+ return;
+ }
+ scope_number[i] = argvars[i].vval.v_number;
+ // It is an error for the scope number to be less than `-1`.
+ if (scope_number[i] < -1) {
+ EMSG(_(e_invarg));
+ return;
+ }
+ // Use the narrowest scope the user requested
+ if (scope_number[i] >= 0 && scope == kCdScopeInvalid) {
+ // The scope is the current iteration step.
+ scope = i;
+ } else if (scope_number[i] < 0) {
+ scope = i + 1;
+ }
+ }
+
+ // If the user didn't specify anything, default to window scope
+ if (scope == kCdScopeInvalid) {
+ scope = MIN_CD_SCOPE;
+ }
+
+ // Find the tabpage by number
+ if (scope_number[kCdScopeTab] > 0) {
+ tp = find_tabpage(scope_number[kCdScopeTab]);
+ if (!tp) {
+ EMSG(_("E5000: Cannot find tab number."));
+ return;
+ }
+ }
+
+ // Find the window in `tp` by number, `NULL` if none.
+ if (scope_number[kCdScopeWindow] >= 0) {
+ if (scope_number[kCdScopeTab] < 0) {
+ EMSG(_("E5001: Higher scope cannot be -1 if lower scope is >= 0."));
+ return;
+ }
+
+ if (scope_number[kCdScopeWindow] > 0) {
+ win = find_win_by_nr(&argvars[0], tp);
+ if (!win) {
+ EMSG(_("E5002: Cannot find window number."));
+ return;
+ }
+ }
+ }
+
+ cwd = xmalloc(MAXPATHL);
+
+ switch (scope) {
+ case kCdScopeWindow:
+ assert(win);
+ from = win->w_localdir;
+ if (from) {
+ break;
+ }
+ FALLTHROUGH;
+ case kCdScopeTab:
+ assert(tp);
+ from = tp->tp_localdir;
+ if (from) {
+ break;
+ }
+ FALLTHROUGH;
+ case kCdScopeGlobal:
+ if (globaldir) { // `globaldir` is not always set.
+ from = globaldir;
+ } else if (os_dirname(cwd, MAXPATHL) == FAIL) { // Get the OS CWD.
+ from = (char_u *)""; // Return empty string on failure.
+ }
+ break;
+ case kCdScopeInvalid: // We should never get here
+ assert(false);
+ }
+
+ if (from) {
+ xstrlcpy((char *)cwd, (char *)from, MAXPATHL);
+ }
+
+ rettv->vval.v_string = vim_strsave(cwd);
+#ifdef BACKSLASH_IN_FILENAME
+ slash_adjust(rettv->vval.v_string);
+#endif
+
+ xfree(cwd);
+}
+
+/*
+ * "getfontname()" function
+ */
+static void f_getfontname(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = NULL;
+}
+
+/*
+ * "getfperm({fname})" function
+ */
+static void f_getfperm(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ char *perm = NULL;
+ char_u flags[] = "rwx";
+
+ const char *filename = tv_get_string(&argvars[0]);
+ int32_t file_perm = os_getperm(filename);
+ if (file_perm >= 0) {
+ perm = xstrdup("---------");
+ for (int i = 0; i < 9; i++) {
+ if (file_perm & (1 << (8 - i))) {
+ perm[i] = flags[i % 3];
+ }
+ }
+ }
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = (char_u *)perm;
+}
+
+/*
+ * "getfsize({fname})" function
+ */
+static void f_getfsize(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const char *fname = tv_get_string(&argvars[0]);
+
+ rettv->v_type = VAR_NUMBER;
+
+ FileInfo file_info;
+ if (os_fileinfo(fname, &file_info)) {
+ uint64_t filesize = os_fileinfo_size(&file_info);
+ if (os_isdir((const char_u *)fname)) {
+ rettv->vval.v_number = 0;
+ } else {
+ rettv->vval.v_number = (varnumber_T)filesize;
+
+ // non-perfect check for overflow
+ if ((uint64_t)rettv->vval.v_number != filesize) {
+ rettv->vval.v_number = -2;
+ }
+ }
+ } else {
+ rettv->vval.v_number = -1;
+ }
+}
+
+/*
+ * "getftime({fname})" function
+ */
+static void f_getftime(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const char *fname = tv_get_string(&argvars[0]);
+
+ FileInfo file_info;
+ if (os_fileinfo(fname, &file_info)) {
+ rettv->vval.v_number = (varnumber_T)file_info.stat.st_mtim.tv_sec;
+ } else {
+ rettv->vval.v_number = -1;
+ }
+}
+
+/*
+ * "getftype({fname})" function
+ */
+static void f_getftype(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ char_u *type = NULL;
+ char *t;
+
+ const char *fname = tv_get_string(&argvars[0]);
+
+ rettv->v_type = VAR_STRING;
+ FileInfo file_info;
+ if (os_fileinfo_link(fname, &file_info)) {
+ uint64_t mode = file_info.stat.st_mode;
+#ifdef S_ISREG
+ if (S_ISREG(mode))
+ t = "file";
+ else if (S_ISDIR(mode))
+ t = "dir";
+# ifdef S_ISLNK
+ else if (S_ISLNK(mode))
+ t = "link";
+# endif
+# ifdef S_ISBLK
+ else if (S_ISBLK(mode))
+ t = "bdev";
+# endif
+# ifdef S_ISCHR
+ else if (S_ISCHR(mode))
+ t = "cdev";
+# endif
+# ifdef S_ISFIFO
+ else if (S_ISFIFO(mode))
+ t = "fifo";
+# endif
+# ifdef S_ISSOCK
+ else if (S_ISSOCK(mode))
+ t = "socket";
+# endif
+ else
+ t = "other";
+#else
+# ifdef S_IFMT
+ switch (mode & S_IFMT) {
+ case S_IFREG: t = "file"; break;
+ case S_IFDIR: t = "dir"; break;
+# ifdef S_IFLNK
+ case S_IFLNK: t = "link"; break;
+# endif
+# ifdef S_IFBLK
+ case S_IFBLK: t = "bdev"; break;
+# endif
+# ifdef S_IFCHR
+ case S_IFCHR: t = "cdev"; break;
+# endif
+# ifdef S_IFIFO
+ case S_IFIFO: t = "fifo"; break;
+# endif
+# ifdef S_IFSOCK
+ case S_IFSOCK: t = "socket"; break;
+# endif
+ default: t = "other";
+ }
+# else
+ if (os_isdir((const char_u *)fname)) {
+ t = "dir";
+ } else {
+ t = "file";
+ }
+# endif
+#endif
+ type = vim_strsave((char_u *)t);
+ }
+ rettv->vval.v_string = type;
+}
+
+// "getjumplist()" function
+static void f_getjumplist(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ tv_list_alloc_ret(rettv, kListLenMayKnow);
+ win_T *const wp = find_tabwin(&argvars[0], &argvars[1]);
+ if (wp == NULL) {
+ return;
+ }
+
+ cleanup_jumplist(wp, true);
+
+ list_T *const l = tv_list_alloc(wp->w_jumplistlen);
+ tv_list_append_list(rettv->vval.v_list, l);
+ tv_list_append_number(rettv->vval.v_list, wp->w_jumplistidx);
+
+ for (int i = 0; i < wp->w_jumplistlen; i++) {
+ if (wp->w_jumplist[i].fmark.mark.lnum == 0) {
+ continue;
+ }
+ dict_T *const d = tv_dict_alloc();
+ tv_list_append_dict(l, d);
+ tv_dict_add_nr(d, S_LEN("lnum"), wp->w_jumplist[i].fmark.mark.lnum);
+ tv_dict_add_nr(d, S_LEN("col"), wp->w_jumplist[i].fmark.mark.col);
+ tv_dict_add_nr(d, S_LEN("coladd"), wp->w_jumplist[i].fmark.mark.coladd);
+ tv_dict_add_nr(d, S_LEN("bufnr"), wp->w_jumplist[i].fmark.fnum);
+ if (wp->w_jumplist[i].fname != NULL) {
+ tv_dict_add_str(d, S_LEN("filename"), (char *)wp->w_jumplist[i].fname);
+ }
+ }
+}
+
+/*
+ * "getline(lnum, [end])" function
+ */
+static void f_getline(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ linenr_T end;
+ bool retlist;
+
+ const linenr_T lnum = tv_get_lnum(argvars);
+ if (argvars[1].v_type == VAR_UNKNOWN) {
+ end = lnum;
+ retlist = false;
+ } else {
+ end = tv_get_lnum(&argvars[1]);
+ retlist = true;
+ }
+
+ get_buffer_lines(curbuf, lnum, end, retlist, rettv);
+}
+
+/// "getloclist()" function
+static void f_getloclist(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ win_T *wp = find_win_by_nr_or_id(&argvars[0]);
+ get_qf_loc_list(false, wp, &argvars[1], rettv);
+}
+
+/*
+ * "getmatches()" function
+ */
+static void f_getmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ matchitem_T *cur;
+ int i;
+ win_T *win = get_optional_window(argvars, 0);
+
+ if (win == NULL) {
+ return;
+ }
+
+ tv_list_alloc_ret(rettv, kListLenMayKnow);
+ cur = win->w_match_head;
+ while (cur != NULL) {
+ dict_T *dict = tv_dict_alloc();
+ if (cur->match.regprog == NULL) {
+ // match added with matchaddpos()
+ for (i = 0; i < MAXPOSMATCH; i++) {
+ llpos_T *llpos;
+ char buf[30]; // use 30 to avoid compiler warning
+
+ llpos = &cur->pos.pos[i];
+ if (llpos->lnum == 0) {
+ break;
+ }
+ list_T *const l = tv_list_alloc(1 + (llpos->col > 0 ? 2 : 0));
+ tv_list_append_number(l, (varnumber_T)llpos->lnum);
+ if (llpos->col > 0) {
+ tv_list_append_number(l, (varnumber_T)llpos->col);
+ tv_list_append_number(l, (varnumber_T)llpos->len);
+ }
+ int len = snprintf(buf, sizeof(buf), "pos%d", i + 1);
+ assert((size_t)len < sizeof(buf));
+ tv_dict_add_list(dict, buf, (size_t)len, l);
+ }
+ } else {
+ tv_dict_add_str(dict, S_LEN("pattern"), (const char *)cur->pattern);
+ }
+ tv_dict_add_str(dict, S_LEN("group"),
+ (const char *)syn_id2name(cur->hlg_id));
+ tv_dict_add_nr(dict, S_LEN("priority"), (varnumber_T)cur->priority);
+ tv_dict_add_nr(dict, S_LEN("id"), (varnumber_T)cur->id);
+
+ if (cur->conceal_char) {
+ char buf[MB_MAXBYTES + 1];
+
+ buf[utf_char2bytes((int)cur->conceal_char, (char_u *)buf)] = NUL;
+ tv_dict_add_str(dict, S_LEN("conceal"), buf);
+ }
+
+ tv_list_append_dict(rettv->vval.v_list, dict);
+ cur = cur->next;
+ }
+}
+
+/*
+ * "getpid()" function
+ */
+static void f_getpid(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = os_get_pid();
+}
+
+static void getpos_both(typval_T *argvars, typval_T *rettv, bool getcurpos)
+{
+ pos_T *fp;
+ int fnum = -1;
+
+ if (getcurpos) {
+ fp = &curwin->w_cursor;
+ } else {
+ fp = var2fpos(&argvars[0], true, &fnum);
+ }
+
+ list_T *const l = tv_list_alloc_ret(rettv, 4 + (!!getcurpos));
+ tv_list_append_number(l, (fnum != -1) ? (varnumber_T)fnum : (varnumber_T)0);
+ tv_list_append_number(l, ((fp != NULL)
+ ? (varnumber_T)fp->lnum
+ : (varnumber_T)0));
+ tv_list_append_number(
+ l, ((fp != NULL)
+ ? (varnumber_T)(fp->col == MAXCOL ? MAXCOL : fp->col + 1)
+ : (varnumber_T)0));
+ tv_list_append_number(
+ l, (fp != NULL) ? (varnumber_T)fp->coladd : (varnumber_T)0);
+ if (getcurpos) {
+ const int save_set_curswant = curwin->w_set_curswant;
+ const colnr_T save_curswant = curwin->w_curswant;
+ const colnr_T save_virtcol = curwin->w_virtcol;
+
+ update_curswant();
+ tv_list_append_number(l, (curwin->w_curswant == MAXCOL
+ ? (varnumber_T)MAXCOL
+ : (varnumber_T)curwin->w_curswant + 1));
+
+ // Do not change "curswant", as it is unexpected that a get
+ // function has a side effect.
+ if (save_set_curswant) {
+ curwin->w_set_curswant = save_set_curswant;
+ curwin->w_curswant = save_curswant;
+ curwin->w_virtcol = save_virtcol;
+ curwin->w_valid &= ~VALID_VIRTCOL;
+ }
+ }
+}
+
+/*
+ * "getcurpos(string)" function
+ */
+static void f_getcurpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ getpos_both(argvars, rettv, true);
+}
+
+/*
+ * "getpos(string)" function
+ */
+static void f_getpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ getpos_both(argvars, rettv, false);
+}
+
+/// "getqflist()" functions
+static void f_getqflist(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ get_qf_loc_list(true, NULL, &argvars[0], rettv);
+}
+
+/// "getreg()" function
+static void f_getreg(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const char *strregname;
+ int arg2 = false;
+ bool return_list = false;
+ bool error = false;
+
+ if (argvars[0].v_type != VAR_UNKNOWN) {
+ strregname = tv_get_string_chk(&argvars[0]);
+ error = strregname == NULL;
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ arg2 = tv_get_number_chk(&argvars[1], &error);
+ if (!error && argvars[2].v_type != VAR_UNKNOWN) {
+ return_list = tv_get_number_chk(&argvars[2], &error);
+ }
+ }
+ } else {
+ strregname = _(get_vim_var_str(VV_REG));
+ }
+
+ if (error) {
+ return;
+ }
+
+ int regname = (uint8_t)(strregname == NULL ? '"' : *strregname);
+ if (regname == 0) {
+ regname = '"';
+ }
+
+ if (return_list) {
+ rettv->v_type = VAR_LIST;
+ rettv->vval.v_list =
+ get_reg_contents(regname, (arg2 ? kGRegExprSrc : 0) | kGRegList);
+ if (rettv->vval.v_list == NULL) {
+ rettv->vval.v_list = tv_list_alloc(0);
+ }
+ tv_list_ref(rettv->vval.v_list);
+ } else {
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = get_reg_contents(regname, arg2 ? kGRegExprSrc : 0);
+ }
+}
+
+/*
+ * "getregtype()" function
+ */
+static void f_getregtype(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const char *strregname;
+
+ if (argvars[0].v_type != VAR_UNKNOWN) {
+ strregname = tv_get_string_chk(&argvars[0]);
+ if (strregname == NULL) { // Type error; errmsg already given.
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = NULL;
+ return;
+ }
+ } else {
+ // Default to v:register.
+ strregname = _(get_vim_var_str(VV_REG));
+ }
+
+ int regname = (uint8_t)(strregname == NULL ? '"' : *strregname);
+ if (regname == 0) {
+ regname = '"';
+ }
+
+ colnr_T reglen = 0;
+ char buf[NUMBUFLEN + 2];
+ MotionType reg_type = get_reg_type(regname, &reglen);
+ format_reg_type(reg_type, reglen, buf, ARRAY_SIZE(buf));
+
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = (char_u *)xstrdup(buf);
+}
+
+/// "gettabinfo()" function
+static void f_gettabinfo(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ tabpage_T *tparg = NULL;
+
+ tv_list_alloc_ret(rettv, (argvars[0].v_type == VAR_UNKNOWN
+ ? 1
+ : kListLenMayKnow));
+
+ if (argvars[0].v_type != VAR_UNKNOWN) {
+ // Information about one tab page
+ tparg = find_tabpage((int)tv_get_number_chk(&argvars[0], NULL));
+ if (tparg == NULL) {
+ return;
+ }
+ }
+
+ // Get information about a specific tab page or all tab pages
+ int tpnr = 0;
+ FOR_ALL_TABS(tp) {
+ tpnr++;
+ if (tparg != NULL && tp != tparg) {
+ continue;
+ }
+ dict_T *const d = get_tabpage_info(tp, tpnr);
+ tv_list_append_dict(rettv->vval.v_list, d);
+ if (tparg != NULL) {
+ return;
+ }
+ }
+}
+
+/*
+ * "gettabvar()" function
+ */
+static void f_gettabvar(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ win_T *oldcurwin;
+ tabpage_T *oldtabpage;
+ bool done = false;
+
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = NULL;
+
+ const char *const varname = tv_get_string_chk(&argvars[1]);
+ tabpage_T *const tp = find_tabpage((int)tv_get_number_chk(&argvars[0], NULL));
+ if (tp != NULL && varname != NULL) {
+ // Set tp to be our tabpage, temporarily. Also set the window to the
+ // first window in the tabpage, otherwise the window is not valid.
+ win_T *const window = tp == curtab || tp->tp_firstwin == NULL
+ ? firstwin
+ : tp->tp_firstwin;
+ if (switch_win(&oldcurwin, &oldtabpage, window, tp, true) == OK) {
+ // look up the variable
+ // Let gettabvar({nr}, "") return the "t:" dictionary.
+ const dictitem_T *const v = find_var_in_ht(&tp->tp_vars->dv_hashtab, 't',
+ varname, strlen(varname),
+ false);
+ if (v != NULL) {
+ tv_copy(&v->di_tv, rettv);
+ done = true;
+ }
+ }
+
+ // restore previous notion of curwin
+ restore_win(oldcurwin, oldtabpage, true);
+ }
+
+ if (!done && argvars[2].v_type != VAR_UNKNOWN) {
+ tv_copy(&argvars[2], rettv);
+ }
+}
+
+/*
+ * "gettabwinvar()" function
+ */
+static void f_gettabwinvar(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ getwinvar(argvars, rettv, 1);
+}
+
+// "gettagstack()" function
+static void f_gettagstack(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ win_T *wp = curwin; // default is current window
+
+ tv_dict_alloc_ret(rettv);
+
+ if (argvars[0].v_type != VAR_UNKNOWN) {
+ wp = find_win_by_nr_or_id(&argvars[0]);
+ if (wp == NULL) {
+ return;
+ }
+ }
+
+ get_tagstack(wp, rettv->vval.v_dict);
+}
+
+/// "getwininfo()" function
+static void f_getwininfo(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ win_T *wparg = NULL;
+
+ tv_list_alloc_ret(rettv, kListLenMayKnow);
+
+ if (argvars[0].v_type != VAR_UNKNOWN) {
+ wparg = win_id2wp(argvars);
+ if (wparg == NULL) {
+ return;
+ }
+ }
+
+ // Collect information about either all the windows across all the tab
+ // pages or one particular window.
+ int16_t tabnr = 0;
+ FOR_ALL_TABS(tp) {
+ tabnr++;
+ int16_t winnr = 0;
+ FOR_ALL_WINDOWS_IN_TAB(wp, tp) {
+ winnr++;
+ if (wparg != NULL && wp != wparg) {
+ continue;
+ }
+ dict_T *const d = get_win_info(wp, tabnr, winnr);
+ tv_list_append_dict(rettv->vval.v_list, d);
+ if (wparg != NULL) {
+ // found information about a specific window
+ return;
+ }
+ }
+ }
+}
+
+// Dummy timer callback. Used by f_wait().
+static void dummy_timer_due_cb(TimeWatcher *tw, void *data)
+{
+}
+
+// Dummy timer close callback. Used by f_wait().
+static void dummy_timer_close_cb(TimeWatcher *tw, void *data)
+{
+ xfree(tw);
+}
+
+/// "wait(timeout, condition[, interval])" function
+static void f_wait(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->v_type = VAR_NUMBER;
+ rettv->vval.v_number = -1;
+
+ if (argvars[0].v_type != VAR_NUMBER) {
+ EMSG2(_(e_invargval), "1");
+ return;
+ }
+ if ((argvars[2].v_type != VAR_NUMBER && argvars[2].v_type != VAR_UNKNOWN)
+ || (argvars[2].v_type == VAR_NUMBER && argvars[2].vval.v_number <= 0)) {
+ EMSG2(_(e_invargval), "3");
+ return;
+ }
+
+ int timeout = argvars[0].vval.v_number;
+ typval_T expr = argvars[1];
+ int interval = argvars[2].v_type == VAR_NUMBER
+ ? argvars[2].vval.v_number
+ : 200; // Default.
+ TimeWatcher *tw = xmalloc(sizeof(TimeWatcher));
+
+ // Start dummy timer.
+ time_watcher_init(&main_loop, tw, NULL);
+ tw->events = main_loop.events;
+ tw->blockable = true;
+ time_watcher_start(tw, dummy_timer_due_cb, interval, interval);
+
+ typval_T argv = TV_INITIAL_VALUE;
+ typval_T exprval = TV_INITIAL_VALUE;
+ bool error = false;
+ int save_called_emsg = called_emsg;
+ called_emsg = false;
+
+ LOOP_PROCESS_EVENTS_UNTIL(&main_loop, main_loop.events, timeout,
+ eval_expr_typval(&expr, &argv, 0, &exprval) != OK
+ || tv_get_number_chk(&exprval, &error)
+ || called_emsg || error || got_int);
+
+ if (called_emsg || error) {
+ rettv->vval.v_number = -3;
+ } else if (got_int) {
+ got_int = false;
+ vgetc();
+ rettv->vval.v_number = -2;
+ } else if (tv_get_number_chk(&exprval, &error)) {
+ rettv->vval.v_number = 0;
+ }
+
+ called_emsg = save_called_emsg;
+
+ // Stop dummy timer
+ time_watcher_stop(tw);
+ time_watcher_close(tw, dummy_timer_close_cb);
+}
+
+// "win_screenpos()" function
+static void f_win_screenpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ tv_list_alloc_ret(rettv, 2);
+ const win_T *const wp = find_win_by_nr_or_id(&argvars[0]);
+ tv_list_append_number(rettv->vval.v_list, wp == NULL ? 0 : wp->w_winrow + 1);
+ tv_list_append_number(rettv->vval.v_list, wp == NULL ? 0 : wp->w_wincol + 1);
+}
+
+// "getwinpos({timeout})" function
+static void f_getwinpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ tv_list_alloc_ret(rettv, 2);
+ tv_list_append_number(rettv->vval.v_list, -1);
+ tv_list_append_number(rettv->vval.v_list, -1);
+}
+
+/*
+ * "getwinposx()" function
+ */
+static void f_getwinposx(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = -1;
+}
+
+/*
+ * "getwinposy()" function
+ */
+static void f_getwinposy(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = -1;
+}
+
+/// "getwinvar()" function
+static void f_getwinvar(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ getwinvar(argvars, rettv, 0);
+}
+
+/*
+ * "glob()" function
+ */
+static void f_glob(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ int options = WILD_SILENT|WILD_USE_NL;
+ expand_T xpc;
+ bool error = false;
+
+ /* When the optional second argument is non-zero, don't remove matches
+ * for 'wildignore' and don't put matches for 'suffixes' at the end. */
+ rettv->v_type = VAR_STRING;
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ if (tv_get_number_chk(&argvars[1], &error)) {
+ options |= WILD_KEEP_ALL;
+ }
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ if (tv_get_number_chk(&argvars[2], &error)) {
+ tv_list_set_ret(rettv, NULL);
+ }
+ if (argvars[3].v_type != VAR_UNKNOWN
+ && tv_get_number_chk(&argvars[3], &error)) {
+ options |= WILD_ALLLINKS;
+ }
+ }
+ }
+ if (!error) {
+ ExpandInit(&xpc);
+ xpc.xp_context = EXPAND_FILES;
+ if (p_wic)
+ options += WILD_ICASE;
+ if (rettv->v_type == VAR_STRING) {
+ rettv->vval.v_string = ExpandOne(
+ &xpc, (char_u *)tv_get_string(&argvars[0]), NULL, options, WILD_ALL);
+ } else {
+ ExpandOne(&xpc, (char_u *)tv_get_string(&argvars[0]), NULL, options,
+ WILD_ALL_KEEP);
+ tv_list_alloc_ret(rettv, xpc.xp_numfiles);
+ for (int i = 0; i < xpc.xp_numfiles; i++) {
+ tv_list_append_string(rettv->vval.v_list, (const char *)xpc.xp_files[i],
+ -1);
+ }
+ ExpandCleanup(&xpc);
+ }
+ } else
+ rettv->vval.v_string = NULL;
+}
+
+/// "globpath()" function
+static void f_globpath(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ int flags = 0; // Flags for globpath.
+ bool error = false;
+
+ // Return a string, or a list if the optional third argument is non-zero.
+ rettv->v_type = VAR_STRING;
+
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ // When the optional second argument is non-zero, don't remove matches
+ // for 'wildignore' and don't put matches for 'suffixes' at the end.
+ if (tv_get_number_chk(&argvars[2], &error)) {
+ flags |= WILD_KEEP_ALL;
+ }
+
+ if (argvars[3].v_type != VAR_UNKNOWN) {
+ if (tv_get_number_chk(&argvars[3], &error)) {
+ tv_list_set_ret(rettv, NULL);
+ }
+ if (argvars[4].v_type != VAR_UNKNOWN
+ && tv_get_number_chk(&argvars[4], &error)) {
+ flags |= WILD_ALLLINKS;
+ }
+ }
+ }
+
+ char buf1[NUMBUFLEN];
+ const char *const file = tv_get_string_buf_chk(&argvars[1], buf1);
+ if (file != NULL && !error) {
+ garray_T ga;
+ ga_init(&ga, (int)sizeof(char_u *), 10);
+ globpath((char_u *)tv_get_string(&argvars[0]), (char_u *)file, &ga, flags);
+
+ if (rettv->v_type == VAR_STRING) {
+ rettv->vval.v_string = ga_concat_strings_sep(&ga, "\n");
+ } else {
+ tv_list_alloc_ret(rettv, ga.ga_len);
+ for (int i = 0; i < ga.ga_len; i++) {
+ tv_list_append_string(rettv->vval.v_list,
+ ((const char **)(ga.ga_data))[i], -1);
+ }
+ }
+
+ ga_clear_strings(&ga);
+ } else {
+ rettv->vval.v_string = NULL;
+ }
+}
+
+// "glob2regpat()" function
+static void f_glob2regpat(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const char *const pat = tv_get_string_chk(&argvars[0]); // NULL on type error
+
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = ((pat == NULL)
+ ? NULL
+ : file_pat_to_reg_pat((char_u *)pat, NULL, NULL,
+ false));
+}
+
+/// "has()" function
+static void f_has(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ static const char *const has_list[] = {
+#if defined(BSD) && !defined(__APPLE__)
+ "bsd",
+#endif
+#ifdef UNIX
+ "unix",
+#endif
+#if defined(WIN32)
+ "win32",
+#endif
+#if defined(WIN64) || defined(_WIN64)
+ "win64",
+#endif
+ "fname_case",
+#ifdef HAVE_ACL
+ "acl",
+#endif
+ "autochdir",
+ "arabic",
+ "autocmd",
+ "browsefilter",
+ "byte_offset",
+ "cindent",
+ "cmdline_compl",
+ "cmdline_hist",
+ "comments",
+ "conceal",
+ "cscope",
+ "cursorbind",
+ "cursorshape",
+#ifdef DEBUG
+ "debug",
+#endif
+ "dialog_con",
+ "diff",
+ "digraphs",
+ "eval", // always present, of course!
+ "ex_extra",
+ "extra_search",
+ "file_in_path",
+ "filterpipe",
+ "find_in_path",
+ "float",
+ "folding",
+#if defined(UNIX)
+ "fork",
+#endif
+ "gettext",
+#if defined(HAVE_ICONV)
+ "iconv",
+#endif
+ "insert_expand",
+ "jumplist",
+ "keymap",
+ "lambda",
+ "langmap",
+ "libcall",
+ "linebreak",
+ "lispindent",
+ "listcmds",
+ "localmap",
+#ifdef __APPLE__
+ "mac",
+ "macunix",
+ "osx",
+ "osxdarwin",
+#endif
+ "menu",
+ "mksession",
+ "modify_fname",
+ "mouse",
+ "multi_byte",
+ "multi_lang",
+ "num64",
+ "packages",
+ "path_extra",
+ "persistent_undo",
+ "postscript",
+ "printer",
+ "profile",
+ "pythonx",
+ "reltime",
+ "quickfix",
+ "rightleft",
+ "scrollbind",
+ "showcmd",
+ "cmdline_info",
+ "shada",
+ "signs",
+ "smartindent",
+ "startuptime",
+ "statusline",
+ "spell",
+ "syntax",
+#if !defined(UNIX)
+ "system", // TODO(SplinterOfChaos): This IS defined for UNIX!
+#endif
+ "tablineat",
+ "tag_binary",
+ "termguicolors",
+ "termresponse",
+ "textobjects",
+ "timers",
+ "title",
+ "user-commands", // was accidentally included in 5.4
+ "user_commands",
+ "vertsplit",
+ "virtualedit",
+ "visual",
+ "visualextra",
+ "vreplace",
+ "wildignore",
+ "wildmenu",
+ "windows",
+ "winaltkeys",
+ "writebackup",
+#if defined(HAVE_WSL)
+ "wsl",
+#endif
+ "nvim",
+ };
+
+ bool n = false;
+ const char *const name = tv_get_string(&argvars[0]);
+ for (size_t i = 0; i < ARRAY_SIZE(has_list); i++) {
+ if (STRICMP(name, has_list[i]) == 0) {
+ n = true;
+ break;
+ }
+ }
+
+ if (!n) {
+ if (STRNICMP(name, "patch", 5) == 0) {
+ if (name[5] == '-'
+ && strlen(name) >= 11
+ && ascii_isdigit(name[6])
+ && ascii_isdigit(name[8])
+ && ascii_isdigit(name[10])) {
+ int major = atoi(name + 6);
+ int minor = atoi(name + 8);
+
+ // Expect "patch-9.9.01234".
+ n = (major < VIM_VERSION_MAJOR
+ || (major == VIM_VERSION_MAJOR
+ && (minor < VIM_VERSION_MINOR
+ || (minor == VIM_VERSION_MINOR
+ && has_vim_patch(atoi(name + 10))))));
+ } else {
+ n = has_vim_patch(atoi(name + 5));
+ }
+ } else if (STRNICMP(name, "nvim-", 5) == 0) {
+ // Expect "nvim-x.y.z"
+ n = has_nvim_version(name + 5);
+ } else if (STRICMP(name, "vim_starting") == 0) {
+ n = (starting != 0);
+ } else if (STRICMP(name, "ttyin") == 0) {
+ n = stdin_isatty;
+ } else if (STRICMP(name, "ttyout") == 0) {
+ n = stdout_isatty;
+ } else if (STRICMP(name, "multi_byte_encoding") == 0) {
+ n = has_mbyte != 0;
+ } else if (STRICMP(name, "syntax_items") == 0) {
+ n = syntax_present(curwin);
+#ifdef UNIX
+ } else if (STRICMP(name, "unnamedplus") == 0) {
+ n = eval_has_provider("clipboard");
+#endif
+ }
+ }
+
+ if (!n && eval_has_provider(name)) {
+ n = true;
+ }
+
+ rettv->vval.v_number = n;
+}
+
+/*
+ * "has_key()" function
+ */
+static void f_has_key(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ if (argvars[0].v_type != VAR_DICT) {
+ EMSG(_(e_dictreq));
+ return;
+ }
+ if (argvars[0].vval.v_dict == NULL)
+ return;
+
+ rettv->vval.v_number = tv_dict_find(argvars[0].vval.v_dict,
+ tv_get_string(&argvars[1]),
+ -1) != NULL;
+}
+
+/// `haslocaldir([{win}[, {tab}]])` function
+///
+/// Returns `1` if the scope object has a local directory, `0` otherwise. If a
+/// scope object is not specified the current one is implied. This function
+/// share a lot of code with `f_getcwd`.
+///
+/// @pre The arguments must be of type number.
+/// @pre There may not be more than two arguments.
+/// @pre An argument may not be -1 if preceding arguments are not all -1.
+///
+/// @post The return value will be either the number `1` or `0`.
+static void f_haslocaldir(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ // Possible scope of working directory to return.
+ CdScope scope = kCdScopeInvalid;
+
+ // Numbers of the scope objects (window, tab) we want the working directory
+ // of. A `-1` means to skip this scope, a `0` means the current object.
+ int scope_number[] = {
+ [kCdScopeWindow] = 0, // Number of window to look at.
+ [kCdScopeTab ] = 0, // Number of tab to look at.
+ };
+
+ tabpage_T *tp = curtab; // The tabpage to look at.
+ win_T *win = curwin; // The window to look at.
+
+ rettv->v_type = VAR_NUMBER;
+ rettv->vval.v_number = 0;
+
+ // Pre-conditions and scope extraction together
+ for (int i = MIN_CD_SCOPE; i < MAX_CD_SCOPE; i++) {
+ if (argvars[i].v_type == VAR_UNKNOWN) {
+ break;
+ }
+ if (argvars[i].v_type != VAR_NUMBER) {
+ EMSG(_(e_invarg));
+ return;
+ }
+ scope_number[i] = argvars[i].vval.v_number;
+ if (scope_number[i] < -1) {
+ EMSG(_(e_invarg));
+ return;
+ }
+ // Use the narrowest scope the user requested
+ if (scope_number[i] >= 0 && scope == kCdScopeInvalid) {
+ // The scope is the current iteration step.
+ scope = i;
+ } else if (scope_number[i] < 0) {
+ scope = i + 1;
+ }
+ }
+
+ // If the user didn't specify anything, default to window scope
+ if (scope == kCdScopeInvalid) {
+ scope = MIN_CD_SCOPE;
+ }
+
+ // Find the tabpage by number
+ if (scope_number[kCdScopeTab] > 0) {
+ tp = find_tabpage(scope_number[kCdScopeTab]);
+ if (!tp) {
+ EMSG(_("E5000: Cannot find tab number."));
+ return;
+ }
+ }
+
+ // Find the window in `tp` by number, `NULL` if none.
+ if (scope_number[kCdScopeWindow] >= 0) {
+ if (scope_number[kCdScopeTab] < 0) {
+ EMSG(_("E5001: Higher scope cannot be -1 if lower scope is >= 0."));
+ return;
+ }
+
+ if (scope_number[kCdScopeWindow] > 0) {
+ win = find_win_by_nr(&argvars[0], tp);
+ if (!win) {
+ EMSG(_("E5002: Cannot find window number."));
+ return;
+ }
+ }
+ }
+
+ switch (scope) {
+ case kCdScopeWindow:
+ assert(win);
+ rettv->vval.v_number = win->w_localdir ? 1 : 0;
+ break;
+ case kCdScopeTab:
+ assert(tp);
+ rettv->vval.v_number = tp->tp_localdir ? 1 : 0;
+ break;
+ case kCdScopeGlobal:
+ // The global scope never has a local directory
+ break;
+ case kCdScopeInvalid:
+ // We should never get here
+ assert(false);
+ }
+}
+
+/*
+ * "hasmapto()" function
+ */
+static void f_hasmapto(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const char *mode;
+ const char *const name = tv_get_string(&argvars[0]);
+ bool abbr = false;
+ char buf[NUMBUFLEN];
+ if (argvars[1].v_type == VAR_UNKNOWN) {
+ mode = "nvo";
+ } else {
+ mode = tv_get_string_buf(&argvars[1], buf);
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ abbr = tv_get_number(&argvars[2]);
+ }
+ }
+
+ if (map_to_exists(name, mode, abbr)) {
+ rettv->vval.v_number = true;
+ } else {
+ rettv->vval.v_number = false;
+ }
+}
+
+/*
+ * "histadd()" function
+ */
+static void f_histadd(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ HistoryType histype;
+
+ rettv->vval.v_number = false;
+ if (check_secure()) {
+ return;
+ }
+ const char *str = tv_get_string_chk(&argvars[0]); // NULL on type error
+ histype = str != NULL ? get_histtype(str, strlen(str), false) : HIST_INVALID;
+ if (histype != HIST_INVALID) {
+ char buf[NUMBUFLEN];
+ str = tv_get_string_buf(&argvars[1], buf);
+ if (*str != NUL) {
+ init_history();
+ add_to_history(histype, (char_u *)str, false, NUL);
+ rettv->vval.v_number = true;
+ return;
+ }
+ }
+}
+
+/*
+ * "histdel()" function
+ */
+static void f_histdel(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ int n;
+ const char *const str = tv_get_string_chk(&argvars[0]); // NULL on type error
+ if (str == NULL) {
+ n = 0;
+ } else if (argvars[1].v_type == VAR_UNKNOWN) {
+ // only one argument: clear entire history
+ n = clr_history(get_histtype(str, strlen(str), false));
+ } else if (argvars[1].v_type == VAR_NUMBER) {
+ // index given: remove that entry
+ n = del_history_idx(get_histtype(str, strlen(str), false),
+ (int)tv_get_number(&argvars[1]));
+ } else {
+ // string given: remove all matching entries
+ char buf[NUMBUFLEN];
+ n = del_history_entry(get_histtype(str, strlen(str), false),
+ (char_u *)tv_get_string_buf(&argvars[1], buf));
+ }
+ rettv->vval.v_number = n;
+}
+
+/*
+ * "histget()" function
+ */
+static void f_histget(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ HistoryType type;
+ int idx;
+
+ const char *const str = tv_get_string_chk(&argvars[0]); // NULL on type error
+ if (str == NULL) {
+ rettv->vval.v_string = NULL;
+ } else {
+ type = get_histtype(str, strlen(str), false);
+ if (argvars[1].v_type == VAR_UNKNOWN) {
+ idx = get_history_idx(type);
+ } else {
+ idx = (int)tv_get_number_chk(&argvars[1], NULL);
+ }
+ // -1 on type error
+ rettv->vval.v_string = vim_strsave(get_history_entry(type, idx));
+ }
+ rettv->v_type = VAR_STRING;
+}
+
+/*
+ * "histnr()" function
+ */
+static void f_histnr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const char *const history = tv_get_string_chk(&argvars[0]);
+ HistoryType i = history == NULL
+ ? HIST_INVALID
+ : get_histtype(history, strlen(history), false);
+ if (i != HIST_INVALID) {
+ i = get_history_idx(i);
+ }
+ rettv->vval.v_number = i;
+}
+
+/*
+ * "highlightID(name)" function
+ */
+static void f_hlID(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = syn_name2id(
+ (const char_u *)tv_get_string(&argvars[0]));
+}
+
+/*
+ * "highlight_exists()" function
+ */
+static void f_hlexists(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = highlight_exists(
+ (const char_u *)tv_get_string(&argvars[0]));
+}
+
+/*
+ * "hostname()" function
+ */
+static void f_hostname(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ char hostname[256];
+
+ os_get_hostname(hostname, 256);
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = vim_strsave((char_u *)hostname);
+}
+
+/*
+ * iconv() function
+ */
+static void f_iconv(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ vimconv_T vimconv;
+
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = NULL;
+
+ const char *const str = tv_get_string(&argvars[0]);
+ char buf1[NUMBUFLEN];
+ char_u *const from = enc_canonize(enc_skip(
+ (char_u *)tv_get_string_buf(&argvars[1], buf1)));
+ char buf2[NUMBUFLEN];
+ char_u *const to = enc_canonize(enc_skip(
+ (char_u *)tv_get_string_buf(&argvars[2], buf2)));
+ vimconv.vc_type = CONV_NONE;
+ convert_setup(&vimconv, from, to);
+
+ // If the encodings are equal, no conversion needed.
+ if (vimconv.vc_type == CONV_NONE) {
+ rettv->vval.v_string = (char_u *)xstrdup(str);
+ } else {
+ rettv->vval.v_string = string_convert(&vimconv, (char_u *)str, NULL);
+ }
+
+ convert_setup(&vimconv, NULL, NULL);
+ xfree(from);
+ xfree(to);
+}
+
+/*
+ * "indent()" function
+ */
+static void f_indent(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const linenr_T lnum = tv_get_lnum(argvars);
+ if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count) {
+ rettv->vval.v_number = get_indent_lnum(lnum);
+ } else {
+ rettv->vval.v_number = -1;
+ }
+}
+
+/*
+ * "index()" function
+ */
+static void f_index(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ long idx = 0;
+ bool ic = false;
+
+ rettv->vval.v_number = -1;
+ if (argvars[0].v_type != VAR_LIST) {
+ EMSG(_(e_listreq));
+ return;
+ }
+ list_T *const l = argvars[0].vval.v_list;
+ if (l != NULL) {
+ listitem_T *item = tv_list_first(l);
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ bool error = false;
+
+ // Start at specified item.
+ idx = tv_list_uidx(l, tv_get_number_chk(&argvars[2], &error));
+ if (error || idx == -1) {
+ item = NULL;
+ } else {
+ item = tv_list_find(l, idx);
+ assert(item != NULL);
+ }
+ if (argvars[3].v_type != VAR_UNKNOWN) {
+ ic = !!tv_get_number_chk(&argvars[3], &error);
+ if (error) {
+ item = NULL;
+ }
+ }
+ }
+
+ for (; item != NULL; item = TV_LIST_ITEM_NEXT(l, item), idx++) {
+ if (tv_equal(TV_LIST_ITEM_TV(item), &argvars[1], ic, false)) {
+ rettv->vval.v_number = idx;
+ break;
+ }
+ }
+ }
+}
+
+static bool inputsecret_flag = false;
+
+/*
+ * "input()" function
+ * Also handles inputsecret() when inputsecret is set.
+ */
+static void f_input(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ get_user_input(argvars, rettv, FALSE, inputsecret_flag);
+}
+
+/*
+ * "inputdialog()" function
+ */
+static void f_inputdialog(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ get_user_input(argvars, rettv, TRUE, inputsecret_flag);
+}
+
+/*
+ * "inputlist()" function
+ */
+static void f_inputlist(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ int selected;
+ int mouse_used;
+
+ if (argvars[0].v_type != VAR_LIST) {
+ EMSG2(_(e_listarg), "inputlist()");
+ return;
+ }
+
+ msg_start();
+ msg_row = Rows - 1; // for when 'cmdheight' > 1
+ lines_left = Rows; // avoid more prompt
+ msg_scroll = true;
+ msg_clr_eos();
+
+ TV_LIST_ITER_CONST(argvars[0].vval.v_list, li, {
+ msg_puts(tv_get_string(TV_LIST_ITEM_TV(li)));
+ msg_putchar('\n');
+ });
+
+ // Ask for choice.
+ selected = prompt_for_number(&mouse_used);
+ if (mouse_used) {
+ selected -= lines_left;
+ }
+
+ rettv->vval.v_number = selected;
+}
+
+
+static garray_T ga_userinput = { 0, 0, sizeof(tasave_T), 4, NULL };
+
+/// "inputrestore()" function
+static void f_inputrestore(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ if (!GA_EMPTY(&ga_userinput)) {
+ ga_userinput.ga_len--;
+ restore_typeahead((tasave_T *)(ga_userinput.ga_data)
+ + ga_userinput.ga_len);
+ // default return is zero == OK
+ } else if (p_verbose > 1) {
+ verb_msg(_("called inputrestore() more often than inputsave()"));
+ rettv->vval.v_number = 1; // Failed
+ }
+}
+
+/// "inputsave()" function
+static void f_inputsave(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ // Add an entry to the stack of typeahead storage.
+ tasave_T *p = GA_APPEND_VIA_PTR(tasave_T, &ga_userinput);
+ save_typeahead(p);
+}
+
+/// "inputsecret()" function
+static void f_inputsecret(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ cmdline_star++;
+ inputsecret_flag = true;
+ f_input(argvars, rettv, NULL);
+ cmdline_star--;
+ inputsecret_flag = false;
+}
+
+/*
+ * "insert()" function
+ */
+static void f_insert(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ list_T *l;
+ bool error = false;
+
+ if (argvars[0].v_type != VAR_LIST) {
+ EMSG2(_(e_listarg), "insert()");
+ } else if (!tv_check_lock(tv_list_locked((l = argvars[0].vval.v_list)),
+ N_("insert() argument"), TV_TRANSLATE)) {
+ long before = 0;
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ before = tv_get_number_chk(&argvars[2], &error);
+ }
+ if (error) {
+ // type error; errmsg already given
+ return;
+ }
+
+ listitem_T *item = NULL;
+ if (before != tv_list_len(l)) {
+ item = tv_list_find(l, before);
+ if (item == NULL) {
+ EMSGN(_(e_listidx), before);
+ l = NULL;
+ }
+ }
+ if (l != NULL) {
+ tv_list_insert_tv(l, &argvars[1], item);
+ tv_copy(&argvars[0], rettv);
+ }
+ }
+}
+
+/*
+ * "invert(expr)" function
+ */
+static void f_invert(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = ~tv_get_number_chk(&argvars[0], NULL);
+}
+
+/*
+ * "isdirectory()" function
+ */
+static void f_isdirectory(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = os_isdir((const char_u *)tv_get_string(&argvars[0]));
+}
+
+/*
+ * "islocked()" function
+ */
+static void f_islocked(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ lval_T lv;
+ dictitem_T *di;
+
+ rettv->vval.v_number = -1;
+ const char_u *const end = get_lval((char_u *)tv_get_string(&argvars[0]),
+ NULL,
+ &lv, false, false,
+ GLV_NO_AUTOLOAD|GLV_READ_ONLY,
+ FNE_CHECK_START);
+ if (end != NULL && lv.ll_name != NULL) {
+ if (*end != NUL) {
+ EMSG(_(e_trailing));
+ } else {
+ if (lv.ll_tv == NULL) {
+ di = find_var((const char *)lv.ll_name, lv.ll_name_len, NULL, true);
+ if (di != NULL) {
+ // Consider a variable locked when:
+ // 1. the variable itself is locked
+ // 2. the value of the variable is locked.
+ // 3. the List or Dict value is locked.
+ rettv->vval.v_number = ((di->di_flags & DI_FLAGS_LOCK)
+ || tv_islocked(&di->di_tv));
+ }
+ } else if (lv.ll_range) {
+ EMSG(_("E786: Range not allowed"));
+ } else if (lv.ll_newkey != NULL) {
+ EMSG2(_(e_dictkey), lv.ll_newkey);
+ } else if (lv.ll_list != NULL) {
+ // List item.
+ rettv->vval.v_number = tv_islocked(TV_LIST_ITEM_TV(lv.ll_li));
+ } else {
+ // Dictionary item.
+ rettv->vval.v_number = tv_islocked(&lv.ll_di->di_tv);
+ }
+ }
+ }
+
+ clear_lval(&lv);
+}
+
+// "isinf()" function
+static void f_isinf(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ if (argvars[0].v_type == VAR_FLOAT
+ && xisinf(argvars[0].vval.v_float)) {
+ rettv->vval.v_number = argvars[0].vval.v_float > 0.0 ? 1 : -1;
+ }
+}
+
+// "isnan()" function
+static void f_isnan(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = argvars[0].v_type == VAR_FLOAT
+ && xisnan(argvars[0].vval.v_float);
+}
+
+/// "id()" function
+static void f_id(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+ FUNC_ATTR_NONNULL_ALL
+{
+ const int len = vim_vsnprintf_typval(NULL, 0, "%p", dummy_ap, argvars);
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = xmalloc(len + 1);
+ vim_vsnprintf_typval((char *)rettv->vval.v_string, len + 1, "%p",
+ dummy_ap, argvars);
+}
+
+/*
+ * "items(dict)" function
+ */
+static void f_items(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ dict_list(argvars, rettv, 2);
+}
+
+// "jobpid(id)" function
+static void f_jobpid(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->v_type = VAR_NUMBER;
+ rettv->vval.v_number = 0;
+
+ if (check_restricted() || check_secure()) {
+ return;
+ }
+
+ if (argvars[0].v_type != VAR_NUMBER) {
+ EMSG(_(e_invarg));
+ return;
+ }
+
+ Channel *data = find_job(argvars[0].vval.v_number, true);
+ if (!data) {
+ return;
+ }
+
+ Process *proc = (Process *)&data->stream.proc;
+ rettv->vval.v_number = proc->pid;
+}
+
+// "jobresize(job, width, height)" function
+static void f_jobresize(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->v_type = VAR_NUMBER;
+ rettv->vval.v_number = 0;
+
+ if (check_restricted() || check_secure()) {
+ return;
+ }
+
+ if (argvars[0].v_type != VAR_NUMBER || argvars[1].v_type != VAR_NUMBER
+ || argvars[2].v_type != VAR_NUMBER) {
+ // job id, width, height
+ EMSG(_(e_invarg));
+ return;
+ }
+
+
+ Channel *data = find_job(argvars[0].vval.v_number, true);
+ if (!data) {
+ return;
+ }
+
+ if (data->stream.proc.type != kProcessTypePty) {
+ EMSG(_(e_channotpty));
+ return;
+ }
+
+ pty_process_resize(&data->stream.pty, argvars[1].vval.v_number,
+ argvars[2].vval.v_number);
+ rettv->vval.v_number = 1;
+}
+
+// "jobstart()" function
+static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->v_type = VAR_NUMBER;
+ rettv->vval.v_number = 0;
+
+ if (check_restricted() || check_secure()) {
+ return;
+ }
+
+ bool executable = true;
+ char **argv = tv_to_argv(&argvars[0], NULL, &executable);
+ char **env = NULL;
+ if (!argv) {
+ rettv->vval.v_number = executable ? 0 : -1;
+ return; // Did error message in tv_to_argv.
+ }
+
+ if (argvars[1].v_type != VAR_DICT && argvars[1].v_type != VAR_UNKNOWN) {
+ // Wrong argument types
+ EMSG2(_(e_invarg2), "expected dictionary");
+ shell_free_argv(argv);
+ return;
+ }
+
+
+ dict_T *job_opts = NULL;
+ bool detach = false;
+ bool rpc = false;
+ bool pty = false;
+ bool clear_env = false;
+ bool overlapped = false;
+ CallbackReader on_stdout = CALLBACK_READER_INIT,
+ on_stderr = CALLBACK_READER_INIT;
+ Callback on_exit = CALLBACK_NONE;
+ char *cwd = NULL;
+ if (argvars[1].v_type == VAR_DICT) {
+ job_opts = argvars[1].vval.v_dict;
+
+ detach = tv_dict_get_number(job_opts, "detach") != 0;
+ rpc = tv_dict_get_number(job_opts, "rpc") != 0;
+ pty = tv_dict_get_number(job_opts, "pty") != 0;
+ clear_env = tv_dict_get_number(job_opts, "clear_env") != 0;
+ overlapped = tv_dict_get_number(job_opts, "overlapped") != 0;
+
+ if (pty && rpc) {
+ EMSG2(_(e_invarg2), "job cannot have both 'pty' and 'rpc' options set");
+ shell_free_argv(argv);
+ return;
+ }
+
+#ifdef WIN32
+ if (pty && overlapped) {
+ EMSG2(_(e_invarg2),
+ "job cannot have both 'pty' and 'overlapped' options set");
+ shell_free_argv(argv);
+ return;
+ }
+#endif
+
+ char *new_cwd = tv_dict_get_string(job_opts, "cwd", false);
+ if (new_cwd && strlen(new_cwd) > 0) {
+ cwd = new_cwd;
+ // The new cwd must be a directory.
+ if (!os_isdir_executable((const char *)cwd)) {
+ EMSG2(_(e_invarg2), "expected valid directory");
+ shell_free_argv(argv);
+ return;
+ }
+ }
+ dictitem_T *job_env = tv_dict_find(job_opts, S_LEN("env"));
+ if (job_env) {
+ if (job_env->di_tv.v_type != VAR_DICT) {
+ EMSG2(_(e_invarg2), "env");
+ shell_free_argv(argv);
+ return;
+ }
+
+ size_t custom_env_size = (size_t)tv_dict_len(job_env->di_tv.vval.v_dict);
+ size_t i = 0;
+ size_t env_size = 0;
+
+ if (clear_env) {
+ // + 1 for last null entry
+ env = xmalloc((custom_env_size + 1) * sizeof(*env));
+ env_size = 0;
+ } else {
+ env_size = os_get_fullenv_size();
+
+ env = xmalloc((custom_env_size + env_size + 1) * sizeof(*env));
+
+ os_copy_fullenv(env, env_size);
+ i = env_size;
+ }
+ assert(env); // env must be allocated at this point
+
+ TV_DICT_ITER(job_env->di_tv.vval.v_dict, var, {
+ const char *str = tv_get_string(&var->di_tv);
+ assert(str);
+ size_t len = STRLEN(var->di_key) + strlen(str) + strlen("=") + 1;
+ env[i] = xmalloc(len);
+ snprintf(env[i], len, "%s=%s", (char *)var->di_key, str);
+ i++;
+ });
+
+ // must be null terminated
+ env[env_size + custom_env_size] = NULL;
+ }
+
+
+ if (!common_job_callbacks(job_opts, &on_stdout, &on_stderr, &on_exit)) {
+ shell_free_argv(argv);
+ return;
+ }
+ }
+
+ uint16_t width = 0, height = 0;
+ char *term_name = NULL;
+
+ if (pty) {
+ width = (uint16_t)tv_dict_get_number(job_opts, "width");
+ height = (uint16_t)tv_dict_get_number(job_opts, "height");
+ term_name = tv_dict_get_string(job_opts, "TERM", true);
+ }
+
+ Channel *chan = channel_job_start(argv, on_stdout, on_stderr, on_exit, pty,
+ rpc, overlapped, detach, cwd, width, height,
+ term_name, env, &rettv->vval.v_number);
+ if (chan) {
+ channel_create_event(chan, NULL);
+ }
+}
+
+// "jobstop()" function
+static void f_jobstop(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->v_type = VAR_NUMBER;
+ rettv->vval.v_number = 0;
+
+ if (check_restricted() || check_secure()) {
+ return;
+ }
+
+ if (argvars[0].v_type != VAR_NUMBER) {
+ // Only argument is the job id
+ EMSG(_(e_invarg));
+ return;
+ }
+
+ Channel *data = find_job(argvars[0].vval.v_number, false);
+ if (!data) {
+ return;
+ }
+
+ const char *error = NULL;
+ if (data->is_rpc) {
+ // Ignore return code, but show error later.
+ (void)channel_close(data->id, kChannelPartRpc, &error);
+ }
+ process_stop((Process *)&data->stream.proc);
+ rettv->vval.v_number = 1;
+ if (error) {
+ EMSG(error);
+ }
+}
+
+// "jobwait(ids[, timeout])" function
+static void f_jobwait(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->v_type = VAR_NUMBER;
+ rettv->vval.v_number = 0;
+
+ if (check_restricted() || check_secure()) {
+ return;
+ }
+ if (argvars[0].v_type != VAR_LIST || (argvars[1].v_type != VAR_NUMBER
+ && argvars[1].v_type != VAR_UNKNOWN)) {
+ EMSG(_(e_invarg));
+ return;
+ }
+
+ ui_busy_start();
+ list_T *args = argvars[0].vval.v_list;
+ Channel **jobs = xcalloc(tv_list_len(args), sizeof(*jobs));
+ MultiQueue *waiting_jobs = multiqueue_new_parent(loop_on_put, &main_loop);
+
+ // Validate, prepare jobs for waiting.
+ int i = 0;
+ TV_LIST_ITER_CONST(args, arg, {
+ Channel *chan = NULL;
+ if (TV_LIST_ITEM_TV(arg)->v_type != VAR_NUMBER
+ || !(chan = find_job(TV_LIST_ITEM_TV(arg)->vval.v_number, false))) {
+ jobs[i] = NULL; // Invalid job.
+ } else {
+ jobs[i] = chan;
+ channel_incref(chan);
+ if (chan->stream.proc.status < 0) {
+ // Process any pending events on the job's queue before temporarily
+ // replacing it.
+ multiqueue_process_events(chan->events);
+ multiqueue_replace_parent(chan->events, waiting_jobs);
+ }
+ }
+ i++;
+ });
+
+ int remaining = -1;
+ uint64_t before = 0;
+ if (argvars[1].v_type == VAR_NUMBER && argvars[1].vval.v_number >= 0) {
+ remaining = argvars[1].vval.v_number;
+ before = os_hrtime();
+ }
+
+ for (i = 0; i < tv_list_len(args); i++) {
+ if (remaining == 0) {
+ break; // Timeout.
+ }
+ if (jobs[i] == NULL) {
+ continue; // Invalid job, will assign status=-3 below.
+ }
+ int status = process_wait(&jobs[i]->stream.proc, remaining,
+ waiting_jobs);
+ if (status < 0) {
+ break; // Interrupted (CTRL-C) or timeout, skip remaining jobs.
+ }
+ if (remaining > 0) {
+ uint64_t now = os_hrtime();
+ remaining = MIN(0, remaining - (int)((now - before) / 1000000));
+ before = now;
+ }
+ }
+
+ list_T *const rv = tv_list_alloc(tv_list_len(args));
+
+ // For each job:
+ // * Restore its parent queue if the job is still alive.
+ // * Append its status to the output list, or:
+ // -3 for "invalid job id"
+ // -2 for "interrupted" (user hit CTRL-C)
+ // -1 for jobs that were skipped or timed out
+ for (i = 0; i < tv_list_len(args); i++) {
+ if (jobs[i] == NULL) {
+ tv_list_append_number(rv, -3);
+ continue;
+ }
+ multiqueue_process_events(jobs[i]->events);
+ multiqueue_replace_parent(jobs[i]->events, main_loop.events);
+
+ tv_list_append_number(rv, jobs[i]->stream.proc.status);
+ channel_decref(jobs[i]);
+ }
+
+ multiqueue_free(waiting_jobs);
+ xfree(jobs);
+ ui_busy_stop();
+ tv_list_ref(rv);
+ rettv->v_type = VAR_LIST;
+ rettv->vval.v_list = rv;
+}
+
+/*
+ * "join()" function
+ */
+static void f_join(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ if (argvars[0].v_type != VAR_LIST) {
+ EMSG(_(e_listreq));
+ return;
+ }
+ const char *const sep = (argvars[1].v_type == VAR_UNKNOWN
+ ? " "
+ : tv_get_string_chk(&argvars[1]));
+
+ rettv->v_type = VAR_STRING;
+
+ if (sep != NULL) {
+ garray_T ga;
+ ga_init(&ga, (int)sizeof(char), 80);
+ tv_list_join(&ga, argvars[0].vval.v_list, sep);
+ ga_append(&ga, NUL);
+ rettv->vval.v_string = (char_u *)ga.ga_data;
+ } else {
+ rettv->vval.v_string = NULL;
+ }
+}
+
+/// json_decode() function
+static void f_json_decode(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ char numbuf[NUMBUFLEN];
+ const char *s = NULL;
+ char *tofree = NULL;
+ size_t len;
+ if (argvars[0].v_type == VAR_LIST) {
+ if (!encode_vim_list_to_buf(argvars[0].vval.v_list, &len, &tofree)) {
+ EMSG(_("E474: Failed to convert list to string"));
+ return;
+ }
+ s = tofree;
+ if (s == NULL) {
+ assert(len == 0);
+ s = "";
+ }
+ } else {
+ s = tv_get_string_buf_chk(&argvars[0], numbuf);
+ if (s) {
+ len = strlen(s);
+ } else {
+ return;
+ }
+ }
+ if (json_decode_string(s, len, rettv) == FAIL) {
+ emsgf(_("E474: Failed to parse %.*s"), (int)len, s);
+ rettv->v_type = VAR_NUMBER;
+ rettv->vval.v_number = 0;
+ }
+ assert(rettv->v_type != VAR_UNKNOWN);
+ xfree(tofree);
+}
+
+/// json_encode() function
+static void f_json_encode(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = (char_u *)encode_tv2json(&argvars[0], NULL);
+}
+
+/*
+ * "keys()" function
+ */
+static void f_keys(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ dict_list(argvars, rettv, 0);
+}
+
+/*
+ * "last_buffer_nr()" function.
+ */
+static void f_last_buffer_nr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ int n = 0;
+
+ FOR_ALL_BUFFERS(buf) {
+ if (n < buf->b_fnum) {
+ n = buf->b_fnum;
+ }
+ }
+
+ rettv->vval.v_number = n;
+}
+
+/*
+ * "len()" function
+ */
+static void f_len(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ switch (argvars[0].v_type) {
+ case VAR_STRING:
+ case VAR_NUMBER: {
+ rettv->vval.v_number = (varnumber_T)strlen(
+ tv_get_string(&argvars[0]));
+ break;
+ }
+ case VAR_LIST: {
+ rettv->vval.v_number = tv_list_len(argvars[0].vval.v_list);
+ break;
+ }
+ case VAR_DICT: {
+ rettv->vval.v_number = tv_dict_len(argvars[0].vval.v_dict);
+ break;
+ }
+ case VAR_UNKNOWN:
+ case VAR_BOOL:
+ case VAR_SPECIAL:
+ case VAR_FLOAT:
+ case VAR_PARTIAL:
+ case VAR_FUNC: {
+ EMSG(_("E701: Invalid type for len()"));
+ break;
+ }
+ }
+}
+
+static void libcall_common(typval_T *argvars, typval_T *rettv, int out_type)
+{
+ rettv->v_type = out_type;
+ if (out_type != VAR_NUMBER) {
+ rettv->vval.v_string = NULL;
+ }
+
+ if (check_restricted() || check_secure()) {
+ return;
+ }
+
+ // The first two args (libname and funcname) must be strings
+ if (argvars[0].v_type != VAR_STRING || argvars[1].v_type != VAR_STRING) {
+ return;
+ }
+
+ const char *libname = (char *)argvars[0].vval.v_string;
+ const char *funcname = (char *)argvars[1].vval.v_string;
+
+ VarType in_type = argvars[2].v_type;
+
+ // input variables
+ char *str_in = (in_type == VAR_STRING)
+ ? (char *)argvars[2].vval.v_string : NULL;
+ int int_in = argvars[2].vval.v_number;
+
+ // output variables
+ char **str_out = (out_type == VAR_STRING)
+ ? (char **)&rettv->vval.v_string : NULL;
+ int int_out = 0;
+
+ bool success = os_libcall(libname, funcname,
+ str_in, int_in,
+ str_out, &int_out);
+
+ if (!success) {
+ EMSG2(_(e_libcall), funcname);
+ return;
+ }
+
+ if (out_type == VAR_NUMBER) {
+ rettv->vval.v_number = (varnumber_T)int_out;
+ }
+}
+
+/*
+ * "libcall()" function
+ */
+static void f_libcall(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ libcall_common(argvars, rettv, VAR_STRING);
+}
+
+/*
+ * "libcallnr()" function
+ */
+static void f_libcallnr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ libcall_common(argvars, rettv, VAR_NUMBER);
+}
+
+/*
+ * "line(string)" function
+ */
+static void f_line(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ linenr_T lnum = 0;
+ pos_T *fp;
+ int fnum;
+
+ fp = var2fpos(&argvars[0], TRUE, &fnum);
+ if (fp != NULL)
+ lnum = fp->lnum;
+ rettv->vval.v_number = lnum;
+}
+
+/*
+ * "line2byte(lnum)" function
+ */
+static void f_line2byte(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const linenr_T lnum = tv_get_lnum(argvars);
+ if (lnum < 1 || lnum > curbuf->b_ml.ml_line_count + 1) {
+ rettv->vval.v_number = -1;
+ } else {
+ rettv->vval.v_number = ml_find_line_or_offset(curbuf, lnum, NULL, false);
+ }
+ if (rettv->vval.v_number >= 0) {
+ rettv->vval.v_number++;
+ }
+}
+
+/*
+ * "lispindent(lnum)" function
+ */
+static void f_lispindent(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const pos_T pos = curwin->w_cursor;
+ const linenr_T lnum = tv_get_lnum(argvars);
+ if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count) {
+ curwin->w_cursor.lnum = lnum;
+ rettv->vval.v_number = get_lisp_indent();
+ curwin->w_cursor = pos;
+ } else {
+ rettv->vval.v_number = -1;
+ }
+}
+
+// "list2str()" function
+static void f_list2str(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ garray_T ga;
+
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = NULL;
+ if (argvars[0].v_type != VAR_LIST) {
+ EMSG(_(e_invarg));
+ return;
+ }
+
+ list_T *const l = argvars[0].vval.v_list;
+ if (l == NULL) {
+ return; // empty list results in empty string
+ }
+
+ ga_init(&ga, 1, 80);
+ char_u buf[MB_MAXBYTES + 1];
+
+ TV_LIST_ITER_CONST(l, li, {
+ buf[utf_char2bytes(tv_get_number(TV_LIST_ITEM_TV(li)), buf)] = NUL;
+ ga_concat(&ga, buf);
+ });
+ ga_append(&ga, NUL);
+
+ rettv->vval.v_string = ga.ga_data;
+}
+
+/*
+ * "localtime()" function
+ */
+static void f_localtime(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = (varnumber_T)time(NULL);
+}
+
+
+static void get_maparg(typval_T *argvars, typval_T *rettv, int exact)
+{
+ char_u *keys_buf = NULL;
+ char_u *rhs;
+ int mode;
+ int abbr = FALSE;
+ int get_dict = FALSE;
+ mapblock_T *mp;
+ int buffer_local;
+
+ // Return empty string for failure.
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = NULL;
+
+ char_u *keys = (char_u *)tv_get_string(&argvars[0]);
+ if (*keys == NUL) {
+ return;
+ }
+
+ char buf[NUMBUFLEN];
+ const char *which;
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ which = tv_get_string_buf_chk(&argvars[1], buf);
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ abbr = tv_get_number(&argvars[2]);
+ if (argvars[3].v_type != VAR_UNKNOWN) {
+ get_dict = tv_get_number(&argvars[3]);
+ }
+ }
+ } else {
+ which = "";
+ }
+ if (which == NULL) {
+ return;
+ }
+
+ mode = get_map_mode((char_u **)&which, 0);
+
+ keys = replace_termcodes(keys, STRLEN(keys), &keys_buf, true, true, true,
+ CPO_TO_CPO_FLAGS);
+ rhs = check_map(keys, mode, exact, false, abbr, &mp, &buffer_local);
+ xfree(keys_buf);
+
+ if (!get_dict) {
+ // Return a string.
+ if (rhs != NULL) {
+ if (*rhs == NUL) {
+ rettv->vval.v_string = vim_strsave((char_u *)"<Nop>");
+ } else {
+ rettv->vval.v_string = (char_u *)str2special_save(
+ (char *)rhs, false, false);
+ }
+ }
+
+ } else {
+ tv_dict_alloc_ret(rettv);
+ if (rhs != NULL) {
+ // Return a dictionary.
+ mapblock_fill_dict(rettv->vval.v_dict, mp, buffer_local, true);
+ }
+ }
+}
+
+/// luaeval() function implementation
+static void f_luaeval(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+ FUNC_ATTR_NONNULL_ALL
+{
+ const char *const str = (const char *)tv_get_string_chk(&argvars[0]);
+ if (str == NULL) {
+ return;
+ }
+
+ executor_eval_lua(cstr_as_string((char *)str), &argvars[1], rettv);
+}
+
+/*
+ * "map()" function
+ */
+static void f_map(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ filter_map(argvars, rettv, TRUE);
+}
+
+/*
+ * "maparg()" function
+ */
+static void f_maparg(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ get_maparg(argvars, rettv, TRUE);
+}
+
+/*
+ * "mapcheck()" function
+ */
+static void f_mapcheck(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ get_maparg(argvars, rettv, FALSE);
+}
+
+
+static void find_some_match(typval_T *const argvars, typval_T *const rettv,
+ const SomeMatchType type)
+{
+ char_u *str = NULL;
+ long len = 0;
+ char_u *expr = NULL;
+ regmatch_T regmatch;
+ char_u *save_cpo;
+ long start = 0;
+ long nth = 1;
+ colnr_T startcol = 0;
+ bool match = false;
+ list_T *l = NULL;
+ listitem_T *li = NULL;
+ long idx = 0;
+ char_u *tofree = NULL;
+
+ // Make 'cpoptions' empty, the 'l' flag should not be used here.
+ save_cpo = p_cpo;
+ p_cpo = (char_u *)"";
+
+ rettv->vval.v_number = -1;
+ switch (type) {
+ // matchlist(): return empty list when there are no matches.
+ case kSomeMatchList: {
+ tv_list_alloc_ret(rettv, kListLenMayKnow);
+ break;
+ }
+ // matchstrpos(): return ["", -1, -1, -1]
+ case kSomeMatchStrPos: {
+ tv_list_alloc_ret(rettv, 4);
+ tv_list_append_string(rettv->vval.v_list, "", 0);
+ tv_list_append_number(rettv->vval.v_list, -1);
+ tv_list_append_number(rettv->vval.v_list, -1);
+ tv_list_append_number(rettv->vval.v_list, -1);
+ break;
+ }
+ case kSomeMatchStr: {
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = NULL;
+ break;
+ }
+ case kSomeMatch:
+ case kSomeMatchEnd: {
+ // Do nothing: zero is default.
+ break;
+ }
+ }
+
+ if (argvars[0].v_type == VAR_LIST) {
+ if ((l = argvars[0].vval.v_list) == NULL) {
+ goto theend;
+ }
+ li = tv_list_first(l);
+ } else {
+ expr = str = (char_u *)tv_get_string(&argvars[0]);
+ len = (long)STRLEN(str);
+ }
+
+ char patbuf[NUMBUFLEN];
+ const char *const pat = tv_get_string_buf_chk(&argvars[1], patbuf);
+ if (pat == NULL) {
+ goto theend;
+ }
+
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ bool error = false;
+
+ start = tv_get_number_chk(&argvars[2], &error);
+ if (error) {
+ goto theend;
+ }
+ if (l != NULL) {
+ idx = tv_list_uidx(l, start);
+ if (idx == -1) {
+ goto theend;
+ }
+ li = tv_list_find(l, idx);
+ } else {
+ if (start < 0)
+ start = 0;
+ if (start > len)
+ goto theend;
+ // When "count" argument is there ignore matches before "start",
+ // otherwise skip part of the string. Differs when pattern is "^"
+ // or "\<".
+ if (argvars[3].v_type != VAR_UNKNOWN) {
+ startcol = start;
+ } else {
+ str += start;
+ len -= start;
+ }
+ }
+
+ if (argvars[3].v_type != VAR_UNKNOWN) {
+ nth = tv_get_number_chk(&argvars[3], &error);
+ }
+ if (error) {
+ goto theend;
+ }
+ }
+
+ regmatch.regprog = vim_regcomp((char_u *)pat, RE_MAGIC + RE_STRING);
+ if (regmatch.regprog != NULL) {
+ regmatch.rm_ic = p_ic;
+
+ for (;; ) {
+ if (l != NULL) {
+ if (li == NULL) {
+ match = false;
+ break;
+ }
+ xfree(tofree);
+ tofree = expr = str = (char_u *)encode_tv2echo(TV_LIST_ITEM_TV(li),
+ NULL);
+ if (str == NULL) {
+ break;
+ }
+ }
+
+ match = vim_regexec_nl(&regmatch, str, (colnr_T)startcol);
+
+ if (match && --nth <= 0)
+ break;
+ if (l == NULL && !match)
+ break;
+
+ // Advance to just after the match.
+ if (l != NULL) {
+ li = TV_LIST_ITEM_NEXT(l, li);
+ idx++;
+ } else {
+ startcol = (colnr_T)(regmatch.startp[0]
+ + (*mb_ptr2len)(regmatch.startp[0]) - str);
+ if (startcol > (colnr_T)len || str + startcol <= regmatch.startp[0]) {
+ match = false;
+ break;
+ }
+ }
+ }
+
+ if (match) {
+ switch (type) {
+ case kSomeMatchStrPos: {
+ list_T *const ret_l = rettv->vval.v_list;
+ listitem_T *li1 = tv_list_first(ret_l);
+ listitem_T *li2 = TV_LIST_ITEM_NEXT(ret_l, li1);
+ listitem_T *li3 = TV_LIST_ITEM_NEXT(ret_l, li2);
+ listitem_T *li4 = TV_LIST_ITEM_NEXT(ret_l, li3);
+ xfree(TV_LIST_ITEM_TV(li1)->vval.v_string);
+
+ const size_t rd = (size_t)(regmatch.endp[0] - regmatch.startp[0]);
+ TV_LIST_ITEM_TV(li1)->vval.v_string = xmemdupz(
+ (const char *)regmatch.startp[0], rd);
+ TV_LIST_ITEM_TV(li3)->vval.v_number = (varnumber_T)(
+ regmatch.startp[0] - expr);
+ TV_LIST_ITEM_TV(li4)->vval.v_number = (varnumber_T)(
+ regmatch.endp[0] - expr);
+ if (l != NULL) {
+ TV_LIST_ITEM_TV(li2)->vval.v_number = (varnumber_T)idx;
+ }
+ break;
+ }
+ case kSomeMatchList: {
+ // Return list with matched string and submatches.
+ for (int i = 0; i < NSUBEXP; i++) {
+ if (regmatch.endp[i] == NULL) {
+ tv_list_append_string(rettv->vval.v_list, NULL, 0);
+ } else {
+ tv_list_append_string(rettv->vval.v_list,
+ (const char *)regmatch.startp[i],
+ (regmatch.endp[i] - regmatch.startp[i]));
+ }
+ }
+ break;
+ }
+ case kSomeMatchStr: {
+ // Return matched string.
+ if (l != NULL) {
+ tv_copy(TV_LIST_ITEM_TV(li), rettv);
+ } else {
+ rettv->vval.v_string = (char_u *)xmemdupz(
+ (const char *)regmatch.startp[0],
+ (size_t)(regmatch.endp[0] - regmatch.startp[0]));
+ }
+ break;
+ }
+ case kSomeMatch:
+ case kSomeMatchEnd: {
+ if (l != NULL) {
+ rettv->vval.v_number = idx;
+ } else {
+ if (type == kSomeMatch) {
+ rettv->vval.v_number =
+ (varnumber_T)(regmatch.startp[0] - str);
+ } else {
+ rettv->vval.v_number =
+ (varnumber_T)(regmatch.endp[0] - str);
+ }
+ rettv->vval.v_number += (varnumber_T)(str - expr);
+ }
+ break;
+ }
+ }
+ }
+ vim_regfree(regmatch.regprog);
+ }
+
+theend:
+ if (type == kSomeMatchStrPos && l == NULL && rettv->vval.v_list != NULL) {
+ // matchstrpos() without a list: drop the second item
+ list_T *const ret_l = rettv->vval.v_list;
+ tv_list_item_remove(ret_l, TV_LIST_ITEM_NEXT(ret_l, tv_list_first(ret_l)));
+ }
+
+ xfree(tofree);
+ p_cpo = save_cpo;
+}
+
+/*
+ * "match()" function
+ */
+static void f_match(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ find_some_match(argvars, rettv, kSomeMatch);
+}
+
+/*
+ * "matchadd()" function
+ */
+static void f_matchadd(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ char grpbuf[NUMBUFLEN];
+ char patbuf[NUMBUFLEN];
+ // group
+ const char *const grp = tv_get_string_buf_chk(&argvars[0], grpbuf);
+ // pattern
+ const char *const pat = tv_get_string_buf_chk(&argvars[1], patbuf);
+ // default priority
+ int prio = 10;
+ int id = -1;
+ bool error = false;
+ const char *conceal_char = NULL;
+ win_T *win = curwin;
+
+ rettv->vval.v_number = -1;
+
+ if (grp == NULL || pat == NULL) {
+ return;
+ }
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ prio = tv_get_number_chk(&argvars[2], &error);
+ if (argvars[3].v_type != VAR_UNKNOWN) {
+ id = tv_get_number_chk(&argvars[3], &error);
+ if (argvars[4].v_type != VAR_UNKNOWN
+ && matchadd_dict_arg(&argvars[4], &conceal_char, &win) == FAIL) {
+ return;
+ }
+ }
+ }
+ if (error) {
+ return;
+ }
+ if (id >= 1 && id <= 3) {
+ EMSGN(_("E798: ID is reserved for \":match\": %" PRId64), id);
+ return;
+ }
+
+ rettv->vval.v_number = match_add(win, grp, pat, prio, id, NULL, conceal_char);
+}
+
+static void f_matchaddpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = -1;
+
+ char buf[NUMBUFLEN];
+ const char *const group = tv_get_string_buf_chk(&argvars[0], buf);
+ if (group == NULL) {
+ return;
+ }
+
+ if (argvars[1].v_type != VAR_LIST) {
+ EMSG2(_(e_listarg), "matchaddpos()");
+ return;
+ }
+
+ list_T *l;
+ l = argvars[1].vval.v_list;
+ if (l == NULL) {
+ return;
+ }
+
+ bool error = false;
+ int prio = 10;
+ int id = -1;
+ const char *conceal_char = NULL;
+ win_T *win = curwin;
+
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ prio = tv_get_number_chk(&argvars[2], &error);
+ if (argvars[3].v_type != VAR_UNKNOWN) {
+ id = tv_get_number_chk(&argvars[3], &error);
+ if (argvars[4].v_type != VAR_UNKNOWN
+ && matchadd_dict_arg(&argvars[4], &conceal_char, &win) == FAIL) {
+ return;
+ }
+ }
+ }
+ if (error == true) {
+ return;
+ }
+
+ // id == 3 is ok because matchaddpos() is supposed to substitute :3match
+ if (id == 1 || id == 2) {
+ EMSGN(_("E798: ID is reserved for \"match\": %" PRId64), id);
+ return;
+ }
+
+ rettv->vval.v_number = match_add(win, group, NULL, prio, id, l, conceal_char);
+}
+
+/*
+ * "matcharg()" function
+ */
+static void f_matcharg(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const int id = tv_get_number(&argvars[0]);
+
+ tv_list_alloc_ret(rettv, (id >= 1 && id <= 3
+ ? 2
+ : 0));
+
+ if (id >= 1 && id <= 3) {
+ matchitem_T *const m = (matchitem_T *)get_match(curwin, id);
+
+ if (m != NULL) {
+ tv_list_append_string(rettv->vval.v_list,
+ (const char *)syn_id2name(m->hlg_id), -1);
+ tv_list_append_string(rettv->vval.v_list, (const char *)m->pattern, -1);
+ } else {
+ tv_list_append_string(rettv->vval.v_list, NULL, 0);
+ tv_list_append_string(rettv->vval.v_list, NULL, 0);
+ }
+ }
+}
+
+/*
+ * "matchdelete()" function
+ */
+static void f_matchdelete(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ win_T *win = get_optional_window(argvars, 1);
+ if (win == NULL) {
+ rettv->vval.v_number = -1;
+ } else {
+ rettv->vval.v_number = match_delete(win,
+ (int)tv_get_number(&argvars[0]), true);
+ }
+}
+
+/*
+ * "matchend()" function
+ */
+static void f_matchend(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ find_some_match(argvars, rettv, kSomeMatchEnd);
+}
+
+/*
+ * "matchlist()" function
+ */
+static void f_matchlist(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ find_some_match(argvars, rettv, kSomeMatchList);
+}
+
+/*
+ * "matchstr()" function
+ */
+static void f_matchstr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ find_some_match(argvars, rettv, kSomeMatchStr);
+}
+
+/// "matchstrpos()" function
+static void f_matchstrpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ find_some_match(argvars, rettv, kSomeMatchStrPos);
+}
+
+/// Get maximal/minimal number value in a list or dictionary
+///
+/// @param[in] tv List or dictionary to work with. If it contains something
+/// that is not an integer number (or cannot be coerced to
+/// it) error is given.
+/// @param[out] rettv Location where result will be saved. Only assigns
+/// vval.v_number, type is not touched. Returns zero for
+/// empty lists/dictionaries.
+/// @param[in] domax Determines whether maximal or minimal value is desired.
+static void max_min(const typval_T *const tv, typval_T *const rettv,
+ const bool domax)
+ FUNC_ATTR_NONNULL_ALL
+{
+ bool error = false;
+
+ rettv->vval.v_number = 0;
+ varnumber_T n = (domax ? VARNUMBER_MIN : VARNUMBER_MAX);
+ if (tv->v_type == VAR_LIST) {
+ if (tv_list_len(tv->vval.v_list) == 0) {
+ return;
+ }
+ TV_LIST_ITER_CONST(tv->vval.v_list, li, {
+ const varnumber_T i = tv_get_number_chk(TV_LIST_ITEM_TV(li), &error);
+ if (error) {
+ return;
+ }
+ if (domax ? i > n : i < n) {
+ n = i;
+ }
+ });
+ } else if (tv->v_type == VAR_DICT) {
+ if (tv_dict_len(tv->vval.v_dict) == 0) {
+ return;
+ }
+ TV_DICT_ITER(tv->vval.v_dict, di, {
+ const varnumber_T i = tv_get_number_chk(&di->di_tv, &error);
+ if (error) {
+ return;
+ }
+ if (domax ? i > n : i < n) {
+ n = i;
+ }
+ });
+ } else {
+ EMSG2(_(e_listdictarg), domax ? "max()" : "min()");
+ return;
+ }
+ rettv->vval.v_number = n;
+}
+
+/*
+ * "max()" function
+ */
+static void f_max(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ max_min(argvars, rettv, TRUE);
+}
+
+/*
+ * "min()" function
+ */
+static void f_min(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ max_min(argvars, rettv, FALSE);
+}
+
+/*
+ * "mkdir()" function
+ */
+static void f_mkdir(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ int prot = 0755; // -V536
+
+ rettv->vval.v_number = FAIL;
+ if (check_restricted() || check_secure())
+ return;
+
+ char buf[NUMBUFLEN];
+ const char *const dir = tv_get_string_buf(&argvars[0], buf);
+ if (*dir == NUL) {
+ return;
+ }
+
+ if (*path_tail((char_u *)dir) == NUL) {
+ // Remove trailing slashes.
+ *path_tail_with_sep((char_u *)dir) = NUL;
+ }
+
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ prot = tv_get_number_chk(&argvars[2], NULL);
+ if (prot == -1) {
+ return;
+ }
+ }
+ if (strcmp(tv_get_string(&argvars[1]), "p") == 0) {
+ char *failed_dir;
+ int ret = os_mkdir_recurse(dir, prot, &failed_dir);
+ if (ret != 0) {
+ EMSG3(_(e_mkdir), failed_dir, os_strerror(ret));
+ xfree(failed_dir);
+ rettv->vval.v_number = FAIL;
+ return;
+ } else {
+ rettv->vval.v_number = OK;
+ return;
+ }
+ }
+ }
+ rettv->vval.v_number = vim_mkdir_emsg(dir, prot);
+}
+
+/// "mode()" function
+static void f_mode(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ char *mode = get_mode();
+
+ // Clear out the minor mode when the argument is not a non-zero number or
+ // non-empty string.
+ if (!non_zero_arg(&argvars[0])) {
+ mode[1] = NUL;
+ }
+
+ rettv->vval.v_string = (char_u *)mode;
+ rettv->v_type = VAR_STRING;
+}
+
+/// "msgpackdump()" function
+static void f_msgpackdump(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (argvars[0].v_type != VAR_LIST) {
+ EMSG2(_(e_listarg), "msgpackdump()");
+ return;
+ }
+ list_T *const ret_list = tv_list_alloc_ret(rettv, kListLenMayKnow);
+ list_T *const list = argvars[0].vval.v_list;
+ msgpack_packer *lpacker = msgpack_packer_new(ret_list, &encode_list_write);
+ const char *const msg = _("msgpackdump() argument, index %i");
+ // Assume that translation will not take more then 4 times more space
+ char msgbuf[sizeof("msgpackdump() argument, index ") * 4 + NUMBUFLEN];
+ int idx = 0;
+ TV_LIST_ITER(list, li, {
+ vim_snprintf(msgbuf, sizeof(msgbuf), (char *)msg, idx);
+ idx++;
+ if (encode_vim_to_msgpack(lpacker, TV_LIST_ITEM_TV(li), msgbuf) == FAIL) {
+ break;
+ }
+ });
+ msgpack_packer_free(lpacker);
+}
+
+/// "msgpackparse" function
+static void f_msgpackparse(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (argvars[0].v_type != VAR_LIST) {
+ EMSG2(_(e_listarg), "msgpackparse()");
+ return;
+ }
+ list_T *const ret_list = tv_list_alloc_ret(rettv, kListLenMayKnow);
+ const list_T *const list = argvars[0].vval.v_list;
+ if (tv_list_len(list) == 0) {
+ return;
+ }
+ if (TV_LIST_ITEM_TV(tv_list_first(list))->v_type != VAR_STRING) {
+ EMSG2(_(e_invarg2), "List item is not a string");
+ return;
+ }
+ ListReaderState lrstate = encode_init_lrstate(list);
+ msgpack_unpacker *const unpacker = msgpack_unpacker_new(IOSIZE);
+ if (unpacker == NULL) {
+ EMSG(_(e_outofmem));
+ return;
+ }
+ msgpack_unpacked unpacked;
+ msgpack_unpacked_init(&unpacked);
+ do {
+ if (!msgpack_unpacker_reserve_buffer(unpacker, IOSIZE)) {
+ EMSG(_(e_outofmem));
+ goto f_msgpackparse_exit;
+ }
+ size_t read_bytes;
+ const int rlret = encode_read_from_list(
+ &lrstate, msgpack_unpacker_buffer(unpacker), IOSIZE, &read_bytes);
+ if (rlret == FAIL) {
+ EMSG2(_(e_invarg2), "List item is not a string");
+ goto f_msgpackparse_exit;
+ }
+ msgpack_unpacker_buffer_consumed(unpacker, read_bytes);
+ if (read_bytes == 0) {
+ break;
+ }
+ while (unpacker->off < unpacker->used) {
+ const msgpack_unpack_return result = msgpack_unpacker_next(unpacker,
+ &unpacked);
+ if (result == MSGPACK_UNPACK_PARSE_ERROR) {
+ EMSG2(_(e_invarg2), "Failed to parse msgpack string");
+ goto f_msgpackparse_exit;
+ }
+ if (result == MSGPACK_UNPACK_NOMEM_ERROR) {
+ EMSG(_(e_outofmem));
+ goto f_msgpackparse_exit;
+ }
+ if (result == MSGPACK_UNPACK_SUCCESS) {
+ typval_T tv = { .v_type = VAR_UNKNOWN };
+ if (msgpack_to_vim(unpacked.data, &tv) == FAIL) {
+ EMSG2(_(e_invarg2), "Failed to convert msgpack string");
+ goto f_msgpackparse_exit;
+ }
+ tv_list_append_owned_tv(ret_list, tv);
+ }
+ if (result == MSGPACK_UNPACK_CONTINUE) {
+ if (rlret == OK) {
+ EMSG2(_(e_invarg2), "Incomplete msgpack string");
+ }
+ break;
+ }
+ }
+ if (rlret == OK) {
+ break;
+ }
+ } while (true);
+
+f_msgpackparse_exit:
+ msgpack_unpacked_destroy(&unpacked);
+ msgpack_unpacker_free(unpacker);
+ return;
+}
+
+/*
+ * "nextnonblank()" function
+ */
+static void f_nextnonblank(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ linenr_T lnum;
+
+ for (lnum = tv_get_lnum(argvars);; lnum++) {
+ if (lnum < 0 || lnum > curbuf->b_ml.ml_line_count) {
+ lnum = 0;
+ break;
+ }
+ if (*skipwhite(ml_get(lnum)) != NUL) {
+ break;
+ }
+ }
+ rettv->vval.v_number = lnum;
+}
+
+/*
+ * "nr2char()" function
+ */
+static void f_nr2char(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ if (!tv_check_num(&argvars[1])) {
+ return;
+ }
+ }
+
+ bool error = false;
+ const varnumber_T num = tv_get_number_chk(&argvars[0], &error);
+ if (error) {
+ return;
+ }
+ if (num < 0) {
+ EMSG(_("E5070: Character number must not be less than zero"));
+ return;
+ }
+ if (num > INT_MAX) {
+ emsgf(_("E5071: Character number must not be greater than INT_MAX (%i)"),
+ INT_MAX);
+ return;
+ }
+
+ char buf[MB_MAXBYTES];
+ const int len = utf_char2bytes((int)num, (char_u *)buf);
+
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = xmemdupz(buf, (size_t)len);
+}
+
+/*
+ * "or(expr, expr)" function
+ */
+static void f_or(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = tv_get_number_chk(&argvars[0], NULL)
+ | tv_get_number_chk(&argvars[1], NULL);
+}
+
+/*
+ * "pathshorten()" function
+ */
+static void f_pathshorten(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->v_type = VAR_STRING;
+ const char *const s = tv_get_string_chk(&argvars[0]);
+ if (!s) {
+ return;
+ }
+ rettv->vval.v_string = shorten_dir((char_u *)xstrdup(s));
+}
+
+/*
+ * "pow()" function
+ */
+static void f_pow(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ float_T fx;
+ float_T fy;
+
+ rettv->v_type = VAR_FLOAT;
+ if (tv_get_float_chk(argvars, &fx) && tv_get_float_chk(&argvars[1], &fy)) {
+ rettv->vval.v_float = pow(fx, fy);
+ } else {
+ rettv->vval.v_float = 0.0;
+ }
+}
+
+/*
+ * "prevnonblank()" function
+ */
+static void f_prevnonblank(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ linenr_T lnum = tv_get_lnum(argvars);
+ if (lnum < 1 || lnum > curbuf->b_ml.ml_line_count) {
+ lnum = 0;
+ } else {
+ while (lnum >= 1 && *skipwhite(ml_get(lnum)) == NUL) {
+ lnum--;
+ }
+ }
+ rettv->vval.v_number = lnum;
+}
+
+/*
+ * "printf()" function
+ */
+static void f_printf(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = NULL;
+ {
+ int len;
+ int saved_did_emsg = did_emsg;
+
+ // Get the required length, allocate the buffer and do it for real.
+ did_emsg = false;
+ char buf[NUMBUFLEN];
+ const char *fmt = tv_get_string_buf(&argvars[0], buf);
+ len = vim_vsnprintf_typval(NULL, 0, fmt, dummy_ap, argvars + 1);
+ if (!did_emsg) {
+ char *s = xmalloc(len + 1);
+ rettv->vval.v_string = (char_u *)s;
+ (void)vim_vsnprintf_typval(s, len + 1, fmt, dummy_ap, argvars + 1);
+ }
+ did_emsg |= saved_did_emsg;
+ }
+}
+
+// "prompt_setcallback({buffer}, {callback})" function
+static void f_prompt_setcallback(typval_T *argvars,
+ typval_T *rettv, FunPtr fptr)
+{
+ buf_T *buf;
+ Callback prompt_callback = { .type = kCallbackNone };
+
+ if (check_secure()) {
+ return;
+ }
+ buf = tv_get_buf(&argvars[0], false);
+ if (buf == NULL) {
+ return;
+ }
+
+ if (argvars[1].v_type != VAR_STRING || *argvars[1].vval.v_string != NUL) {
+ if (!callback_from_typval(&prompt_callback, &argvars[1])) {
+ return;
+ }
+ }
+
+ callback_free(&buf->b_prompt_callback);
+ buf->b_prompt_callback = prompt_callback;
+}
+
+// "prompt_setinterrupt({buffer}, {callback})" function
+static void f_prompt_setinterrupt(typval_T *argvars,
+ typval_T *rettv, FunPtr fptr)
+{
+ buf_T *buf;
+ Callback interrupt_callback = { .type = kCallbackNone };
+
+ if (check_secure()) {
+ return;
+ }
+ buf = tv_get_buf(&argvars[0], false);
+ if (buf == NULL) {
+ return;
+ }
+
+ if (argvars[1].v_type != VAR_STRING || *argvars[1].vval.v_string != NUL) {
+ if (!callback_from_typval(&interrupt_callback, &argvars[1])) {
+ return;
+ }
+ }
+
+ callback_free(&buf->b_prompt_interrupt);
+ buf->b_prompt_interrupt= interrupt_callback;
+}
+
+// "prompt_setprompt({buffer}, {text})" function
+static void f_prompt_setprompt(typval_T *argvars,
+ typval_T *rettv, FunPtr fptr)
+{
+ buf_T *buf;
+ const char_u *text;
+
+ if (check_secure()) {
+ return;
+ }
+ buf = tv_get_buf(&argvars[0], false);
+ if (buf == NULL) {
+ return;
+ }
+
+ text = (const char_u *)tv_get_string(&argvars[1]);
+ xfree(buf->b_prompt_text);
+ buf->b_prompt_text = vim_strsave(text);
+}
+
+// "pum_getpos()" function
+static void f_pum_getpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ tv_dict_alloc_ret(rettv);
+ pum_set_event_info(rettv->vval.v_dict);
+}
+
+/*
+ * "pumvisible()" function
+ */
+static void f_pumvisible(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ if (pum_visible())
+ rettv->vval.v_number = 1;
+}
+
+/*
+ * "pyeval()" function
+ */
+static void f_pyeval(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ script_host_eval("python", argvars, rettv);
+}
+
+/*
+ * "py3eval()" function
+ */
+static void f_py3eval(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ script_host_eval("python3", argvars, rettv);
+}
+
+// "pyxeval()" function
+static void f_pyxeval(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ init_pyxversion();
+ if (p_pyx == 2) {
+ f_pyeval(argvars, rettv, NULL);
+ } else {
+ f_py3eval(argvars, rettv, NULL);
+ }
+}
+
+/*
+ * "range()" function
+ */
+static void f_range(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ varnumber_T start;
+ varnumber_T end;
+ varnumber_T stride = 1;
+ varnumber_T i;
+ bool error = false;
+
+ start = tv_get_number_chk(&argvars[0], &error);
+ if (argvars[1].v_type == VAR_UNKNOWN) {
+ end = start - 1;
+ start = 0;
+ } else {
+ end = tv_get_number_chk(&argvars[1], &error);
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ stride = tv_get_number_chk(&argvars[2], &error);
+ }
+ }
+
+ if (error) {
+ return; // Type error; errmsg already given.
+ }
+ if (stride == 0) {
+ EMSG(_("E726: Stride is zero"));
+ } else if (stride > 0 ? end + 1 < start : end - 1 > start) {
+ EMSG(_("E727: Start past end"));
+ } else {
+ tv_list_alloc_ret(rettv, (end - start) / stride);
+ for (i = start; stride > 0 ? i <= end : i >= end; i += stride) {
+ tv_list_append_number(rettv->vval.v_list, (varnumber_T)i);
+ }
+ }
+}
+
+// Evaluate "expr" for readdir().
+static varnumber_T readdir_checkitem(typval_T *expr, const char *name)
+{
+ typval_T save_val;
+ typval_T rettv;
+ typval_T argv[2];
+ varnumber_T retval = 0;
+ bool error = false;
+
+ prepare_vimvar(VV_VAL, &save_val);
+ set_vim_var_string(VV_VAL, name, -1);
+ argv[0].v_type = VAR_STRING;
+ argv[0].vval.v_string = (char_u *)name;
+
+ if (eval_expr_typval(expr, argv, 1, &rettv) == FAIL) {
+ goto theend;
+ }
+
+ retval = tv_get_number_chk(&rettv, &error);
+ if (error) {
+ retval = -1;
+ }
+
+ tv_clear(&rettv);
+
+theend:
+ set_vim_var_string(VV_VAL, NULL, 0);
+ restore_vimvar(VV_VAL, &save_val);
+ return retval;
+}
+
+// "readdir()" function
+static void f_readdir(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ typval_T *expr;
+ const char *path;
+ garray_T ga;
+ Directory dir;
+
+ tv_list_alloc_ret(rettv, kListLenUnknown);
+ path = tv_get_string(&argvars[0]);
+ expr = &argvars[1];
+ ga_init(&ga, (int)sizeof(char *), 20);
+
+ if (!os_scandir(&dir, path)) {
+ smsg(_(e_notopen), path);
+ } else {
+ for (;;) {
+ bool ignore;
+
+ path = os_scandir_next(&dir);
+ if (path == NULL) {
+ break;
+ }
+
+ ignore = (path[0] == '.'
+ && (path[1] == NUL || (path[1] == '.' && path[2] == NUL)));
+ if (!ignore && expr->v_type != VAR_UNKNOWN) {
+ varnumber_T r = readdir_checkitem(expr, path);
+
+ if (r < 0) {
+ break;
+ }
+ if (r == 0) {
+ ignore = true;
+ }
+ }
+
+ if (!ignore) {
+ ga_grow(&ga, 1);
+ ((char **)ga.ga_data)[ga.ga_len++] = xstrdup(path);
+ }
+ }
+
+ os_closedir(&dir);
+ }
+
+ if (rettv->vval.v_list != NULL && ga.ga_len > 0) {
+ sort_strings((char_u **)ga.ga_data, ga.ga_len);
+ for (int i = 0; i < ga.ga_len; i++) {
+ path = ((const char **)ga.ga_data)[i];
+ tv_list_append_string(rettv->vval.v_list, path, -1);
+ }
+ }
+ ga_clear_strings(&ga);
+}
+
+/*
+ * "readfile()" function
+ */
+static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ bool binary = false;
+ FILE *fd;
+ char_u buf[(IOSIZE/256) * 256]; // rounded to avoid odd + 1
+ int io_size = sizeof(buf);
+ int readlen; // size of last fread()
+ char_u *prev = NULL; // previously read bytes, if any
+ long prevlen = 0; // length of data in prev
+ long prevsize = 0; // size of prev buffer
+ long maxline = MAXLNUM;
+
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ if (strcmp(tv_get_string(&argvars[1]), "b") == 0) {
+ binary = true;
+ }
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ maxline = tv_get_number(&argvars[2]);
+ }
+ }
+
+ list_T *const l = tv_list_alloc_ret(rettv, kListLenUnknown);
+
+ // Always open the file in binary mode, library functions have a mind of
+ // their own about CR-LF conversion.
+ const char *const fname = tv_get_string(&argvars[0]);
+ if (*fname == NUL || (fd = os_fopen(fname, READBIN)) == NULL) {
+ EMSG2(_(e_notopen), *fname == NUL ? _("<empty>") : fname);
+ return;
+ }
+
+ while (maxline < 0 || tv_list_len(l) < maxline) {
+ readlen = (int)fread(buf, 1, io_size, fd);
+
+ // This for loop processes what was read, but is also entered at end
+ // of file so that either:
+ // - an incomplete line gets written
+ // - a "binary" file gets an empty line at the end if it ends in a
+ // newline.
+ char_u *p; // Position in buf.
+ char_u *start; // Start of current line.
+ for (p = buf, start = buf;
+ p < buf + readlen || (readlen <= 0 && (prevlen > 0 || binary));
+ p++) {
+ if (*p == '\n' || readlen <= 0) {
+ char_u *s = NULL;
+ size_t len = p - start;
+
+ // Finished a line. Remove CRs before NL.
+ if (readlen > 0 && !binary) {
+ while (len > 0 && start[len - 1] == '\r') {
+ len--;
+ }
+ // removal may cross back to the "prev" string
+ if (len == 0) {
+ while (prevlen > 0 && prev[prevlen - 1] == '\r') {
+ prevlen--;
+ }
+ }
+ }
+ if (prevlen == 0) {
+ assert(len < INT_MAX);
+ s = vim_strnsave(start, (int)len);
+ } else {
+ /* Change "prev" buffer to be the right size. This way
+ * the bytes are only copied once, and very long lines are
+ * allocated only once. */
+ s = xrealloc(prev, prevlen + len + 1);
+ memcpy(s + prevlen, start, len);
+ s[prevlen + len] = NUL;
+ prev = NULL; // the list will own the string
+ prevlen = prevsize = 0;
+ }
+
+ tv_list_append_owned_tv(l, (typval_T) {
+ .v_type = VAR_STRING,
+ .v_lock = VAR_UNLOCKED,
+ .vval.v_string = s,
+ });
+
+ start = p + 1; // Step over newline.
+ if (maxline < 0) {
+ if (tv_list_len(l) > -maxline) {
+ assert(tv_list_len(l) == 1 + (-maxline));
+ tv_list_item_remove(l, tv_list_first(l));
+ }
+ } else if (tv_list_len(l) >= maxline) {
+ assert(tv_list_len(l) == maxline);
+ break;
+ }
+ if (readlen <= 0) {
+ break;
+ }
+ } else if (*p == NUL) {
+ *p = '\n';
+ // Check for utf8 "bom"; U+FEFF is encoded as EF BB BF. Do this
+ // when finding the BF and check the previous two bytes.
+ } else if (*p == 0xbf && !binary) {
+ // Find the two bytes before the 0xbf. If p is at buf, or buf + 1,
+ // these may be in the "prev" string.
+ char_u back1 = p >= buf + 1 ? p[-1]
+ : prevlen >= 1 ? prev[prevlen - 1] : NUL;
+ char_u back2 = p >= buf + 2 ? p[-2]
+ : p == buf + 1 && prevlen >= 1 ? prev[prevlen - 1]
+ : prevlen >= 2 ? prev[prevlen - 2] : NUL;
+
+ if (back2 == 0xef && back1 == 0xbb) {
+ char_u *dest = p - 2;
+
+ // Usually a BOM is at the beginning of a file, and so at
+ // the beginning of a line; then we can just step over it.
+ if (start == dest) {
+ start = p + 1;
+ } else {
+ // have to shuffle buf to close gap
+ int adjust_prevlen = 0;
+
+ if (dest < buf) { // -V782
+ adjust_prevlen = (int)(buf - dest); // -V782
+ // adjust_prevlen must be 1 or 2.
+ dest = buf;
+ }
+ if (readlen > p - buf + 1)
+ memmove(dest, p + 1, readlen - (p - buf) - 1);
+ readlen -= 3 - adjust_prevlen;
+ prevlen -= adjust_prevlen;
+ p = dest - 1;
+ }
+ }
+ }
+ } // for
+
+ if ((maxline >= 0 && tv_list_len(l) >= maxline) || readlen <= 0) {
+ break;
+ }
+ if (start < p) {
+ // There's part of a line in buf, store it in "prev".
+ if (p - start + prevlen >= prevsize) {
+ /* A common use case is ordinary text files and "prev" gets a
+ * fragment of a line, so the first allocation is made
+ * small, to avoid repeatedly 'allocing' large and
+ * 'reallocing' small. */
+ if (prevsize == 0)
+ prevsize = (long)(p - start);
+ else {
+ long grow50pc = (prevsize * 3) / 2;
+ long growmin = (long)((p - start) * 2 + prevlen);
+ prevsize = grow50pc > growmin ? grow50pc : growmin;
+ }
+ prev = xrealloc(prev, prevsize);
+ }
+ // Add the line part to end of "prev".
+ memmove(prev + prevlen, start, p - start);
+ prevlen += (long)(p - start);
+ }
+ } // while
+
+ xfree(prev);
+ fclose(fd);
+}
+
+// "reg_executing()" function
+static void f_reg_executing(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ return_register(reg_executing, rettv);
+}
+
+// "reg_recording()" function
+static void f_reg_recording(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ return_register(reg_recording, rettv);
+}
+
+/// list2proftime - convert a List to proftime_T
+///
+/// @param arg The input list, must be of type VAR_LIST and have
+/// exactly 2 items
+/// @param[out] tm The proftime_T representation of `arg`
+/// @return OK In case of success, FAIL in case of error
+static int list2proftime(typval_T *arg, proftime_T *tm) FUNC_ATTR_NONNULL_ALL
+{
+ if (arg->v_type != VAR_LIST || tv_list_len(arg->vval.v_list) != 2) {
+ return FAIL;
+ }
+
+ bool error = false;
+ varnumber_T n1 = tv_list_find_nr(arg->vval.v_list, 0L, &error);
+ varnumber_T n2 = tv_list_find_nr(arg->vval.v_list, 1L, &error);
+ if (error) {
+ return FAIL;
+ }
+
+ // in f_reltime() we split up the 64-bit proftime_T into two 32-bit
+ // values, now we combine them again.
+ union {
+ struct { int32_t low, high; } split;
+ proftime_T prof;
+ } u = { .split.high = n1, .split.low = n2 };
+
+ *tm = u.prof;
+
+ return OK;
+}
+
+/// f_reltime - return an item that represents a time value
+///
+/// @param[out] rettv Without an argument it returns the current time. With
+/// one argument it returns the time passed since the argument.
+/// With two arguments it returns the time passed between
+/// the two arguments.
+static void f_reltime(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ proftime_T res;
+ proftime_T start;
+
+ if (argvars[0].v_type == VAR_UNKNOWN) {
+ // no arguments: get current time.
+ res = profile_start();
+ } else if (argvars[1].v_type == VAR_UNKNOWN) {
+ if (list2proftime(&argvars[0], &res) == FAIL) {
+ return;
+ }
+ res = profile_end(res);
+ } else {
+ // two arguments: compute the difference.
+ if (list2proftime(&argvars[0], &start) == FAIL
+ || list2proftime(&argvars[1], &res) == FAIL) {
+ return;
+ }
+ res = profile_sub(res, start);
+ }
+
+ // we have to store the 64-bit proftime_T inside of a list of int's
+ // (varnumber_T is defined as int). For all our supported platforms, int's
+ // are at least 32-bits wide. So we'll use two 32-bit values to store it.
+ union {
+ struct { int32_t low, high; } split;
+ proftime_T prof;
+ } u = { .prof = res };
+
+ // statically assert that the union type conv will provide the correct
+ // results, if varnumber_T or proftime_T change, the union cast will need
+ // to be revised.
+ STATIC_ASSERT(sizeof(u.prof) == sizeof(u) && sizeof(u.split) == sizeof(u),
+ "type punning will produce incorrect results on this platform");
+
+ tv_list_alloc_ret(rettv, 2);
+ tv_list_append_number(rettv->vval.v_list, u.split.high);
+ tv_list_append_number(rettv->vval.v_list, u.split.low);
+}
+
+/// "reltimestr()" function
+static void f_reltimestr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+ FUNC_ATTR_NONNULL_ALL
+{
+ proftime_T tm;
+
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = NULL;
+ if (list2proftime(&argvars[0], &tm) == OK) {
+ rettv->vval.v_string = (char_u *)xstrdup(profile_msg(tm));
+ }
+}
+
+/*
+ * "remove()" function
+ */
+static void f_remove(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ list_T *l;
+ listitem_T *item, *item2;
+ listitem_T *li;
+ long idx;
+ long end;
+ dict_T *d;
+ dictitem_T *di;
+ const char *const arg_errmsg = N_("remove() argument");
+
+ if (argvars[0].v_type == VAR_DICT) {
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ EMSG2(_(e_toomanyarg), "remove()");
+ } else if ((d = argvars[0].vval.v_dict) != NULL
+ && !tv_check_lock(d->dv_lock, arg_errmsg, TV_TRANSLATE)) {
+ const char *key = tv_get_string_chk(&argvars[1]);
+ if (key != NULL) {
+ di = tv_dict_find(d, key, -1);
+ if (di == NULL) {
+ EMSG2(_(e_dictkey), key);
+ } else if (!var_check_fixed(di->di_flags, arg_errmsg, TV_TRANSLATE)
+ && !var_check_ro(di->di_flags, arg_errmsg, TV_TRANSLATE)) {
+ *rettv = di->di_tv;
+ di->di_tv = TV_INITIAL_VALUE;
+ tv_dict_item_remove(d, di);
+ if (tv_dict_is_watched(d)) {
+ tv_dict_watcher_notify(d, key, NULL, rettv);
+ }
+ }
+ }
+ }
+ } else if (argvars[0].v_type != VAR_LIST) {
+ EMSG2(_(e_listdictarg), "remove()");
+ } else if (!tv_check_lock(tv_list_locked((l = argvars[0].vval.v_list)),
+ arg_errmsg, TV_TRANSLATE)) {
+ bool error = false;
+
+ idx = tv_get_number_chk(&argvars[1], &error);
+ if (error) {
+ // Type error: do nothing, errmsg already given.
+ } else if ((item = tv_list_find(l, idx)) == NULL) {
+ EMSGN(_(e_listidx), idx);
+ } else {
+ if (argvars[2].v_type == VAR_UNKNOWN) {
+ // Remove one item, return its value.
+ tv_list_drop_items(l, item, item);
+ *rettv = *TV_LIST_ITEM_TV(item);
+ xfree(item);
+ } else {
+ // Remove range of items, return list with values.
+ end = tv_get_number_chk(&argvars[2], &error);
+ if (error) {
+ // Type error: do nothing.
+ } else if ((item2 = tv_list_find(l, end)) == NULL) {
+ EMSGN(_(e_listidx), end);
+ } else {
+ int cnt = 0;
+
+ for (li = item; li != NULL; li = TV_LIST_ITEM_NEXT(l, li)) {
+ cnt++;
+ if (li == item2) {
+ break;
+ }
+ }
+ if (li == NULL) { // Didn't find "item2" after "item".
+ EMSG(_(e_invrange));
+ } else {
+ tv_list_move_items(l, item, item2, tv_list_alloc_ret(rettv, cnt),
+ cnt);
+ }
+ }
+ }
+ }
+ }
+}
+
+/*
+ * "rename({from}, {to})" function
+ */
+static void f_rename(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ if (check_restricted() || check_secure()) {
+ rettv->vval.v_number = -1;
+ } else {
+ char buf[NUMBUFLEN];
+ rettv->vval.v_number = vim_rename(
+ (const char_u *)tv_get_string(&argvars[0]),
+ (const char_u *)tv_get_string_buf(&argvars[1], buf));
+ }
+}
+
+/*
+ * "repeat()" function
+ */
+static void f_repeat(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ varnumber_T n = tv_get_number(&argvars[1]);
+ if (argvars[0].v_type == VAR_LIST) {
+ tv_list_alloc_ret(rettv, (n > 0) * n * tv_list_len(argvars[0].vval.v_list));
+ while (n-- > 0) {
+ tv_list_extend(rettv->vval.v_list, argvars[0].vval.v_list, NULL);
+ }
+ } else {
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = NULL;
+ if (n <= 0) {
+ return;
+ }
+
+ const char *const p = tv_get_string(&argvars[0]);
+
+ const size_t slen = strlen(p);
+ if (slen == 0) {
+ return;
+ }
+ const size_t len = slen * n;
+ // Detect overflow.
+ if (len / n != slen) {
+ return;
+ }
+
+ char *const r = xmallocz(len);
+ for (varnumber_T i = 0; i < n; i++) {
+ memmove(r + i * slen, p, slen);
+ }
+
+ rettv->vval.v_string = (char_u *)r;
+ }
+}
+
+/*
+ * "resolve()" function
+ */
+static void f_resolve(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->v_type = VAR_STRING;
+ const char *fname = tv_get_string(&argvars[0]);
+#ifdef WIN32
+ char *v = os_resolve_shortcut(fname);
+ if (v == NULL) {
+ if (os_is_reparse_point_include(fname)) {
+ v = os_realpath(fname, v);
+ }
+ }
+ rettv->vval.v_string = (char_u *)(v == NULL ? xstrdup(fname) : v);
+#else
+# ifdef HAVE_READLINK
+ {
+ bool is_relative_to_current = false;
+ bool has_trailing_pathsep = false;
+ int limit = 100;
+
+ char *p = xstrdup(fname);
+
+ if (p[0] == '.' && (vim_ispathsep(p[1])
+ || (p[1] == '.' && (vim_ispathsep(p[2]))))) {
+ is_relative_to_current = true;
+ }
+
+ ptrdiff_t len = (ptrdiff_t)strlen(p);
+ if (len > 0 && after_pathsep(p, p + len)) {
+ has_trailing_pathsep = true;
+ p[len - 1] = NUL; // The trailing slash breaks readlink().
+ }
+
+ char *q = (char *)path_next_component(p);
+ char *remain = NULL;
+ if (*q != NUL) {
+ // Separate the first path component in "p", and keep the
+ // remainder (beginning with the path separator).
+ remain = xstrdup(q - 1);
+ q[-1] = NUL;
+ }
+
+ char *const buf = xmallocz(MAXPATHL);
+
+ char *cpy;
+ for (;; ) {
+ for (;; ) {
+ len = readlink(p, buf, MAXPATHL);
+ if (len <= 0) {
+ break;
+ }
+ buf[len] = NUL;
+
+ if (limit-- == 0) {
+ xfree(p);
+ xfree(remain);
+ EMSG(_("E655: Too many symbolic links (cycle?)"));
+ rettv->vval.v_string = NULL;
+ xfree(buf);
+ return;
+ }
+
+ // Ensure that the result will have a trailing path separator
+ // if the argument has one. */
+ if (remain == NULL && has_trailing_pathsep) {
+ add_pathsep(buf);
+ }
+
+ // Separate the first path component in the link value and
+ // concatenate the remainders. */
+ q = (char *)path_next_component(vim_ispathsep(*buf) ? buf + 1 : buf);
+ if (*q != NUL) {
+ cpy = remain;
+ remain = (remain
+ ? (char *)concat_str((char_u *)q - 1, (char_u *)remain)
+ : xstrdup(q - 1));
+ xfree(cpy);
+ q[-1] = NUL;
+ }
+
+ q = (char *)path_tail((char_u *)p);
+ if (q > p && *q == NUL) {
+ // Ignore trailing path separator.
+ q[-1] = NUL;
+ q = (char *)path_tail((char_u *)p);
+ }
+ if (q > p && !path_is_absolute((const char_u *)buf)) {
+ // Symlink is relative to directory of argument. Replace the
+ // symlink with the resolved name in the same directory.
+ const size_t p_len = strlen(p);
+ const size_t buf_len = strlen(buf);
+ p = xrealloc(p, p_len + buf_len + 1);
+ memcpy(path_tail((char_u *)p), buf, buf_len + 1);
+ } else {
+ xfree(p);
+ p = xstrdup(buf);
+ }
+ }
+
+ if (remain == NULL) {
+ break;
+ }
+
+ // Append the first path component of "remain" to "p".
+ q = (char *)path_next_component(remain + 1);
+ len = q - remain - (*q != NUL);
+ const size_t p_len = strlen(p);
+ cpy = xmallocz(p_len + len);
+ memcpy(cpy, p, p_len + 1);
+ xstrlcat(cpy + p_len, remain, len + 1);
+ xfree(p);
+ p = cpy;
+
+ // Shorten "remain".
+ if (*q != NUL) {
+ STRMOVE(remain, q - 1);
+ } else {
+ XFREE_CLEAR(remain);
+ }
+ }
+
+ // If the result is a relative path name, make it explicitly relative to
+ // the current directory if and only if the argument had this form.
+ if (!vim_ispathsep(*p)) {
+ if (is_relative_to_current
+ && *p != NUL
+ && !(p[0] == '.'
+ && (p[1] == NUL
+ || vim_ispathsep(p[1])
+ || (p[1] == '.'
+ && (p[2] == NUL
+ || vim_ispathsep(p[2])))))) {
+ // Prepend "./".
+ cpy = (char *)concat_str((const char_u *)"./", (const char_u *)p);
+ xfree(p);
+ p = cpy;
+ } else if (!is_relative_to_current) {
+ // Strip leading "./".
+ q = p;
+ while (q[0] == '.' && vim_ispathsep(q[1])) {
+ q += 2;
+ }
+ if (q > p) {
+ STRMOVE(p, p + 2);
+ }
+ }
+ }
+
+ // Ensure that the result will have no trailing path separator
+ // if the argument had none. But keep "/" or "//".
+ if (!has_trailing_pathsep) {
+ q = p + strlen(p);
+ if (after_pathsep(p, q)) {
+ *path_tail_with_sep((char_u *)p) = NUL;
+ }
+ }
+
+ rettv->vval.v_string = (char_u *)p;
+ xfree(buf);
+ }
+# else
+ rettv->vval.v_string = (char_u *)xstrdup(p);
+# endif
+#endif
+
+ simplify_filename(rettv->vval.v_string);
+}
+
+/*
+ * "reverse({list})" function
+ */
+static void f_reverse(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ list_T *l;
+ if (argvars[0].v_type != VAR_LIST) {
+ EMSG2(_(e_listarg), "reverse()");
+ } else if (!tv_check_lock(tv_list_locked((l = argvars[0].vval.v_list)),
+ N_("reverse() argument"), TV_TRANSLATE)) {
+ tv_list_reverse(l);
+ tv_list_set_ret(rettv, l);
+ }
+}
+
+#define SP_NOMOVE 0x01 ///< don't move cursor
+#define SP_REPEAT 0x02 ///< repeat to find outer pair
+#define SP_RETCOUNT 0x04 ///< return matchcount
+#define SP_SETPCMARK 0x08 ///< set previous context mark
+#define SP_START 0x10 ///< accept match at start position
+#define SP_SUBPAT 0x20 ///< return nr of matching sub-pattern
+#define SP_END 0x40 ///< leave cursor at end of match
+#define SP_COLUMN 0x80 ///< start at cursor column
+
+/*
+ * Get flags for a search function.
+ * Possibly sets "p_ws".
+ * Returns BACKWARD, FORWARD or zero (for an error).
+ */
+static int get_search_arg(typval_T *varp, int *flagsp)
+{
+ int dir = FORWARD;
+ int mask;
+
+ if (varp->v_type != VAR_UNKNOWN) {
+ char nbuf[NUMBUFLEN];
+ const char *flags = tv_get_string_buf_chk(varp, nbuf);
+ if (flags == NULL) {
+ return 0; // Type error; errmsg already given.
+ }
+ while (*flags != NUL) {
+ switch (*flags) {
+ case 'b': dir = BACKWARD; break;
+ case 'w': p_ws = true; break;
+ case 'W': p_ws = false; break;
+ default: {
+ mask = 0;
+ if (flagsp != NULL) {
+ switch (*flags) {
+ case 'c': mask = SP_START; break;
+ case 'e': mask = SP_END; break;
+ case 'm': mask = SP_RETCOUNT; break;
+ case 'n': mask = SP_NOMOVE; break;
+ case 'p': mask = SP_SUBPAT; break;
+ case 'r': mask = SP_REPEAT; break;
+ case 's': mask = SP_SETPCMARK; break;
+ case 'z': mask = SP_COLUMN; break;
+ }
+ }
+ if (mask == 0) {
+ emsgf(_(e_invarg2), flags);
+ dir = 0;
+ } else {
+ *flagsp |= mask;
+ }
+ }
+ }
+ if (dir == 0) {
+ break;
+ }
+ flags++;
+ }
+ }
+ return dir;
+}
+
+// Shared by search() and searchpos() functions.
+static int search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp)
+{
+ int flags;
+ pos_T pos;
+ pos_T save_cursor;
+ bool save_p_ws = p_ws;
+ int dir;
+ int retval = 0; // default: FAIL
+ long lnum_stop = 0;
+ proftime_T tm;
+ long time_limit = 0;
+ int options = SEARCH_KEEP;
+ int subpatnum;
+ searchit_arg_T sia;
+
+ const char *const pat = tv_get_string(&argvars[0]);
+ dir = get_search_arg(&argvars[1], flagsp); // May set p_ws.
+ if (dir == 0) {
+ goto theend;
+ }
+ flags = *flagsp;
+ if (flags & SP_START) {
+ options |= SEARCH_START;
+ }
+ if (flags & SP_END) {
+ options |= SEARCH_END;
+ }
+ if (flags & SP_COLUMN) {
+ options |= SEARCH_COL;
+ }
+
+ // Optional arguments: line number to stop searching and timeout.
+ if (argvars[1].v_type != VAR_UNKNOWN && argvars[2].v_type != VAR_UNKNOWN) {
+ lnum_stop = tv_get_number_chk(&argvars[2], NULL);
+ if (lnum_stop < 0) {
+ goto theend;
+ }
+ if (argvars[3].v_type != VAR_UNKNOWN) {
+ time_limit = tv_get_number_chk(&argvars[3], NULL);
+ if (time_limit < 0) {
+ goto theend;
+ }
+ }
+ }
+
+ // Set the time limit, if there is one.
+ tm = profile_setlimit(time_limit);
+
+ /*
+ * This function does not accept SP_REPEAT and SP_RETCOUNT flags.
+ * Check to make sure only those flags are set.
+ * Also, Only the SP_NOMOVE or the SP_SETPCMARK flag can be set. Both
+ * flags cannot be set. Check for that condition also.
+ */
+ if (((flags & (SP_REPEAT | SP_RETCOUNT)) != 0)
+ || ((flags & SP_NOMOVE) && (flags & SP_SETPCMARK))) {
+ EMSG2(_(e_invarg2), tv_get_string(&argvars[1]));
+ goto theend;
+ }
+
+ pos = save_cursor = curwin->w_cursor;
+ memset(&sia, 0, sizeof(sia));
+ sia.sa_stop_lnum = (linenr_T)lnum_stop;
+ sia.sa_tm = &tm;
+ subpatnum = searchit(curwin, curbuf, &pos, NULL, dir, (char_u *)pat, 1,
+ options, RE_SEARCH, &sia);
+ if (subpatnum != FAIL) {
+ if (flags & SP_SUBPAT)
+ retval = subpatnum;
+ else
+ retval = pos.lnum;
+ if (flags & SP_SETPCMARK)
+ setpcmark();
+ curwin->w_cursor = pos;
+ if (match_pos != NULL) {
+ // Store the match cursor position
+ match_pos->lnum = pos.lnum;
+ match_pos->col = pos.col + 1;
+ }
+ // "/$" will put the cursor after the end of the line, may need to
+ // correct that here
+ check_cursor();
+ }
+
+ // If 'n' flag is used: restore cursor position.
+ if (flags & SP_NOMOVE) {
+ curwin->w_cursor = save_cursor;
+ } else {
+ curwin->w_set_curswant = true;
+ }
+theend:
+ p_ws = save_p_ws;
+
+ return retval;
+}
+
+// "rpcnotify()" function
+static void f_rpcnotify(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->v_type = VAR_NUMBER;
+ rettv->vval.v_number = 0;
+
+ if (check_restricted() || check_secure()) {
+ return;
+ }
+
+ if (argvars[0].v_type != VAR_NUMBER || argvars[0].vval.v_number < 0) {
+ EMSG2(_(e_invarg2), "Channel id must be a positive integer");
+ return;
+ }
+
+ if (argvars[1].v_type != VAR_STRING) {
+ EMSG2(_(e_invarg2), "Event type must be a string");
+ return;
+ }
+
+ Array args = ARRAY_DICT_INIT;
+
+ for (typval_T *tv = argvars + 2; tv->v_type != VAR_UNKNOWN; tv++) {
+ ADD(args, vim_to_object(tv));
+ }
+
+ if (!rpc_send_event((uint64_t)argvars[0].vval.v_number,
+ tv_get_string(&argvars[1]), args)) {
+ EMSG2(_(e_invarg2), "Channel doesn't exist");
+ return;
+ }
+
+ rettv->vval.v_number = 1;
+}
+
+// "rpcrequest()" function
+static void f_rpcrequest(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->v_type = VAR_NUMBER;
+ rettv->vval.v_number = 0;
+ const int l_provider_call_nesting = provider_call_nesting;
+
+ if (check_restricted() || check_secure()) {
+ return;
+ }
+
+ if (argvars[0].v_type != VAR_NUMBER || argvars[0].vval.v_number <= 0) {
+ EMSG2(_(e_invarg2), "Channel id must be a positive integer");
+ return;
+ }
+
+ if (argvars[1].v_type != VAR_STRING) {
+ EMSG2(_(e_invarg2), "Method name must be a string");
+ return;
+ }
+
+ Array args = ARRAY_DICT_INIT;
+
+ for (typval_T *tv = argvars + 2; tv->v_type != VAR_UNKNOWN; tv++) {
+ ADD(args, vim_to_object(tv));
+ }
+
+ sctx_T save_current_sctx;
+ uint8_t *save_sourcing_name, *save_autocmd_fname, *save_autocmd_match;
+ linenr_T save_sourcing_lnum;
+ int save_autocmd_bufnr;
+ funccal_entry_T funccal_entry;
+
+ if (l_provider_call_nesting) {
+ // If this is called from a provider function, restore the scope
+ // information of the caller.
+ save_current_sctx = current_sctx;
+ save_sourcing_name = sourcing_name;
+ save_sourcing_lnum = sourcing_lnum;
+ save_autocmd_fname = autocmd_fname;
+ save_autocmd_match = autocmd_match;
+ save_autocmd_bufnr = autocmd_bufnr;
+ save_funccal(&funccal_entry);
+
+ current_sctx = provider_caller_scope.script_ctx;
+ sourcing_name = provider_caller_scope.sourcing_name;
+ sourcing_lnum = provider_caller_scope.sourcing_lnum;
+ autocmd_fname = provider_caller_scope.autocmd_fname;
+ autocmd_match = provider_caller_scope.autocmd_match;
+ autocmd_bufnr = provider_caller_scope.autocmd_bufnr;
+ set_current_funccal((funccall_T *)(provider_caller_scope.funccalp));
+ }
+
+
+ Error err = ERROR_INIT;
+
+ uint64_t chan_id = (uint64_t)argvars[0].vval.v_number;
+ const char *method = tv_get_string(&argvars[1]);
+
+ Object result = rpc_send_call(chan_id, method, args, &err);
+
+ if (l_provider_call_nesting) {
+ current_sctx = save_current_sctx;
+ sourcing_name = save_sourcing_name;
+ sourcing_lnum = save_sourcing_lnum;
+ autocmd_fname = save_autocmd_fname;
+ autocmd_match = save_autocmd_match;
+ autocmd_bufnr = save_autocmd_bufnr;
+ restore_funccal();
+ }
+
+ if (ERROR_SET(&err)) {
+ const char *name = NULL;
+ Channel *chan = find_channel(chan_id);
+ if (chan) {
+ name = rpc_client_name(chan);
+ }
+ msg_ext_set_kind("rpc_error");
+ if (name) {
+ emsgf_multiline("Error invoking '%s' on channel %"PRIu64" (%s):\n%s",
+ method, chan_id, name, err.msg);
+ } else {
+ emsgf_multiline("Error invoking '%s' on channel %"PRIu64":\n%s",
+ method, chan_id, err.msg);
+ }
+
+ goto end;
+ }
+
+ if (!object_to_vim(result, rettv, &err)) {
+ EMSG2(_("Error converting the call result: %s"), err.msg);
+ }
+
+end:
+ api_free_object(result);
+ api_clear_error(&err);
+}
+
+// "rpcstart()" function (DEPRECATED)
+static void f_rpcstart(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->v_type = VAR_NUMBER;
+ rettv->vval.v_number = 0;
+
+ if (check_restricted() || check_secure()) {
+ return;
+ }
+
+ if (argvars[0].v_type != VAR_STRING
+ || (argvars[1].v_type != VAR_LIST && argvars[1].v_type != VAR_UNKNOWN)) {
+ // Wrong argument types
+ EMSG(_(e_invarg));
+ return;
+ }
+
+ list_T *args = NULL;
+ int argsl = 0;
+ if (argvars[1].v_type == VAR_LIST) {
+ args = argvars[1].vval.v_list;
+ argsl = tv_list_len(args);
+ // Assert that all list items are strings
+ int i = 0;
+ TV_LIST_ITER_CONST(args, arg, {
+ if (TV_LIST_ITEM_TV(arg)->v_type != VAR_STRING) {
+ emsgf(_("E5010: List item %d of the second argument is not a string"),
+ i);
+ return;
+ }
+ i++;
+ });
+ }
+
+ if (argvars[0].vval.v_string == NULL || argvars[0].vval.v_string[0] == NUL) {
+ EMSG(_(e_api_spawn_failed));
+ return;
+ }
+
+ // Allocate extra memory for the argument vector and the NULL pointer
+ int argvl = argsl + 2;
+ char **argv = xmalloc(sizeof(char_u *) * argvl);
+
+ // Copy program name
+ argv[0] = xstrdup((char *)argvars[0].vval.v_string);
+
+ int i = 1;
+ // Copy arguments to the vector
+ if (argsl > 0) {
+ TV_LIST_ITER_CONST(args, arg, {
+ argv[i++] = xstrdup(tv_get_string(TV_LIST_ITEM_TV(arg)));
+ });
+ }
+
+ // The last item of argv must be NULL
+ argv[i] = NULL;
+
+ Channel *chan = channel_job_start(argv, CALLBACK_READER_INIT,
+ CALLBACK_READER_INIT, CALLBACK_NONE,
+ false, true, false, false, NULL, 0, 0,
+ NULL, NULL, &rettv->vval.v_number);
+ if (chan) {
+ channel_create_event(chan, NULL);
+ }
+}
+
+// "rpcstop()" function
+static void f_rpcstop(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->v_type = VAR_NUMBER;
+ rettv->vval.v_number = 0;
+
+ if (check_restricted() || check_secure()) {
+ return;
+ }
+
+ if (argvars[0].v_type != VAR_NUMBER) {
+ // Wrong argument types
+ EMSG(_(e_invarg));
+ return;
+ }
+
+ // if called with a job, stop it, else closes the channel
+ uint64_t id = argvars[0].vval.v_number;
+ if (find_job(id, false)) {
+ f_jobstop(argvars, rettv, NULL);
+ } else {
+ const char *error;
+ rettv->vval.v_number = channel_close(argvars[0].vval.v_number,
+ kChannelPartRpc, &error);
+ if (!rettv->vval.v_number) {
+ EMSG(error);
+ }
+ }
+}
+
+/*
+ * "screenattr()" function
+ */
+static void f_screenattr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ int c;
+
+ int row = (int)tv_get_number_chk(&argvars[0], NULL) - 1;
+ int col = (int)tv_get_number_chk(&argvars[1], NULL) - 1;
+ if (row < 0 || row >= default_grid.Rows
+ || col < 0 || col >= default_grid.Columns) {
+ c = -1;
+ } else {
+ ScreenGrid *grid = &default_grid;
+ screenchar_adjust_grid(&grid, &row, &col);
+ c = grid->attrs[grid->line_offset[row] + col];
+ }
+ rettv->vval.v_number = c;
+}
+
+/*
+ * "screenchar()" function
+ */
+static void f_screenchar(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ int c;
+
+ int row = tv_get_number_chk(&argvars[0], NULL) - 1;
+ int col = tv_get_number_chk(&argvars[1], NULL) - 1;
+ if (row < 0 || row >= default_grid.Rows
+ || col < 0 || col >= default_grid.Columns) {
+ c = -1;
+ } else {
+ ScreenGrid *grid = &default_grid;
+ screenchar_adjust_grid(&grid, &row, &col);
+ c = utf_ptr2char(grid->chars[grid->line_offset[row] + col]);
+ }
+ rettv->vval.v_number = c;
+}
+
+/*
+ * "screencol()" function
+ *
+ * First column is 1 to be consistent with virtcol().
+ */
+static void f_screencol(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = ui_current_col() + 1;
+}
+
+/// "screenpos({winid}, {lnum}, {col})" function
+static void f_screenpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ pos_T pos;
+ int row = 0;
+ int scol = 0, ccol = 0, ecol = 0;
+
+ tv_dict_alloc_ret(rettv);
+ dict_T *dict = rettv->vval.v_dict;
+
+ win_T *wp = find_win_by_nr_or_id(&argvars[0]);
+ if (wp == NULL) {
+ return;
+ }
+
+ pos.lnum = tv_get_number(&argvars[1]);
+ pos.col = tv_get_number(&argvars[2]) - 1;
+ pos.coladd = 0;
+ textpos2screenpos(wp, &pos, &row, &scol, &ccol, &ecol, false);
+
+ tv_dict_add_nr(dict, S_LEN("row"), row);
+ tv_dict_add_nr(dict, S_LEN("col"), scol);
+ tv_dict_add_nr(dict, S_LEN("curscol"), ccol);
+ tv_dict_add_nr(dict, S_LEN("endcol"), ecol);
+}
+
+/*
+ * "screenrow()" function
+ */
+static void f_screenrow(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = ui_current_row() + 1;
+}
+
+/*
+ * "search()" function
+ */
+static void f_search(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ int flags = 0;
+
+ rettv->vval.v_number = search_cmn(argvars, NULL, &flags);
+}
+
+/*
+ * "searchdecl()" function
+ */
+static void f_searchdecl(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ int locally = 1;
+ int thisblock = 0;
+ bool error = false;
+
+ rettv->vval.v_number = 1; // default: FAIL
+
+ const char *const name = tv_get_string_chk(&argvars[0]);
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ locally = tv_get_number_chk(&argvars[1], &error) == 0;
+ if (!error && argvars[2].v_type != VAR_UNKNOWN) {
+ thisblock = tv_get_number_chk(&argvars[2], &error) != 0;
+ }
+ }
+ if (!error && name != NULL) {
+ rettv->vval.v_number = find_decl((char_u *)name, strlen(name), locally,
+ thisblock, SEARCH_KEEP) == FAIL;
+ }
+}
+
+/*
+ * Used by searchpair() and searchpairpos()
+ */
+static int searchpair_cmn(typval_T *argvars, pos_T *match_pos)
+{
+ bool save_p_ws = p_ws;
+ int dir;
+ int flags = 0;
+ int retval = 0; // default: FAIL
+ long lnum_stop = 0;
+ long time_limit = 0;
+
+ // Get the three pattern arguments: start, middle, end. Will result in an
+ // error if not a valid argument.
+ char nbuf1[NUMBUFLEN];
+ char nbuf2[NUMBUFLEN];
+ const char *spat = tv_get_string_chk(&argvars[0]);
+ const char *mpat = tv_get_string_buf_chk(&argvars[1], nbuf1);
+ const char *epat = tv_get_string_buf_chk(&argvars[2], nbuf2);
+ if (spat == NULL || mpat == NULL || epat == NULL) {
+ goto theend; // Type error.
+ }
+
+ // Handle the optional fourth argument: flags.
+ dir = get_search_arg(&argvars[3], &flags); // may set p_ws.
+ if (dir == 0) {
+ goto theend;
+ }
+
+ // Don't accept SP_END or SP_SUBPAT.
+ // Only one of the SP_NOMOVE or SP_SETPCMARK flags can be set.
+ if ((flags & (SP_END | SP_SUBPAT)) != 0
+ || ((flags & SP_NOMOVE) && (flags & SP_SETPCMARK))) {
+ EMSG2(_(e_invarg2), tv_get_string(&argvars[3]));
+ goto theend;
+ }
+
+ // Using 'r' implies 'W', otherwise it doesn't work.
+ if (flags & SP_REPEAT) {
+ p_ws = false;
+ }
+
+ // Optional fifth argument: skip expression.
+ const typval_T *skip;
+ if (argvars[3].v_type == VAR_UNKNOWN
+ || argvars[4].v_type == VAR_UNKNOWN) {
+ skip = NULL;
+ } else {
+ skip = &argvars[4];
+ if (skip->v_type != VAR_FUNC
+ && skip->v_type != VAR_PARTIAL
+ && skip->v_type != VAR_STRING) {
+ emsgf(_(e_invarg2), tv_get_string(&argvars[4]));
+ goto theend; // Type error.
+ }
+ if (argvars[5].v_type != VAR_UNKNOWN) {
+ lnum_stop = tv_get_number_chk(&argvars[5], NULL);
+ if (lnum_stop < 0) {
+ emsgf(_(e_invarg2), tv_get_string(&argvars[5]));
+ goto theend;
+ }
+ if (argvars[6].v_type != VAR_UNKNOWN) {
+ time_limit = tv_get_number_chk(&argvars[6], NULL);
+ if (time_limit < 0) {
+ emsgf(_(e_invarg2), tv_get_string(&argvars[6]));
+ goto theend;
+ }
+ }
+ }
+ }
+
+ retval = do_searchpair(
+ (char_u *)spat, (char_u *)mpat, (char_u *)epat, dir, skip,
+ flags, match_pos, lnum_stop, time_limit);
+
+theend:
+ p_ws = save_p_ws;
+
+ return retval;
+}
+
+/*
+ * "searchpair()" function
+ */
+static void f_searchpair(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = searchpair_cmn(argvars, NULL);
+}
+
+/*
+ * "searchpairpos()" function
+ */
+static void f_searchpairpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ pos_T match_pos;
+ int lnum = 0;
+ int col = 0;
+
+ tv_list_alloc_ret(rettv, 2);
+
+ if (searchpair_cmn(argvars, &match_pos) > 0) {
+ lnum = match_pos.lnum;
+ col = match_pos.col;
+ }
+
+ tv_list_append_number(rettv->vval.v_list, (varnumber_T)lnum);
+ tv_list_append_number(rettv->vval.v_list, (varnumber_T)col);
+}
+
+/*
+ * Search for a start/middle/end thing.
+ * Used by searchpair(), see its documentation for the details.
+ * Returns 0 or -1 for no match,
+ */
+long
+do_searchpair(
+ char_u *spat, // start pattern
+ char_u *mpat, // middle pattern
+ char_u *epat, // end pattern
+ int dir, // BACKWARD or FORWARD
+ const typval_T *skip, // skip expression
+ int flags, // SP_SETPCMARK and other SP_ values
+ pos_T *match_pos,
+ linenr_T lnum_stop, // stop at this line if not zero
+ long time_limit // stop after this many msec
+)
+{
+ char_u *save_cpo;
+ char_u *pat, *pat2 = NULL, *pat3 = NULL;
+ long retval = 0;
+ pos_T pos;
+ pos_T firstpos;
+ pos_T foundpos;
+ pos_T save_cursor;
+ pos_T save_pos;
+ int n;
+ int nest = 1;
+ bool use_skip = false;
+ int options = SEARCH_KEEP;
+ proftime_T tm;
+ size_t pat2_len;
+ size_t pat3_len;
+
+ // Make 'cpoptions' empty, the 'l' flag should not be used here.
+ save_cpo = p_cpo;
+ p_cpo = empty_option;
+
+ // Set the time limit, if there is one.
+ tm = profile_setlimit(time_limit);
+
+ // Make two search patterns: start/end (pat2, for in nested pairs) and
+ // start/middle/end (pat3, for the top pair).
+ pat2_len = STRLEN(spat) + STRLEN(epat) + 17;
+ pat2 = xmalloc(pat2_len);
+ pat3_len = STRLEN(spat) + STRLEN(mpat) + STRLEN(epat) + 25;
+ pat3 = xmalloc(pat3_len);
+ snprintf((char *)pat2, pat2_len, "\\m\\(%s\\m\\)\\|\\(%s\\m\\)", spat, epat);
+ if (*mpat == NUL) {
+ STRCPY(pat3, pat2);
+ } else {
+ snprintf((char *)pat3, pat3_len,
+ "\\m\\(%s\\m\\)\\|\\(%s\\m\\)\\|\\(%s\\m\\)", spat, epat, mpat);
+ }
+ if (flags & SP_START) {
+ options |= SEARCH_START;
+ }
+
+ if (skip != NULL) {
+ // Empty string means to not use the skip expression.
+ if (skip->v_type == VAR_STRING || skip->v_type == VAR_FUNC) {
+ use_skip = skip->vval.v_string != NULL && *skip->vval.v_string != NUL;
+ }
+ }
+
+ save_cursor = curwin->w_cursor;
+ pos = curwin->w_cursor;
+ clearpos(&firstpos);
+ clearpos(&foundpos);
+ pat = pat3;
+ for (;; ) {
+ searchit_arg_T sia;
+ memset(&sia, 0, sizeof(sia));
+ sia.sa_stop_lnum = lnum_stop;
+ sia.sa_tm = &tm;
+
+ n = searchit(curwin, curbuf, &pos, NULL, dir, pat, 1L,
+ options, RE_SEARCH, &sia);
+ if (n == FAIL || (firstpos.lnum != 0 && equalpos(pos, firstpos))) {
+ // didn't find it or found the first match again: FAIL
+ break;
+ }
+
+ if (firstpos.lnum == 0)
+ firstpos = pos;
+ if (equalpos(pos, foundpos)) {
+ // Found the same position again. Can happen with a pattern that
+ // has "\zs" at the end and searching backwards. Advance one
+ // character and try again.
+ if (dir == BACKWARD) {
+ decl(&pos);
+ } else {
+ incl(&pos);
+ }
+ }
+ foundpos = pos;
+
+ // clear the start flag to avoid getting stuck here
+ options &= ~SEARCH_START;
+
+ // If the skip pattern matches, ignore this match.
+ if (use_skip) {
+ save_pos = curwin->w_cursor;
+ curwin->w_cursor = pos;
+ bool err = false;
+ const bool r = eval_expr_to_bool(skip, &err);
+ curwin->w_cursor = save_pos;
+ if (err) {
+ // Evaluating {skip} caused an error, break here.
+ curwin->w_cursor = save_cursor;
+ retval = -1;
+ break;
+ }
+ if (r)
+ continue;
+ }
+
+ if ((dir == BACKWARD && n == 3) || (dir == FORWARD && n == 2)) {
+ // Found end when searching backwards or start when searching
+ // forward: nested pair.
+ nest++;
+ pat = pat2; // nested, don't search for middle
+ } else {
+ // Found end when searching forward or start when searching
+ // backward: end of (nested) pair; or found middle in outer pair.
+ if (--nest == 1) {
+ pat = pat3; // outer level, search for middle
+ }
+ }
+
+ if (nest == 0) {
+ // Found the match: return matchcount or line number.
+ if (flags & SP_RETCOUNT) {
+ retval++;
+ } else {
+ retval = pos.lnum;
+ }
+ if (flags & SP_SETPCMARK) {
+ setpcmark();
+ }
+ curwin->w_cursor = pos;
+ if (!(flags & SP_REPEAT))
+ break;
+ nest = 1; // search for next unmatched
+ }
+ }
+
+ if (match_pos != NULL) {
+ // Store the match cursor position
+ match_pos->lnum = curwin->w_cursor.lnum;
+ match_pos->col = curwin->w_cursor.col + 1;
+ }
+
+ // If 'n' flag is used or search failed: restore cursor position.
+ if ((flags & SP_NOMOVE) || retval == 0) {
+ curwin->w_cursor = save_cursor;
+ }
+
+ xfree(pat2);
+ xfree(pat3);
+ if (p_cpo == empty_option) {
+ p_cpo = save_cpo;
+ } else {
+ // Darn, evaluating the {skip} expression changed the value.
+ free_string_option(save_cpo);
+ }
+
+ return retval;
+}
+
+/*
+ * "searchpos()" function
+ */
+static void f_searchpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ pos_T match_pos;
+ int flags = 0;
+
+ const int n = search_cmn(argvars, &match_pos, &flags);
+
+ tv_list_alloc_ret(rettv, 2 + (!!(flags & SP_SUBPAT)));
+
+ const int lnum = (n > 0 ? match_pos.lnum : 0);
+ const int col = (n > 0 ? match_pos.col : 0);
+
+ tv_list_append_number(rettv->vval.v_list, (varnumber_T)lnum);
+ tv_list_append_number(rettv->vval.v_list, (varnumber_T)col);
+ if (flags & SP_SUBPAT) {
+ tv_list_append_number(rettv->vval.v_list, (varnumber_T)n);
+ }
+}
+
+/// "serverlist()" function
+static void f_serverlist(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ size_t n;
+ char **addrs = server_address_list(&n);
+
+ // Copy addrs into a linked list.
+ list_T *const l = tv_list_alloc_ret(rettv, n);
+ for (size_t i = 0; i < n; i++) {
+ tv_list_append_allocated_string(l, addrs[i]);
+ }
+ xfree(addrs);
+}
+
+/// "serverstart()" function
+static void f_serverstart(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = NULL; // Address of the new server
+
+ if (check_restricted() || check_secure()) {
+ return;
+ }
+
+ char *address;
+ // If the user supplied an address, use it, otherwise use a temp.
+ if (argvars[0].v_type != VAR_UNKNOWN) {
+ if (argvars[0].v_type != VAR_STRING) {
+ EMSG(_(e_invarg));
+ return;
+ } else {
+ address = xstrdup(tv_get_string(argvars));
+ }
+ } else {
+ address = server_address_new();
+ }
+
+ int result = server_start(address);
+ xfree(address);
+
+ if (result != 0) {
+ EMSG2("Failed to start server: %s",
+ result > 0 ? "Unknown system error" : uv_strerror(result));
+ return;
+ }
+
+ // Since it's possible server_start adjusted the given {address} (e.g.,
+ // "localhost:" will now have a port), return the final value to the user.
+ size_t n;
+ char **addrs = server_address_list(&n);
+ rettv->vval.v_string = (char_u *)addrs[n - 1];
+
+ n--;
+ for (size_t i = 0; i < n; i++) {
+ xfree(addrs[i]);
+ }
+ xfree(addrs);
+}
+
+/// "serverstop()" function
+static void f_serverstop(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ if (check_restricted() || check_secure()) {
+ return;
+ }
+
+ if (argvars[0].v_type != VAR_STRING) {
+ EMSG(_(e_invarg));
+ return;
+ }
+
+ rettv->v_type = VAR_NUMBER;
+ rettv->vval.v_number = 0;
+ if (argvars[0].vval.v_string) {
+ bool rv = server_stop((char *)argvars[0].vval.v_string);
+ rettv->vval.v_number = (rv ? 1 : 0);
+ }
+}
+
+/// "setbufline()" function
+static void f_setbufline(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ linenr_T lnum;
+ buf_T *buf;
+
+ buf = tv_get_buf(&argvars[0], false);
+ if (buf == NULL) {
+ rettv->vval.v_number = 1; // FAIL
+ } else {
+ lnum = tv_get_lnum_buf(&argvars[1], buf);
+ set_buffer_lines(buf, lnum, false, &argvars[2], rettv);
+ }
+}
+
+/*
+ * "setbufvar()" function
+ */
+static void f_setbufvar(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ if (check_secure()
+ || !tv_check_str_or_nr(&argvars[0])) {
+ return;
+ }
+ const char *varname = tv_get_string_chk(&argvars[1]);
+ buf_T *const buf = tv_get_buf(&argvars[0], false);
+ typval_T *varp = &argvars[2];
+
+ if (buf != NULL && varname != NULL) {
+ if (*varname == '&') {
+ long numval;
+ bool error = false;
+ aco_save_T aco;
+
+ // set curbuf to be our buf, temporarily
+ aucmd_prepbuf(&aco, buf);
+
+ varname++;
+ numval = tv_get_number_chk(varp, &error);
+ char nbuf[NUMBUFLEN];
+ const char *const strval = tv_get_string_buf_chk(varp, nbuf);
+ if (!error && strval != NULL) {
+ set_option_value(varname, numval, strval, OPT_LOCAL);
+ }
+
+ // reset notion of buffer
+ aucmd_restbuf(&aco);
+ } else {
+ const size_t varname_len = STRLEN(varname);
+ char *const bufvarname = xmalloc(varname_len + 3);
+ buf_T *const save_curbuf = curbuf;
+ curbuf = buf;
+ memcpy(bufvarname, "b:", 2);
+ memcpy(bufvarname + 2, varname, varname_len + 1);
+ set_var(bufvarname, varname_len + 2, varp, true);
+ xfree(bufvarname);
+ curbuf = save_curbuf;
+ }
+ }
+}
+
+static void f_setcharsearch(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ dict_T *d;
+ dictitem_T *di;
+
+ if (argvars[0].v_type != VAR_DICT) {
+ EMSG(_(e_dictreq));
+ return;
+ }
+
+ if ((d = argvars[0].vval.v_dict) != NULL) {
+ char_u *const csearch = (char_u *)tv_dict_get_string(d, "char", false);
+ if (csearch != NULL) {
+ if (enc_utf8) {
+ int pcc[MAX_MCO];
+ int c = utfc_ptr2char(csearch, pcc);
+ set_last_csearch(c, csearch, utfc_ptr2len(csearch));
+ }
+ else
+ set_last_csearch(PTR2CHAR(csearch),
+ csearch, utfc_ptr2len(csearch));
+ }
+
+ di = tv_dict_find(d, S_LEN("forward"));
+ if (di != NULL) {
+ set_csearch_direction(tv_get_number(&di->di_tv) ? FORWARD : BACKWARD);
+ }
+
+ di = tv_dict_find(d, S_LEN("until"));
+ if (di != NULL) {
+ set_csearch_until(!!tv_get_number(&di->di_tv));
+ }
+ }
+}
+
+/*
+ * "setcmdpos()" function
+ */
+static void f_setcmdpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const int pos = (int)tv_get_number(&argvars[0]) - 1;
+
+ if (pos >= 0) {
+ rettv->vval.v_number = set_cmdline_pos(pos);
+ }
+}
+
+/// "setenv()" function
+static void f_setenv(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ char namebuf[NUMBUFLEN];
+ char valbuf[NUMBUFLEN];
+ const char *name = tv_get_string_buf(&argvars[0], namebuf);
+
+ if (argvars[1].v_type == VAR_SPECIAL
+ && argvars[1].vval.v_special == kSpecialVarNull) {
+ os_unsetenv(name);
+ } else {
+ os_setenv(name, tv_get_string_buf(&argvars[1], valbuf), 1);
+ }
+}
+
+/// "setfperm({fname}, {mode})" function
+static void f_setfperm(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = 0;
+
+ const char *const fname = tv_get_string_chk(&argvars[0]);
+ if (fname == NULL) {
+ return;
+ }
+
+ char modebuf[NUMBUFLEN];
+ const char *const mode_str = tv_get_string_buf_chk(&argvars[1], modebuf);
+ if (mode_str == NULL) {
+ return;
+ }
+ if (strlen(mode_str) != 9) {
+ EMSG2(_(e_invarg2), mode_str);
+ return;
+ }
+
+ int mask = 1;
+ int mode = 0;
+ for (int i = 8; i >= 0; i--) {
+ if (mode_str[i] != '-') {
+ mode |= mask;
+ }
+ mask = mask << 1;
+ }
+ rettv->vval.v_number = os_setperm(fname, mode) == OK;
+}
+
+/*
+ * "setline()" function
+ */
+static void f_setline(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ linenr_T lnum = tv_get_lnum(&argvars[0]);
+ set_buffer_lines(curbuf, lnum, false, &argvars[1], rettv);
+}
+
+/// Create quickfix/location list from VimL values
+///
+/// Used by `setqflist()` and `setloclist()` functions. Accepts invalid
+/// list_arg, action_arg and what_arg arguments in which case errors out,
+/// including VAR_UNKNOWN parameters.
+///
+/// @param[in,out] wp Window to create location list for. May be NULL in
+/// which case quickfix list will be created.
+/// @param[in] list_arg Quickfix list contents.
+/// @param[in] action_arg Action to perform: append to an existing list,
+/// replace its content or create a new one.
+/// @param[in] title_arg New list title. Defaults to caller function name.
+/// @param[out] rettv Return value: 0 in case of success, -1 otherwise.
+static void set_qf_ll_list(win_T *wp, typval_T *args, typval_T *rettv)
+ FUNC_ATTR_NONNULL_ARG(2, 3)
+{
+ static char *e_invact = N_("E927: Invalid action: '%s'");
+ const char *title = NULL;
+ int action = ' ';
+ static int recursive = 0;
+ rettv->vval.v_number = -1;
+ dict_T *d = NULL;
+
+ typval_T *list_arg = &args[0];
+ if (list_arg->v_type != VAR_LIST) {
+ EMSG(_(e_listreq));
+ return;
+ } else if (recursive != 0) {
+ EMSG(_(e_au_recursive));
+ return;
+ }
+
+ typval_T *action_arg = &args[1];
+ if (action_arg->v_type == VAR_UNKNOWN) {
+ // Option argument was not given.
+ goto skip_args;
+ } else if (action_arg->v_type != VAR_STRING) {
+ EMSG(_(e_stringreq));
+ return;
+ }
+ const char *const act = tv_get_string_chk(action_arg);
+ if ((*act == 'a' || *act == 'r' || *act == ' ' || *act == 'f')
+ && act[1] == NUL) {
+ action = *act;
+ } else {
+ EMSG2(_(e_invact), act);
+ return;
+ }
+
+ typval_T *title_arg = &args[2];
+ if (title_arg->v_type == VAR_UNKNOWN) {
+ // Option argument was not given.
+ goto skip_args;
+ } else if (title_arg->v_type == VAR_STRING) {
+ title = tv_get_string_chk(title_arg);
+ if (!title) {
+ // Type error. Error already printed by tv_get_string_chk().
+ return;
+ }
+ } else if (title_arg->v_type == VAR_DICT) {
+ d = title_arg->vval.v_dict;
+ } else {
+ EMSG(_(e_dictreq));
+ return;
+ }
+
+skip_args:
+ if (!title) {
+ title = (wp ? ":setloclist()" : ":setqflist()");
+ }
+
+ recursive++;
+ list_T *const l = list_arg->vval.v_list;
+ if (set_errorlist(wp, l, action, (char_u *)title, d) == OK) {
+ rettv->vval.v_number = 0;
+ }
+ recursive--;
+}
+
+/*
+ * "setloclist()" function
+ */
+static void f_setloclist(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ win_T *win;
+
+ rettv->vval.v_number = -1;
+
+ win = find_win_by_nr_or_id(&argvars[0]);
+ if (win != NULL) {
+ set_qf_ll_list(win, &argvars[1], rettv);
+ }
+}
+
+/*
+ * "setmatches()" function
+ */
+static void f_setmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ dict_T *d;
+ list_T *s = NULL;
+ win_T *win = get_optional_window(argvars, 1);
+
+ rettv->vval.v_number = -1;
+ if (argvars[0].v_type != VAR_LIST) {
+ EMSG(_(e_listreq));
+ return;
+ }
+ if (win == NULL) {
+ return;
+ }
+
+ list_T *const l = argvars[0].vval.v_list;
+ // To some extent make sure that we are dealing with a list from
+ // "getmatches()".
+ int li_idx = 0;
+ TV_LIST_ITER_CONST(l, li, {
+ if (TV_LIST_ITEM_TV(li)->v_type != VAR_DICT
+ || (d = TV_LIST_ITEM_TV(li)->vval.v_dict) == NULL) {
+ emsgf(_("E474: List item %d is either not a dictionary "
+ "or an empty one"), li_idx);
+ return;
+ }
+ if (!(tv_dict_find(d, S_LEN("group")) != NULL
+ && (tv_dict_find(d, S_LEN("pattern")) != NULL
+ || tv_dict_find(d, S_LEN("pos1")) != NULL)
+ && tv_dict_find(d, S_LEN("priority")) != NULL
+ && tv_dict_find(d, S_LEN("id")) != NULL)) {
+ emsgf(_("E474: List item %d is missing one of the required keys"),
+ li_idx);
+ return;
+ }
+ li_idx++;
+ });
+
+ clear_matches(win);
+ bool match_add_failed = false;
+ TV_LIST_ITER_CONST(l, li, {
+ int i = 0;
+
+ d = TV_LIST_ITEM_TV(li)->vval.v_dict;
+ dictitem_T *const di = tv_dict_find(d, S_LEN("pattern"));
+ if (di == NULL) {
+ if (s == NULL) {
+ s = tv_list_alloc(9);
+ }
+
+ // match from matchaddpos()
+ for (i = 1; i < 9; i++) {
+ char buf[30]; // use 30 to avoid compiler warning
+ snprintf(buf, sizeof(buf), "pos%d", i);
+ dictitem_T *const pos_di = tv_dict_find(d, buf, -1);
+ if (pos_di != NULL) {
+ if (pos_di->di_tv.v_type != VAR_LIST) {
+ return;
+ }
+
+ tv_list_append_tv(s, &pos_di->di_tv);
+ tv_list_ref(s);
+ } else {
+ break;
+ }
+ }
+ }
+
+ // Note: there are three number buffers involved:
+ // - group_buf below.
+ // - numbuf in tv_dict_get_string().
+ // - mybuf in tv_get_string().
+ //
+ // If you change this code make sure that buffers will not get
+ // accidentally reused.
+ char group_buf[NUMBUFLEN];
+ const char *const group = tv_dict_get_string_buf(d, "group", group_buf);
+ const int priority = (int)tv_dict_get_number(d, "priority");
+ const int id = (int)tv_dict_get_number(d, "id");
+ dictitem_T *const conceal_di = tv_dict_find(d, S_LEN("conceal"));
+ const char *const conceal = (conceal_di != NULL
+ ? tv_get_string(&conceal_di->di_tv)
+ : NULL);
+ if (i == 0) {
+ if (match_add(win, group,
+ tv_dict_get_string(d, "pattern", false),
+ priority, id, NULL, conceal) != id) {
+ match_add_failed = true;
+ }
+ } else {
+ if (match_add(win, group, NULL, priority, id, s, conceal) != id) {
+ match_add_failed = true;
+ }
+ tv_list_unref(s);
+ s = NULL;
+ }
+ });
+ if (!match_add_failed) {
+ rettv->vval.v_number = 0;
+ }
+}
+
+/*
+ * "setpos()" function
+ */
+static void f_setpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ pos_T pos;
+ int fnum;
+ colnr_T curswant = -1;
+
+ rettv->vval.v_number = -1;
+ const char *const name = tv_get_string_chk(argvars);
+ if (name != NULL) {
+ if (list2fpos(&argvars[1], &pos, &fnum, &curswant) == OK) {
+ if (pos.col != MAXCOL && --pos.col < 0) {
+ pos.col = 0;
+ }
+ if (name[0] == '.' && name[1] == NUL) {
+ // set cursor; "fnum" is ignored
+ curwin->w_cursor = pos;
+ if (curswant >= 0) {
+ curwin->w_curswant = curswant - 1;
+ curwin->w_set_curswant = false;
+ }
+ check_cursor();
+ rettv->vval.v_number = 0;
+ } else if (name[0] == '\'' && name[1] != NUL && name[2] == NUL) {
+ // set mark
+ if (setmark_pos((uint8_t)name[1], &pos, fnum) == OK) {
+ rettv->vval.v_number = 0;
+ }
+ } else {
+ EMSG(_(e_invarg));
+ }
+ }
+ }
+}
+
+/*
+ * "setqflist()" function
+ */
+static void f_setqflist(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ set_qf_ll_list(NULL, argvars, rettv);
+}
+
+/*
+ * "setreg()" function
+ */
+static void f_setreg(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ int regname;
+ bool append = false;
+ MotionType yank_type;
+ long block_len;
+
+ block_len = -1;
+ yank_type = kMTUnknown;
+
+ rettv->vval.v_number = 1; // FAIL is default.
+
+ const char *const strregname = tv_get_string_chk(argvars);
+ if (strregname == NULL) {
+ return; // Type error; errmsg already given.
+ }
+ regname = (uint8_t)(*strregname);
+ if (regname == 0 || regname == '@') {
+ regname = '"';
+ }
+
+ bool set_unnamed = false;
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ const char *stropt = tv_get_string_chk(&argvars[2]);
+ if (stropt == NULL) {
+ return; // Type error.
+ }
+ for (; *stropt != NUL; stropt++) {
+ switch (*stropt) {
+ case 'a': case 'A': { // append
+ append = true;
+ break;
+ }
+ case 'v': case 'c': { // character-wise selection
+ yank_type = kMTCharWise;
+ break;
+ }
+ case 'V': case 'l': { // line-wise selection
+ yank_type = kMTLineWise;
+ break;
+ }
+ case 'b': case Ctrl_V: { // block-wise selection
+ yank_type = kMTBlockWise;
+ if (ascii_isdigit(stropt[1])) {
+ stropt++;
+ block_len = getdigits_long((char_u **)&stropt, true, 0) - 1;
+ stropt--;
+ }
+ break;
+ }
+ case 'u': case '"': { // unnamed register
+ set_unnamed = true;
+ break;
+ }
+ }
+ }
+ }
+
+ if (argvars[1].v_type == VAR_LIST) {
+ list_T *ll = argvars[1].vval.v_list;
+ // If the list is NULL handle like an empty list.
+ const int len = tv_list_len(ll);
+
+ // First half: use for pointers to result lines; second half: use for
+ // pointers to allocated copies.
+ char **lstval = xmalloc(sizeof(char *) * ((len + 1) * 2));
+ const char **curval = (const char **)lstval;
+ char **allocval = lstval + len + 2;
+ char **curallocval = allocval;
+
+ TV_LIST_ITER_CONST(ll, li, {
+ char buf[NUMBUFLEN];
+ *curval = tv_get_string_buf_chk(TV_LIST_ITEM_TV(li), buf);
+ if (*curval == NULL) {
+ goto free_lstval;
+ }
+ if (*curval == buf) {
+ // Need to make a copy,
+ // next tv_get_string_buf_chk() will overwrite the string.
+ *curallocval = xstrdup(*curval);
+ *curval = *curallocval;
+ curallocval++;
+ }
+ curval++;
+ });
+ *curval++ = NULL;
+
+ write_reg_contents_lst(regname, (char_u **)lstval, append, yank_type,
+ block_len);
+
+free_lstval:
+ while (curallocval > allocval) {
+ xfree(*--curallocval);
+ }
+ xfree(lstval);
+ } else {
+ const char *strval = tv_get_string_chk(&argvars[1]);
+ if (strval == NULL) {
+ return;
+ }
+ write_reg_contents_ex(regname, (const char_u *)strval, STRLEN(strval),
+ append, yank_type, block_len);
+ }
+ rettv->vval.v_number = 0;
+
+ if (set_unnamed) {
+ // Discard the result. We already handle the error case.
+ if (op_reg_set_previous(regname)) { }
+ }
+}
+
+/*
+ * "settabvar()" function
+ */
+static void f_settabvar(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = 0;
+
+ if (check_secure()) {
+ return;
+ }
+
+ tabpage_T *const tp = find_tabpage((int)tv_get_number_chk(&argvars[0], NULL));
+ const char *const varname = tv_get_string_chk(&argvars[1]);
+ typval_T *const varp = &argvars[2];
+
+ if (varname != NULL && tp != NULL) {
+ tabpage_T *const save_curtab = curtab;
+ goto_tabpage_tp(tp, false, false);
+
+ const size_t varname_len = strlen(varname);
+ char *const tabvarname = xmalloc(varname_len + 3);
+ memcpy(tabvarname, "t:", 2);
+ memcpy(tabvarname + 2, varname, varname_len + 1);
+ set_var(tabvarname, varname_len + 2, varp, true);
+ xfree(tabvarname);
+
+ // Restore current tabpage.
+ if (valid_tabpage(save_curtab)) {
+ goto_tabpage_tp(save_curtab, false, false);
+ }
+ }
+}
+
+/*
+ * "settabwinvar()" function
+ */
+static void f_settabwinvar(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ setwinvar(argvars, rettv, 1);
+}
+
+// "settagstack()" function
+static void f_settagstack(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ static char *e_invact2 = N_("E962: Invalid action: '%s'");
+ win_T *wp;
+ dict_T *d;
+ int action = 'r';
+
+ rettv->vval.v_number = -1;
+
+ // first argument: window number or id
+ wp = find_win_by_nr_or_id(&argvars[0]);
+ if (wp == NULL) {
+ return;
+ }
+
+ // second argument: dict with items to set in the tag stack
+ if (argvars[1].v_type != VAR_DICT) {
+ EMSG(_(e_dictreq));
+ return;
+ }
+ d = argvars[1].vval.v_dict;
+ if (d == NULL) {
+ return;
+ }
+
+ // third argument: action - 'a' for append and 'r' for replace.
+ // default is to replace the stack.
+ if (argvars[2].v_type == VAR_UNKNOWN) {
+ action = 'r';
+ } else if (argvars[2].v_type == VAR_STRING) {
+ const char *actstr;
+ actstr = tv_get_string_chk(&argvars[2]);
+ if (actstr == NULL) {
+ return;
+ }
+ if ((*actstr == 'r' || *actstr == 'a' || *actstr == 't')
+ && actstr[1] == NUL) {
+ action = *actstr;
+ } else {
+ EMSG2(_(e_invact2), actstr);
+ return;
+ }
+ } else {
+ EMSG(_(e_stringreq));
+ return;
+ }
+
+ if (set_tagstack(wp, d, action) == OK) {
+ rettv->vval.v_number = 0;
+ } else {
+ EMSG(_(e_listreq));
+ }
+}
+
+/*
+ * "setwinvar()" function
+ */
+static void f_setwinvar(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ setwinvar(argvars, rettv, 0);
+}
+
+/// f_sha256 - sha256({string}) function
+static void f_sha256(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const char *p = tv_get_string(&argvars[0]);
+ const char *hash = sha256_bytes((const uint8_t *)p, strlen(p) , NULL, 0);
+
+ // make a copy of the hash (sha256_bytes returns a static buffer)
+ rettv->vval.v_string = (char_u *)xstrdup(hash);
+ rettv->v_type = VAR_STRING;
+}
+
+/*
+ * "shellescape({string})" function
+ */
+static void f_shellescape(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const bool do_special = non_zero_arg(&argvars[1]);
+
+ rettv->vval.v_string = vim_strsave_shellescape(
+ (const char_u *)tv_get_string(&argvars[0]), do_special, do_special);
+ rettv->v_type = VAR_STRING;
+}
+
+/*
+ * shiftwidth() function
+ */
+static void f_shiftwidth(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = get_sw_value(curbuf);
+}
+
+/// "sign_define()" function
+static void f_sign_define(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const char *name;
+ dict_T *dict;
+ char *icon = NULL;
+ char *linehl = NULL;
+ char *text = NULL;
+ char *texthl = NULL;
+ char *numhl = NULL;
+
+ rettv->vval.v_number = -1;
+
+ name = tv_get_string_chk(&argvars[0]);
+ if (name == NULL) {
+ return;
+ }
+
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ if (argvars[1].v_type != VAR_DICT) {
+ EMSG(_(e_dictreq));
+ return;
+ }
+
+ // sign attributes
+ dict = argvars[1].vval.v_dict;
+ if (tv_dict_find(dict, "icon", -1) != NULL) {
+ icon = tv_dict_get_string(dict, "icon", true);
+ }
+ if (tv_dict_find(dict, "linehl", -1) != NULL) {
+ linehl = tv_dict_get_string(dict, "linehl", true);
+ }
+ if (tv_dict_find(dict, "text", -1) != NULL) {
+ text = tv_dict_get_string(dict, "text", true);
+ }
+ if (tv_dict_find(dict, "texthl", -1) != NULL) {
+ texthl = tv_dict_get_string(dict, "texthl", true);
+ }
+ if (tv_dict_find(dict, "numhl", -1) != NULL) {
+ numhl = tv_dict_get_string(dict, "numhl", true);
+ }
+ }
+
+ if (sign_define_by_name((char_u *)name, (char_u *)icon, (char_u *)linehl,
+ (char_u *)text, (char_u *)texthl, (char_u *)numhl)
+ == OK) {
+ rettv->vval.v_number = 0;
+ }
+
+ xfree(icon);
+ xfree(linehl);
+ xfree(text);
+ xfree(texthl);
+ xfree(numhl);
+}
+
+/// "sign_getdefined()" function
+static void f_sign_getdefined(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const char *name = NULL;
+
+ tv_list_alloc_ret(rettv, 0);
+
+ if (argvars[0].v_type != VAR_UNKNOWN) {
+ name = tv_get_string(&argvars[0]);
+ }
+
+ sign_getlist((const char_u *)name, rettv->vval.v_list);
+}
+
+/// "sign_getplaced()" function
+static void f_sign_getplaced(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ buf_T *buf = NULL;
+ dict_T *dict;
+ dictitem_T *di;
+ linenr_T lnum = 0;
+ int sign_id = 0;
+ const char *group = NULL;
+ bool notanum = false;
+
+ tv_list_alloc_ret(rettv, 0);
+
+ if (argvars[0].v_type != VAR_UNKNOWN) {
+ // get signs placed in the specified buffer
+ buf = get_buf_arg(&argvars[0]);
+ if (buf == NULL) {
+ return;
+ }
+
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ if (argvars[1].v_type != VAR_DICT
+ || ((dict = argvars[1].vval.v_dict) == NULL)) {
+ EMSG(_(e_dictreq));
+ return;
+ }
+ if ((di = tv_dict_find(dict, "lnum", -1)) != NULL) {
+ // get signs placed at this line
+ lnum = (linenr_T)tv_get_number_chk(&di->di_tv, &notanum);
+ if (notanum) {
+ return;
+ }
+ (void)lnum;
+ lnum = tv_get_lnum(&di->di_tv);
+ }
+ if ((di = tv_dict_find(dict, "id", -1)) != NULL) {
+ // get sign placed with this identifier
+ sign_id = (int)tv_get_number_chk(&di->di_tv, &notanum);
+ if (notanum) {
+ return;
+ }
+ }
+ if ((di = tv_dict_find(dict, "group", -1)) != NULL) {
+ group = tv_get_string_chk(&di->di_tv);
+ if (group == NULL) {
+ return;
+ }
+ if (*group == '\0') { // empty string means global group
+ group = NULL;
+ }
+ }
+ }
+ }
+
+ sign_get_placed(buf, lnum, sign_id, (const char_u *)group,
+ rettv->vval.v_list);
+}
+
+/// "sign_jump()" function
+static void f_sign_jump(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ int sign_id;
+ char *sign_group = NULL;
+ buf_T *buf;
+ bool notanum = false;
+
+ rettv->vval.v_number = -1;
+
+ // Sign identifer
+ sign_id = (int)tv_get_number_chk(&argvars[0], &notanum);
+ if (notanum) {
+ return;
+ }
+ if (sign_id <= 0) {
+ EMSG(_(e_invarg));
+ return;
+ }
+
+ // Sign group
+ const char * sign_group_chk = tv_get_string_chk(&argvars[1]);
+ if (sign_group_chk == NULL) {
+ return;
+ }
+ if (sign_group_chk[0] == '\0') {
+ sign_group = NULL; // global sign group
+ } else {
+ sign_group = xstrdup(sign_group_chk);
+ }
+
+ // Buffer to place the sign
+ buf = get_buf_arg(&argvars[2]);
+ if (buf == NULL) {
+ goto cleanup;
+ }
+
+ rettv->vval.v_number = sign_jump(sign_id, (char_u *)sign_group, buf);
+
+cleanup:
+ xfree(sign_group);
+}
+
+/// "sign_place()" function
+static void f_sign_place(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ int sign_id;
+ char_u *group = NULL;
+ const char *sign_name;
+ buf_T *buf;
+ dict_T *dict;
+ dictitem_T *di;
+ linenr_T lnum = 0;
+ int prio = SIGN_DEF_PRIO;
+ bool notanum = false;
+
+ rettv->vval.v_number = -1;
+
+ // Sign identifer
+ sign_id = (int)tv_get_number_chk(&argvars[0], &notanum);
+ if (notanum) {
+ return;
+ }
+ if (sign_id < 0) {
+ EMSG(_(e_invarg));
+ return;
+ }
+
+ // Sign group
+ const char *group_chk = tv_get_string_chk(&argvars[1]);
+ if (group_chk == NULL) {
+ return;
+ }
+ if (group_chk[0] == '\0') {
+ group = NULL; // global sign group
+ } else {
+ group = vim_strsave((const char_u *)group_chk);
+ }
+
+ // Sign name
+ sign_name = tv_get_string_chk(&argvars[2]);
+ if (sign_name == NULL) {
+ goto cleanup;
+ }
+
+ // Buffer to place the sign
+ buf = get_buf_arg(&argvars[3]);
+ if (buf == NULL) {
+ goto cleanup;
+ }
+
+ if (argvars[4].v_type != VAR_UNKNOWN) {
+ if (argvars[4].v_type != VAR_DICT
+ || ((dict = argvars[4].vval.v_dict) == NULL)) {
+ EMSG(_(e_dictreq));
+ goto cleanup;
+ }
+
+ // Line number where the sign is to be placed
+ if ((di = tv_dict_find(dict, "lnum", -1)) != NULL) {
+ lnum = (linenr_T)tv_get_number_chk(&di->di_tv, &notanum);
+ if (notanum) {
+ goto cleanup;
+ }
+ (void)lnum;
+ lnum = tv_get_lnum(&di->di_tv);
+ }
+ if ((di = tv_dict_find(dict, "priority", -1)) != NULL) {
+ // Sign priority
+ prio = (int)tv_get_number_chk(&di->di_tv, &notanum);
+ if (notanum) {
+ goto cleanup;
+ }
+ }
+ }
+
+ if (sign_place(&sign_id, group, (const char_u *)sign_name, buf, lnum, prio)
+ == OK) {
+ rettv->vval.v_number = sign_id;
+ }
+
+cleanup:
+ xfree(group);
+}
+
+/// "sign_undefine()" function
+static void f_sign_undefine(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const char *name;
+
+ rettv->vval.v_number = -1;
+
+ if (argvars[0].v_type == VAR_UNKNOWN) {
+ // Free all the signs
+ free_signs();
+ rettv->vval.v_number = 0;
+ } else {
+ // Free only the specified sign
+ name = tv_get_string_chk(&argvars[0]);
+ if (name == NULL) {
+ return;
+ }
+
+ if (sign_undefine_by_name((const char_u *)name) == OK) {
+ rettv->vval.v_number = 0;
+ }
+ }
+}
+
+/// "sign_unplace()" function
+static void f_sign_unplace(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ dict_T *dict;
+ dictitem_T *di;
+ int sign_id = 0;
+ buf_T *buf = NULL;
+ char_u *group = NULL;
+
+ rettv->vval.v_number = -1;
+
+ if (argvars[0].v_type != VAR_STRING) {
+ EMSG(_(e_invarg));
+ return;
+ }
+
+ const char *group_chk = tv_get_string(&argvars[0]);
+ if (group_chk[0] == '\0') {
+ group = NULL; // global sign group
+ } else {
+ group = vim_strsave((const char_u *)group_chk);
+ }
+
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ if (argvars[1].v_type != VAR_DICT) {
+ EMSG(_(e_dictreq));
+ goto cleanup;
+ }
+ dict = argvars[1].vval.v_dict;
+
+ if ((di = tv_dict_find(dict, "buffer", -1)) != NULL) {
+ buf = get_buf_arg(&di->di_tv);
+ if (buf == NULL) {
+ goto cleanup;
+ }
+ }
+ if (tv_dict_find(dict, "id", -1) != NULL) {
+ sign_id = tv_dict_get_number(dict, "id");
+ }
+ }
+
+ if (buf == NULL) {
+ // Delete the sign in all the buffers
+ FOR_ALL_BUFFERS(cbuf) {
+ if (sign_unplace(sign_id, group, cbuf, 0) == OK) {
+ rettv->vval.v_number = 0;
+ }
+ }
+ } else {
+ if (sign_unplace(sign_id, group, buf, 0) == OK) {
+ rettv->vval.v_number = 0;
+ }
+ }
+
+cleanup:
+ xfree(group);
+}
+
+/*
+ * "simplify()" function
+ */
+static void f_simplify(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const char *const p = tv_get_string(&argvars[0]);
+ rettv->vval.v_string = (char_u *)xstrdup(p);
+ simplify_filename(rettv->vval.v_string); // Simplify in place.
+ rettv->v_type = VAR_STRING;
+}
+
+/// "sockconnect()" function
+static void f_sockconnect(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ if (argvars[0].v_type != VAR_STRING || argvars[1].v_type != VAR_STRING) {
+ EMSG(_(e_invarg));
+ return;
+ }
+ if (argvars[2].v_type != VAR_DICT && argvars[2].v_type != VAR_UNKNOWN) {
+ // Wrong argument types
+ EMSG2(_(e_invarg2), "expected dictionary");
+ return;
+ }
+
+ const char *mode = tv_get_string(&argvars[0]);
+ const char *address = tv_get_string(&argvars[1]);
+
+ bool tcp;
+ if (strcmp(mode, "tcp") == 0) {
+ tcp = true;
+ } else if (strcmp(mode, "pipe") == 0) {
+ tcp = false;
+ } else {
+ EMSG2(_(e_invarg2), "invalid mode");
+ return;
+ }
+
+ bool rpc = false;
+ CallbackReader on_data = CALLBACK_READER_INIT;
+ if (argvars[2].v_type == VAR_DICT) {
+ dict_T *opts = argvars[2].vval.v_dict;
+ rpc = tv_dict_get_number(opts, "rpc") != 0;
+
+ if (!tv_dict_get_callback(opts, S_LEN("on_data"), &on_data.cb)) {
+ return;
+ }
+ on_data.buffered = tv_dict_get_number(opts, "data_buffered");
+ if (on_data.buffered && on_data.cb.type == kCallbackNone) {
+ on_data.self = opts;
+ }
+ }
+
+ const char *error = NULL;
+ uint64_t id = channel_connect(tcp, address, rpc, on_data, 50, &error);
+
+ if (error) {
+ EMSG2(_("connection failed: %s"), error);
+ }
+
+ rettv->vval.v_number = (varnumber_T)id;
+ rettv->v_type = VAR_NUMBER;
+}
+
+/// struct storing information about current sort
+typedef struct {
+ int item_compare_ic;
+ bool item_compare_numeric;
+ bool item_compare_numbers;
+ bool item_compare_float;
+ const char *item_compare_func;
+ partial_T *item_compare_partial;
+ dict_T *item_compare_selfdict;
+ bool item_compare_func_err;
+} sortinfo_T;
+static sortinfo_T *sortinfo = NULL;
+
+#define ITEM_COMPARE_FAIL 999
+
+/*
+ * Compare functions for f_sort() and f_uniq() below.
+ */
+static int item_compare(const void *s1, const void *s2, bool keep_zero)
+{
+ ListSortItem *const si1 = (ListSortItem *)s1;
+ ListSortItem *const si2 = (ListSortItem *)s2;
+
+ typval_T *const tv1 = TV_LIST_ITEM_TV(si1->item);
+ typval_T *const tv2 = TV_LIST_ITEM_TV(si2->item);
+
+ int res;
+
+ if (sortinfo->item_compare_numbers) {
+ const varnumber_T v1 = tv_get_number(tv1);
+ const varnumber_T v2 = tv_get_number(tv2);
+
+ res = v1 == v2 ? 0 : v1 > v2 ? 1 : -1;
+ goto item_compare_end;
+ }
+
+ if (sortinfo->item_compare_float) {
+ const float_T v1 = tv_get_float(tv1);
+ const float_T v2 = tv_get_float(tv2);
+
+ res = v1 == v2 ? 0 : v1 > v2 ? 1 : -1;
+ goto item_compare_end;
+ }
+
+ char *tofree1 = NULL;
+ char *tofree2 = NULL;
+ char *p1;
+ char *p2;
+
+ // encode_tv2string() puts quotes around a string and allocates memory. Don't
+ // do that for string variables. Use a single quote when comparing with
+ // a non-string to do what the docs promise.
+ if (tv1->v_type == VAR_STRING) {
+ if (tv2->v_type != VAR_STRING || sortinfo->item_compare_numeric) {
+ p1 = "'";
+ } else {
+ p1 = (char *)tv1->vval.v_string;
+ }
+ } else {
+ tofree1 = p1 = encode_tv2string(tv1, NULL);
+ }
+ if (tv2->v_type == VAR_STRING) {
+ if (tv1->v_type != VAR_STRING || sortinfo->item_compare_numeric) {
+ p2 = "'";
+ } else {
+ p2 = (char *)tv2->vval.v_string;
+ }
+ } else {
+ tofree2 = p2 = encode_tv2string(tv2, NULL);
+ }
+ if (p1 == NULL) {
+ p1 = "";
+ }
+ if (p2 == NULL) {
+ p2 = "";
+ }
+ if (!sortinfo->item_compare_numeric) {
+ if (sortinfo->item_compare_ic) {
+ res = STRICMP(p1, p2);
+ } else {
+ res = STRCMP(p1, p2);
+ }
+ } else {
+ double n1, n2;
+ n1 = strtod(p1, &p1);
+ n2 = strtod(p2, &p2);
+ res = n1 == n2 ? 0 : n1 > n2 ? 1 : -1;
+ }
+
+ xfree(tofree1);
+ xfree(tofree2);
+
+item_compare_end:
+ // When the result would be zero, compare the item indexes. Makes the
+ // sort stable.
+ if (res == 0 && !keep_zero) {
+ // WARNING: When using uniq si1 and si2 are actually listitem_T **, no
+ // indexes are there.
+ res = si1->idx > si2->idx ? 1 : -1;
+ }
+ return res;
+}
+
+static int item_compare_keeping_zero(const void *s1, const void *s2)
+{
+ return item_compare(s1, s2, true);
+}
+
+static int item_compare_not_keeping_zero(const void *s1, const void *s2)
+{
+ return item_compare(s1, s2, false);
+}
+
+static int item_compare2(const void *s1, const void *s2, bool keep_zero)
+{
+ ListSortItem *si1, *si2;
+ int res;
+ typval_T rettv;
+ typval_T argv[3];
+ int dummy;
+ const char *func_name;
+ partial_T *partial = sortinfo->item_compare_partial;
+
+ // shortcut after failure in previous call; compare all items equal
+ if (sortinfo->item_compare_func_err) {
+ return 0;
+ }
+
+ si1 = (ListSortItem *)s1;
+ si2 = (ListSortItem *)s2;
+
+ if (partial == NULL) {
+ func_name = sortinfo->item_compare_func;
+ } else {
+ func_name = (const char *)partial_name(partial);
+ }
+
+ // Copy the values. This is needed to be able to set v_lock to VAR_FIXED
+ // in the copy without changing the original list items.
+ tv_copy(TV_LIST_ITEM_TV(si1->item), &argv[0]);
+ tv_copy(TV_LIST_ITEM_TV(si2->item), &argv[1]);
+
+ rettv.v_type = VAR_UNKNOWN; // tv_clear() uses this
+ res = call_func((const char_u *)func_name,
+ (int)STRLEN(func_name),
+ &rettv, 2, argv, NULL, 0L, 0L, &dummy, true,
+ partial, sortinfo->item_compare_selfdict);
+ tv_clear(&argv[0]);
+ tv_clear(&argv[1]);
+
+ if (res == FAIL) {
+ res = ITEM_COMPARE_FAIL;
+ } else {
+ res = tv_get_number_chk(&rettv, &sortinfo->item_compare_func_err);
+ }
+ if (sortinfo->item_compare_func_err) {
+ res = ITEM_COMPARE_FAIL; // return value has wrong type
+ }
+ tv_clear(&rettv);
+
+ // When the result would be zero, compare the pointers themselves. Makes
+ // the sort stable.
+ if (res == 0 && !keep_zero) {
+ // WARNING: When using uniq si1 and si2 are actually listitem_T **, no
+ // indexes are there.
+ res = si1->idx > si2->idx ? 1 : -1;
+ }
+
+ return res;
+}
+
+static int item_compare2_keeping_zero(const void *s1, const void *s2)
+{
+ return item_compare2(s1, s2, true);
+}
+
+static int item_compare2_not_keeping_zero(const void *s1, const void *s2)
+{
+ return item_compare2(s1, s2, false);
+}
+
+/*
+ * "sort({list})" function
+ */
+static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort)
+{
+ ListSortItem *ptrs;
+ long len;
+ long i;
+
+ // Pointer to current info struct used in compare function. Save and restore
+ // the current one for nested calls.
+ sortinfo_T info;
+ sortinfo_T *old_sortinfo = sortinfo;
+ sortinfo = &info;
+
+ const char *const arg_errmsg = (sort
+ ? N_("sort() argument")
+ : N_("uniq() argument"));
+
+ if (argvars[0].v_type != VAR_LIST) {
+ EMSG2(_(e_listarg), sort ? "sort()" : "uniq()");
+ } else {
+ list_T *const l = argvars[0].vval.v_list;
+ if (tv_check_lock(tv_list_locked(l), arg_errmsg, TV_TRANSLATE)) {
+ goto theend;
+ }
+ tv_list_set_ret(rettv, l);
+
+ len = tv_list_len(l);
+ if (len <= 1) {
+ goto theend; // short list sorts pretty quickly
+ }
+
+ info.item_compare_ic = false;
+ info.item_compare_numeric = false;
+ info.item_compare_numbers = false;
+ info.item_compare_float = false;
+ info.item_compare_func = NULL;
+ info.item_compare_partial = NULL;
+ info.item_compare_selfdict = NULL;
+
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ // optional second argument: {func}
+ if (argvars[1].v_type == VAR_FUNC) {
+ info.item_compare_func = (const char *)argvars[1].vval.v_string;
+ } else if (argvars[1].v_type == VAR_PARTIAL) {
+ info.item_compare_partial = argvars[1].vval.v_partial;
+ } else {
+ bool error = false;
+
+ i = tv_get_number_chk(&argvars[1], &error);
+ if (error) {
+ goto theend; // type error; errmsg already given
+ }
+ if (i == 1) {
+ info.item_compare_ic = true;
+ } else if (argvars[1].v_type != VAR_NUMBER) {
+ info.item_compare_func = tv_get_string(&argvars[1]);
+ } else if (i != 0) {
+ EMSG(_(e_invarg));
+ goto theend;
+ }
+ if (info.item_compare_func != NULL) {
+ if (*info.item_compare_func == NUL) {
+ // empty string means default sort
+ info.item_compare_func = NULL;
+ } else if (strcmp(info.item_compare_func, "n") == 0) {
+ info.item_compare_func = NULL;
+ info.item_compare_numeric = true;
+ } else if (strcmp(info.item_compare_func, "N") == 0) {
+ info.item_compare_func = NULL;
+ info.item_compare_numbers = true;
+ } else if (strcmp(info.item_compare_func, "f") == 0) {
+ info.item_compare_func = NULL;
+ info.item_compare_float = true;
+ } else if (strcmp(info.item_compare_func, "i") == 0) {
+ info.item_compare_func = NULL;
+ info.item_compare_ic = true;
+ }
+ }
+ }
+
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ // optional third argument: {dict}
+ if (argvars[2].v_type != VAR_DICT) {
+ EMSG(_(e_dictreq));
+ goto theend;
+ }
+ info.item_compare_selfdict = argvars[2].vval.v_dict;
+ }
+ }
+
+ // Make an array with each entry pointing to an item in the List.
+ ptrs = xmalloc((size_t)(len * sizeof(ListSortItem)));
+
+ if (sort) {
+ info.item_compare_func_err = false;
+ tv_list_item_sort(l, ptrs,
+ ((info.item_compare_func == NULL
+ && info.item_compare_partial == NULL)
+ ? item_compare_not_keeping_zero
+ : item_compare2_not_keeping_zero),
+ &info.item_compare_func_err);
+ if (info.item_compare_func_err) {
+ EMSG(_("E702: Sort compare function failed"));
+ }
+ } else {
+ ListSorter item_compare_func_ptr;
+
+ // f_uniq(): ptrs will be a stack of items to remove.
+ info.item_compare_func_err = false;
+ if (info.item_compare_func != NULL
+ || info.item_compare_partial != NULL) {
+ item_compare_func_ptr = item_compare2_keeping_zero;
+ } else {
+ item_compare_func_ptr = item_compare_keeping_zero;
+ }
+
+ int idx = 0;
+ for (listitem_T *li = TV_LIST_ITEM_NEXT(l, tv_list_first(l))
+ ; li != NULL;) {
+ listitem_T *const prev_li = TV_LIST_ITEM_PREV(l, li);
+ if (item_compare_func_ptr(&prev_li, &li) == 0) {
+ if (info.item_compare_func_err) { // -V547
+ EMSG(_("E882: Uniq compare function failed"));
+ break;
+ }
+ li = tv_list_item_remove(l, li);
+ } else {
+ idx++;
+ li = TV_LIST_ITEM_NEXT(l, li);
+ }
+ }
+ }
+
+ xfree(ptrs);
+ }
+
+theend:
+ sortinfo = old_sortinfo;
+}
+
+/// "sort"({list})" function
+static void f_sort(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ do_sort_uniq(argvars, rettv, true);
+}
+
+/// "stdioopen()" function
+static void f_stdioopen(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ if (argvars[0].v_type != VAR_DICT) {
+ EMSG(_(e_invarg));
+ return;
+ }
+
+
+ bool rpc = false;
+ CallbackReader on_stdin = CALLBACK_READER_INIT;
+ dict_T *opts = argvars[0].vval.v_dict;
+ rpc = tv_dict_get_number(opts, "rpc") != 0;
+
+ if (!tv_dict_get_callback(opts, S_LEN("on_stdin"), &on_stdin.cb)) {
+ return;
+ }
+ on_stdin.buffered = tv_dict_get_number(opts, "stdin_buffered");
+ if (on_stdin.buffered && on_stdin.cb.type == kCallbackNone) {
+ on_stdin.self = opts;
+ }
+
+ const char *error;
+ uint64_t id = channel_from_stdio(rpc, on_stdin, &error);
+ if (!id) {
+ EMSG2(e_stdiochan2, error);
+ }
+
+
+ rettv->vval.v_number = (varnumber_T)id;
+ rettv->v_type = VAR_NUMBER;
+}
+
+/// "uniq({list})" function
+static void f_uniq(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ do_sort_uniq(argvars, rettv, false);
+}
+
+// "reltimefloat()" function
+static void f_reltimefloat(typval_T *argvars , typval_T *rettv, FunPtr fptr)
+ FUNC_ATTR_NONNULL_ALL
+{
+ proftime_T tm;
+
+ rettv->v_type = VAR_FLOAT;
+ rettv->vval.v_float = 0;
+ if (list2proftime(&argvars[0], &tm) == OK) {
+ rettv->vval.v_float = (float_T)profile_signed(tm) / 1000000000.0;
+ }
+}
+
+/*
+ * "soundfold({word})" function
+ */
+static void f_soundfold(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->v_type = VAR_STRING;
+ const char *const s = tv_get_string(&argvars[0]);
+ rettv->vval.v_string = (char_u *)eval_soundfold(s);
+}
+
+/*
+ * "spellbadword()" function
+ */
+static void f_spellbadword(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const char *word = "";
+ hlf_T attr = HLF_COUNT;
+ size_t len = 0;
+
+ if (argvars[0].v_type == VAR_UNKNOWN) {
+ // Find the start and length of the badly spelled word.
+ len = spell_move_to(curwin, FORWARD, true, true, &attr);
+ if (len != 0) {
+ word = (char *)get_cursor_pos_ptr();
+ curwin->w_set_curswant = true;
+ }
+ } else if (curwin->w_p_spell && *curbuf->b_s.b_p_spl != NUL) {
+ const char *str = tv_get_string_chk(&argvars[0]);
+ int capcol = -1;
+
+ if (str != NULL) {
+ // Check the argument for spelling.
+ while (*str != NUL) {
+ len = spell_check(curwin, (char_u *)str, &attr, &capcol, false);
+ if (attr != HLF_COUNT) {
+ word = str;
+ break;
+ }
+ str += len;
+ capcol -= len;
+ len = 0;
+ }
+ }
+ }
+
+ assert(len <= INT_MAX);
+ tv_list_alloc_ret(rettv, 2);
+ tv_list_append_string(rettv->vval.v_list, word, len);
+ tv_list_append_string(rettv->vval.v_list,
+ (attr == HLF_SPB ? "bad"
+ : attr == HLF_SPR ? "rare"
+ : attr == HLF_SPL ? "local"
+ : attr == HLF_SPC ? "caps"
+ : NULL), -1);
+}
+
+/*
+ * "spellsuggest()" function
+ */
+static void f_spellsuggest(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ bool typeerr = false;
+ int maxcount;
+ garray_T ga = GA_EMPTY_INIT_VALUE;
+ bool need_capital = false;
+
+ if (curwin->w_p_spell && *curwin->w_s->b_p_spl != NUL) {
+ const char *const str = tv_get_string(&argvars[0]);
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ maxcount = tv_get_number_chk(&argvars[1], &typeerr);
+ if (maxcount <= 0) {
+ goto f_spellsuggest_return;
+ }
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ need_capital = tv_get_number_chk(&argvars[2], &typeerr);
+ if (typeerr) {
+ goto f_spellsuggest_return;
+ }
+ }
+ } else {
+ maxcount = 25;
+ }
+
+ spell_suggest_list(&ga, (char_u *)str, maxcount, need_capital, false);
+ }
+
+f_spellsuggest_return:
+ tv_list_alloc_ret(rettv, (ptrdiff_t)ga.ga_len);
+ for (int i = 0; i < ga.ga_len; i++) {
+ char *const p = ((char **)ga.ga_data)[i];
+ tv_list_append_allocated_string(rettv->vval.v_list, p);
+ }
+ ga_clear(&ga);
+}
+
+static void f_split(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ char_u *save_cpo;
+ int match;
+ colnr_T col = 0;
+ bool keepempty = false;
+ bool typeerr = false;
+
+ // Make 'cpoptions' empty, the 'l' flag should not be used here.
+ save_cpo = p_cpo;
+ p_cpo = (char_u *)"";
+
+ const char *str = tv_get_string(&argvars[0]);
+ const char *pat = NULL;
+ char patbuf[NUMBUFLEN];
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ pat = tv_get_string_buf_chk(&argvars[1], patbuf);
+ if (pat == NULL) {
+ typeerr = true;
+ }
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ keepempty = (bool)tv_get_number_chk(&argvars[2], &typeerr);
+ }
+ }
+ if (pat == NULL || *pat == NUL) {
+ pat = "[\\x01- ]\\+";
+ }
+
+ tv_list_alloc_ret(rettv, kListLenMayKnow);
+
+ if (typeerr) {
+ goto theend;
+ }
+
+ regmatch_T regmatch = {
+ .regprog = vim_regcomp((char_u *)pat, RE_MAGIC + RE_STRING),
+ .startp = { NULL },
+ .endp = { NULL },
+ .rm_ic = false,
+ };
+ if (regmatch.regprog != NULL) {
+ while (*str != NUL || keepempty) {
+ if (*str == NUL) {
+ match = false; // Empty item at the end.
+ } else {
+ match = vim_regexec_nl(&regmatch, (char_u *)str, col);
+ }
+ const char *end;
+ if (match) {
+ end = (const char *)regmatch.startp[0];
+ } else {
+ end = str + strlen(str);
+ }
+ if (keepempty || end > str || (tv_list_len(rettv->vval.v_list) > 0
+ && *str != NUL
+ && match
+ && end < (const char *)regmatch.endp[0])) {
+ tv_list_append_string(rettv->vval.v_list, str, end - str);
+ }
+ if (!match) {
+ break;
+ }
+ // Advance to just after the match.
+ if (regmatch.endp[0] > (char_u *)str) {
+ col = 0;
+ } else {
+ // Don't get stuck at the same match.
+ col = (*mb_ptr2len)(regmatch.endp[0]);
+ }
+ str = (const char *)regmatch.endp[0];
+ }
+
+ vim_regfree(regmatch.regprog);
+ }
+
+theend:
+ p_cpo = save_cpo;
+}
+
+/// "stdpath(type)" function
+static void f_stdpath(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = NULL;
+
+ const char *const p = tv_get_string_chk(&argvars[0]);
+ if (p == NULL) {
+ return; // Type error; errmsg already given.
+ }
+
+ if (strequal(p, "config")) {
+ rettv->vval.v_string = (char_u *)get_xdg_home(kXDGConfigHome);
+ } else if (strequal(p, "data")) {
+ rettv->vval.v_string = (char_u *)get_xdg_home(kXDGDataHome);
+ } else if (strequal(p, "cache")) {
+ rettv->vval.v_string = (char_u *)get_xdg_home(kXDGCacheHome);
+ } else if (strequal(p, "config_dirs")) {
+ get_xdg_var_list(kXDGConfigDirs, rettv);
+ } else if (strequal(p, "data_dirs")) {
+ get_xdg_var_list(kXDGDataDirs, rettv);
+ } else {
+ EMSG2(_("E6100: \"%s\" is not a valid stdpath"), p);
+ }
+}
+
+/*
+ * "str2float()" function
+ */
+static void f_str2float(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ char_u *p = skipwhite((const char_u *)tv_get_string(&argvars[0]));
+ bool isneg = (*p == '-');
+
+ if (*p == '+' || *p == '-') {
+ p = skipwhite(p + 1);
+ }
+ (void)string2float((char *)p, &rettv->vval.v_float);
+ if (isneg) {
+ rettv->vval.v_float *= -1;
+ }
+ rettv->v_type = VAR_FLOAT;
+}
+
+// "str2list()" function
+static void f_str2list(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ tv_list_alloc_ret(rettv, kListLenUnknown);
+ const char_u *p = (const char_u *)tv_get_string(&argvars[0]);
+
+ for (; *p != NUL; p += utf_ptr2len(p)) {
+ tv_list_append_number(rettv->vval.v_list, utf_ptr2char(p));
+ }
+}
+
+// "str2nr()" function
+static void f_str2nr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ int base = 10;
+ varnumber_T n;
+ int what;
+
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ base = tv_get_number(&argvars[1]);
+ if (base != 2 && base != 8 && base != 10 && base != 16) {
+ EMSG(_(e_invarg));
+ return;
+ }
+ }
+
+ char_u *p = skipwhite((const char_u *)tv_get_string(&argvars[0]));
+ bool isneg = (*p == '-');
+ if (*p == '+' || *p == '-') {
+ p = skipwhite(p + 1);
+ }
+ switch (base) {
+ case 2: {
+ what = STR2NR_BIN | STR2NR_FORCE;
+ break;
+ }
+ case 8: {
+ what = STR2NR_OCT | STR2NR_FORCE;
+ break;
+ }
+ case 16: {
+ what = STR2NR_HEX | STR2NR_FORCE;
+ break;
+ }
+ default: {
+ what = 0;
+ }
+ }
+ vim_str2nr(p, NULL, NULL, what, &n, NULL, 0);
+ if (isneg) {
+ rettv->vval.v_number = -n;
+ } else {
+ rettv->vval.v_number = n;
+ }
+}
+
+/*
+ * "strftime({format}[, {time}])" function
+ */
+static void f_strftime(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ time_t seconds;
+
+ rettv->v_type = VAR_STRING;
+
+ char *p = (char *)tv_get_string(&argvars[0]);
+ if (argvars[1].v_type == VAR_UNKNOWN) {
+ seconds = time(NULL);
+ } else {
+ seconds = (time_t)tv_get_number(&argvars[1]);
+ }
+
+ struct tm curtime;
+ struct tm *curtime_ptr = os_localtime_r(&seconds, &curtime);
+ // MSVC returns NULL for an invalid value of seconds.
+ if (curtime_ptr == NULL) {
+ rettv->vval.v_string = vim_strsave((char_u *)_("(Invalid)"));
+ } else {
+ vimconv_T conv;
+ char_u *enc;
+
+ conv.vc_type = CONV_NONE;
+ enc = enc_locale();
+ convert_setup(&conv, p_enc, enc);
+ if (conv.vc_type != CONV_NONE) {
+ p = (char *)string_convert(&conv, (char_u *)p, NULL);
+ }
+ char result_buf[256];
+ if (p != NULL) {
+ (void)strftime(result_buf, sizeof(result_buf), p, curtime_ptr);
+ } else {
+ result_buf[0] = NUL;
+ }
+
+ if (conv.vc_type != CONV_NONE) {
+ xfree(p);
+ }
+ convert_setup(&conv, enc, p_enc);
+ if (conv.vc_type != CONV_NONE) {
+ rettv->vval.v_string = string_convert(&conv, (char_u *)result_buf, NULL);
+ } else {
+ rettv->vval.v_string = (char_u *)xstrdup(result_buf);
+ }
+
+ // Release conversion descriptors.
+ convert_setup(&conv, NULL, NULL);
+ xfree(enc);
+ }
+}
+
+// "strgetchar()" function
+static void f_strgetchar(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = -1;
+
+ const char *const str = tv_get_string_chk(&argvars[0]);
+ if (str == NULL) {
+ return;
+ }
+ bool error = false;
+ varnumber_T charidx = tv_get_number_chk(&argvars[1], &error);
+ if (error) {
+ return;
+ }
+
+ const size_t len = STRLEN(str);
+ size_t byteidx = 0;
+
+ while (charidx >= 0 && byteidx < len) {
+ if (charidx == 0) {
+ rettv->vval.v_number = utf_ptr2char((const char_u *)str + byteidx);
+ break;
+ }
+ charidx--;
+ byteidx += MB_CPTR2LEN((const char_u *)str + byteidx);
+ }
+}
+
+/*
+ * "stridx()" function
+ */
+static void f_stridx(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = -1;
+
+ char buf[NUMBUFLEN];
+ const char *const needle = tv_get_string_chk(&argvars[1]);
+ const char *haystack = tv_get_string_buf_chk(&argvars[0], buf);
+ const char *const haystack_start = haystack;
+ if (needle == NULL || haystack == NULL) {
+ return; // Type error; errmsg already given.
+ }
+
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ bool error = false;
+
+ const ptrdiff_t start_idx = (ptrdiff_t)tv_get_number_chk(&argvars[2],
+ &error);
+ if (error || start_idx >= (ptrdiff_t)strlen(haystack)) {
+ return;
+ }
+ if (start_idx >= 0) {
+ haystack += start_idx;
+ }
+ }
+
+ const char *pos = strstr(haystack, needle);
+ if (pos != NULL) {
+ rettv->vval.v_number = (varnumber_T)(pos - haystack_start);
+ }
+}
+
+/*
+ * "string()" function
+ */
+void f_string(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = (char_u *)encode_tv2string(&argvars[0], NULL);
+}
+
+/*
+ * "strlen()" function
+ */
+static void f_strlen(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = (varnumber_T)strlen(tv_get_string(&argvars[0]));
+}
+
+/*
+ * "strchars()" function
+ */
+static void f_strchars(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const char *s = tv_get_string(&argvars[0]);
+ int skipcc = 0;
+ varnumber_T len = 0;
+ int (*func_mb_ptr2char_adv)(const char_u **pp);
+
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ skipcc = tv_get_number_chk(&argvars[1], NULL);
+ }
+ if (skipcc < 0 || skipcc > 1) {
+ EMSG(_(e_invarg));
+ } else {
+ func_mb_ptr2char_adv = skipcc ? mb_ptr2char_adv : mb_cptr2char_adv;
+ while (*s != NUL) {
+ func_mb_ptr2char_adv((const char_u **)&s);
+ len++;
+ }
+ rettv->vval.v_number = len;
+ }
+}
+
+/*
+ * "strdisplaywidth()" function
+ */
+static void f_strdisplaywidth(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const char *const s = tv_get_string(&argvars[0]);
+ int col = 0;
+
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ col = tv_get_number(&argvars[1]);
+ }
+
+ rettv->vval.v_number = (varnumber_T)(linetabsize_col(col, (char_u *)s) - col);
+}
+
+/*
+ * "strwidth()" function
+ */
+static void f_strwidth(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const char *const s = tv_get_string(&argvars[0]);
+
+ rettv->vval.v_number = (varnumber_T)mb_string2cells((const char_u *)s);
+}
+
+// "strcharpart()" function
+static void f_strcharpart(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const char *const p = tv_get_string(&argvars[0]);
+ const size_t slen = STRLEN(p);
+
+ int nbyte = 0;
+ bool error = false;
+ varnumber_T nchar = tv_get_number_chk(&argvars[1], &error);
+ if (!error) {
+ if (nchar > 0) {
+ while (nchar > 0 && (size_t)nbyte < slen) {
+ nbyte += MB_CPTR2LEN((const char_u *)p + nbyte);
+ nchar--;
+ }
+ } else {
+ nbyte = nchar;
+ }
+ }
+ int len = 0;
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ int charlen = tv_get_number(&argvars[2]);
+ while (charlen > 0 && nbyte + len < (int)slen) {
+ int off = nbyte + len;
+
+ if (off < 0) {
+ len += 1;
+ } else {
+ len += (size_t)MB_CPTR2LEN((const char_u *)p + off);
+ }
+ charlen--;
+ }
+ } else {
+ len = slen - nbyte; // default: all bytes that are available.
+ }
+
+ // Only return the overlap between the specified part and the actual
+ // string.
+ if (nbyte < 0) {
+ len += nbyte;
+ nbyte = 0;
+ } else if ((size_t)nbyte > slen) {
+ nbyte = slen;
+ }
+ if (len < 0) {
+ len = 0;
+ } else if (nbyte + len > (int)slen) {
+ len = slen - nbyte;
+ }
+
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = (char_u *)xstrndup(p + nbyte, (size_t)len);
+}
+
+/*
+ * "strpart()" function
+ */
+static void f_strpart(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ bool error = false;
+
+ const char *const p = tv_get_string(&argvars[0]);
+ const size_t slen = strlen(p);
+
+ varnumber_T n = tv_get_number_chk(&argvars[1], &error);
+ varnumber_T len;
+ if (error) {
+ len = 0;
+ } else if (argvars[2].v_type != VAR_UNKNOWN) {
+ len = tv_get_number(&argvars[2]);
+ } else {
+ len = slen - n; // Default len: all bytes that are available.
+ }
+
+ // Only return the overlap between the specified part and the actual
+ // string.
+ if (n < 0) {
+ len += n;
+ n = 0;
+ } else if (n > (varnumber_T)slen) {
+ n = slen;
+ }
+ if (len < 0) {
+ len = 0;
+ } else if (n + len > (varnumber_T)slen) {
+ len = slen - n;
+ }
+
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = (char_u *)xmemdupz(p + n, (size_t)len);
+}
+
+/*
+ * "strridx()" function
+ */
+static void f_strridx(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ char buf[NUMBUFLEN];
+ const char *const needle = tv_get_string_chk(&argvars[1]);
+ const char *const haystack = tv_get_string_buf_chk(&argvars[0], buf);
+
+ rettv->vval.v_number = -1;
+ if (needle == NULL || haystack == NULL) {
+ return; // Type error; errmsg already given.
+ }
+
+ const size_t haystack_len = STRLEN(haystack);
+ ptrdiff_t end_idx;
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ // Third argument: upper limit for index.
+ end_idx = (ptrdiff_t)tv_get_number_chk(&argvars[2], NULL);
+ if (end_idx < 0) {
+ return; // Can never find a match.
+ }
+ } else {
+ end_idx = (ptrdiff_t)haystack_len;
+ }
+
+ const char *lastmatch = NULL;
+ if (*needle == NUL) {
+ // Empty string matches past the end.
+ lastmatch = haystack + end_idx;
+ } else {
+ for (const char *rest = haystack; *rest != NUL; rest++) {
+ rest = strstr(rest, needle);
+ if (rest == NULL || rest > haystack + end_idx) {
+ break;
+ }
+ lastmatch = rest;
+ }
+ }
+
+ if (lastmatch != NULL) {
+ rettv->vval.v_number = (varnumber_T)(lastmatch - haystack);
+ }
+}
+
+/*
+ * "strtrans()" function
+ */
+static void f_strtrans(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = (char_u *)transstr(tv_get_string(&argvars[0]));
+}
+
+/*
+ * "submatch()" function
+ */
+static void f_submatch(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ bool error = false;
+ int no = (int)tv_get_number_chk(&argvars[0], &error);
+ if (error) {
+ return;
+ }
+
+ if (no < 0 || no >= NSUBEXP) {
+ emsgf(_("E935: invalid submatch number: %d"), no);
+ return;
+ }
+ int retList = 0;
+
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ retList = tv_get_number_chk(&argvars[1], &error);
+ if (error) {
+ return;
+ }
+ }
+
+ if (retList == 0) {
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = reg_submatch(no);
+ } else {
+ rettv->v_type = VAR_LIST;
+ rettv->vval.v_list = reg_submatch_list(no);
+ }
+}
+
+/*
+ * "substitute()" function
+ */
+static void f_substitute(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ char patbuf[NUMBUFLEN];
+ char subbuf[NUMBUFLEN];
+ char flagsbuf[NUMBUFLEN];
+
+ const char *const str = tv_get_string_chk(&argvars[0]);
+ const char *const pat = tv_get_string_buf_chk(&argvars[1], patbuf);
+ const char *sub = NULL;
+ const char *const flg = tv_get_string_buf_chk(&argvars[3], flagsbuf);
+
+ typval_T *expr = NULL;
+ if (tv_is_func(argvars[2])) {
+ expr = &argvars[2];
+ } else {
+ sub = tv_get_string_buf_chk(&argvars[2], subbuf);
+ }
+
+ rettv->v_type = VAR_STRING;
+ if (str == NULL || pat == NULL || (sub == NULL && expr == NULL)
+ || flg == NULL) {
+ rettv->vval.v_string = NULL;
+ } else {
+ rettv->vval.v_string = do_string_sub((char_u *)str, (char_u *)pat,
+ (char_u *)sub, expr, (char_u *)flg);
+ }
+}
+
+/// "swapinfo(swap_filename)" function
+static void f_swapinfo(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ tv_dict_alloc_ret(rettv);
+ get_b0_dict(tv_get_string(argvars), rettv->vval.v_dict);
+}
+
+/// "swapname(expr)" function
+static void f_swapname(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->v_type = VAR_STRING;
+ buf_T *buf = tv_get_buf(&argvars[0], false);
+ if (buf == NULL
+ || buf->b_ml.ml_mfp == NULL
+ || buf->b_ml.ml_mfp->mf_fname == NULL) {
+ rettv->vval.v_string = NULL;
+ } else {
+ rettv->vval.v_string = vim_strsave(buf->b_ml.ml_mfp->mf_fname);
+ }
+}
+
+/// "synID(lnum, col, trans)" function
+static void f_synID(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ // -1 on type error (both)
+ const linenr_T lnum = tv_get_lnum(argvars);
+ const colnr_T col = (colnr_T)tv_get_number(&argvars[1]) - 1;
+
+ bool transerr = false;
+ const int trans = tv_get_number_chk(&argvars[2], &transerr);
+
+ int id = 0;
+ if (!transerr && lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count
+ && col >= 0 && (size_t)col < STRLEN(ml_get(lnum))) {
+ id = syn_get_id(curwin, lnum, col, trans, NULL, false);
+ }
+
+ rettv->vval.v_number = id;
+}
+
+/*
+ * "synIDattr(id, what [, mode])" function
+ */
+static void f_synIDattr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const int id = (int)tv_get_number(&argvars[0]);
+ const char *const what = tv_get_string(&argvars[1]);
+ int modec;
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ char modebuf[NUMBUFLEN];
+ const char *const mode = tv_get_string_buf(&argvars[2], modebuf);
+ modec = TOLOWER_ASC(mode[0]);
+ if (modec != 'c' && modec != 'g') {
+ modec = 0; // Replace invalid with current.
+ }
+ } else if (ui_rgb_attached()) {
+ modec = 'g';
+ } else {
+ modec = 'c';
+ }
+
+
+ const char *p = NULL;
+ switch (TOLOWER_ASC(what[0])) {
+ case 'b': {
+ if (TOLOWER_ASC(what[1]) == 'g') { // bg[#]
+ p = highlight_color(id, what, modec);
+ } else { // bold
+ p = highlight_has_attr(id, HL_BOLD, modec);
+ }
+ break;
+ }
+ case 'f': { // fg[#] or font
+ p = highlight_color(id, what, modec);
+ break;
+ }
+ case 'i': {
+ if (TOLOWER_ASC(what[1]) == 'n') { // inverse
+ p = highlight_has_attr(id, HL_INVERSE, modec);
+ } else { // italic
+ p = highlight_has_attr(id, HL_ITALIC, modec);
+ }
+ break;
+ }
+ case 'n': { // name
+ p = get_highlight_name_ext(NULL, id - 1, false);
+ break;
+ }
+ case 'r': { // reverse
+ p = highlight_has_attr(id, HL_INVERSE, modec);
+ break;
+ }
+ case 's': {
+ if (TOLOWER_ASC(what[1]) == 'p') { // sp[#]
+ p = highlight_color(id, what, modec);
+ } else if (TOLOWER_ASC(what[1]) == 't'
+ && TOLOWER_ASC(what[2]) == 'r') { // strikethrough
+ p = highlight_has_attr(id, HL_STRIKETHROUGH, modec);
+ } else { // standout
+ p = highlight_has_attr(id, HL_STANDOUT, modec);
+ }
+ break;
+ }
+ case 'u': {
+ if (STRLEN(what) <= 5 || TOLOWER_ASC(what[5]) != 'c') { // underline
+ p = highlight_has_attr(id, HL_UNDERLINE, modec);
+ } else { // undercurl
+ p = highlight_has_attr(id, HL_UNDERCURL, modec);
+ }
+ break;
+ }
+ }
+
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = (char_u *)(p == NULL ? p : xstrdup(p));
+}
+
+/*
+ * "synIDtrans(id)" function
+ */
+static void f_synIDtrans(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ int id = tv_get_number(&argvars[0]);
+
+ if (id > 0) {
+ id = syn_get_final_id(id);
+ } else {
+ id = 0;
+ }
+
+ rettv->vval.v_number = id;
+}
+
+/*
+ * "synconcealed(lnum, col)" function
+ */
+static void f_synconcealed(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ int syntax_flags = 0;
+ int cchar;
+ int matchid = 0;
+ char_u str[NUMBUFLEN];
+
+ tv_list_set_ret(rettv, NULL);
+
+ // -1 on type error (both)
+ const linenr_T lnum = tv_get_lnum(argvars);
+ const colnr_T col = (colnr_T)tv_get_number(&argvars[1]) - 1;
+
+ memset(str, NUL, sizeof(str));
+
+ if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count && col >= 0
+ && (size_t)col <= STRLEN(ml_get(lnum)) && curwin->w_p_cole > 0) {
+ (void)syn_get_id(curwin, lnum, col, false, NULL, false);
+ syntax_flags = get_syntax_info(&matchid);
+
+ // get the conceal character
+ if ((syntax_flags & HL_CONCEAL) && curwin->w_p_cole < 3) {
+ cchar = syn_get_sub_char();
+ if (cchar == NUL && curwin->w_p_cole == 1) {
+ cchar = (curwin->w_p_lcs_chars.conceal == NUL)
+ ? ' '
+ : curwin->w_p_lcs_chars.conceal;
+ }
+ if (cchar != NUL) {
+ utf_char2bytes(cchar, str);
+ }
+ }
+ }
+
+ tv_list_alloc_ret(rettv, 3);
+ tv_list_append_number(rettv->vval.v_list, (syntax_flags & HL_CONCEAL) != 0);
+ // -1 to auto-determine strlen
+ tv_list_append_string(rettv->vval.v_list, (const char *)str, -1);
+ tv_list_append_number(rettv->vval.v_list, matchid);
+}
+
+/*
+ * "synstack(lnum, col)" function
+ */
+static void f_synstack(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ tv_list_set_ret(rettv, NULL);
+
+ // -1 on type error (both)
+ const linenr_T lnum = tv_get_lnum(argvars);
+ const colnr_T col = (colnr_T)tv_get_number(&argvars[1]) - 1;
+
+ if (lnum >= 1
+ && lnum <= curbuf->b_ml.ml_line_count
+ && col >= 0
+ && (size_t)col <= STRLEN(ml_get(lnum))) {
+ tv_list_alloc_ret(rettv, kListLenMayKnow);
+ (void)syn_get_id(curwin, lnum, col, false, NULL, true);
+
+ int id;
+ int i = 0;
+ while ((id = syn_get_stack_item(i++)) >= 0) {
+ tv_list_append_number(rettv->vval.v_list, id);
+ }
+ }
+}
+
+/// f_system - the VimL system() function
+static void f_system(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ get_system_output_as_rettv(argvars, rettv, false);
+}
+
+static void f_systemlist(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ get_system_output_as_rettv(argvars, rettv, true);
+}
+
+
+/*
+ * "tabpagebuflist()" function
+ */
+static void f_tabpagebuflist(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ win_T *wp = NULL;
+
+ if (argvars[0].v_type == VAR_UNKNOWN) {
+ wp = firstwin;
+ } else {
+ tabpage_T *const tp = find_tabpage((int)tv_get_number(&argvars[0]));
+ if (tp != NULL) {
+ wp = (tp == curtab) ? firstwin : tp->tp_firstwin;
+ }
+ }
+ if (wp != NULL) {
+ tv_list_alloc_ret(rettv, kListLenMayKnow);
+ while (wp != NULL) {
+ tv_list_append_number(rettv->vval.v_list, wp->w_buffer->b_fnum);
+ wp = wp->w_next;
+ }
+ }
+}
+
+/*
+ * "tabpagenr()" function
+ */
+static void f_tabpagenr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ int nr = 1;
+
+ if (argvars[0].v_type != VAR_UNKNOWN) {
+ const char *const arg = tv_get_string_chk(&argvars[0]);
+ nr = 0;
+ if (arg != NULL) {
+ if (strcmp(arg, "$") == 0) {
+ nr = tabpage_index(NULL) - 1;
+ } else if (strcmp(arg, "#") == 0) {
+ nr = valid_tabpage(lastused_tabpage)
+ ? tabpage_index(lastused_tabpage)
+ : nr;
+ } else {
+ EMSG2(_(e_invexpr2), arg);
+ }
+ }
+ } else {
+ nr = tabpage_index(curtab);
+ }
+ rettv->vval.v_number = nr;
+}
+
+
+
+/*
+ * Common code for tabpagewinnr() and winnr().
+ */
+static int get_winnr(tabpage_T *tp, typval_T *argvar)
+{
+ win_T *twin;
+ int nr = 1;
+ win_T *wp;
+
+ twin = (tp == curtab) ? curwin : tp->tp_curwin;
+ if (argvar->v_type != VAR_UNKNOWN) {
+ bool invalid_arg = false;
+ const char *const arg = tv_get_string_chk(argvar);
+ if (arg == NULL) {
+ nr = 0; // Type error; errmsg already given.
+ } else if (strcmp(arg, "$") == 0) {
+ twin = (tp == curtab) ? lastwin : tp->tp_lastwin;
+ } else if (strcmp(arg, "#") == 0) {
+ twin = (tp == curtab) ? prevwin : tp->tp_prevwin;
+ if (twin == NULL) {
+ nr = 0;
+ }
+ } else {
+ // Extract the window count (if specified). e.g. winnr('3j')
+ char_u *endp;
+ long count = strtol((char *)arg, (char **)&endp, 10);
+ if (count <= 0) {
+ // if count is not specified, default to 1
+ count = 1;
+ }
+ if (endp != NULL && *endp != '\0') {
+ if (strequal((char *)endp, "j")) {
+ twin = win_vert_neighbor(tp, twin, false, count);
+ } else if (strequal((char *)endp, "k")) {
+ twin = win_vert_neighbor(tp, twin, true, count);
+ } else if (strequal((char *)endp, "h")) {
+ twin = win_horz_neighbor(tp, twin, true, count);
+ } else if (strequal((char *)endp, "l")) {
+ twin = win_horz_neighbor(tp, twin, false, count);
+ } else {
+ invalid_arg = true;
+ }
+ } else {
+ invalid_arg = true;
+ }
+ }
+
+ if (invalid_arg) {
+ EMSG2(_(e_invexpr2), arg);
+ nr = 0;
+ }
+ }
+
+ if (nr > 0)
+ for (wp = (tp == curtab) ? firstwin : tp->tp_firstwin;
+ wp != twin; wp = wp->w_next) {
+ if (wp == NULL) {
+ // didn't find it in this tabpage
+ nr = 0;
+ break;
+ }
+ ++nr;
+ }
+ return nr;
+}
+
+/*
+ * "tabpagewinnr()" function
+ */
+static void f_tabpagewinnr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ int nr = 1;
+ tabpage_T *const tp = find_tabpage((int)tv_get_number(&argvars[0]));
+ if (tp == NULL) {
+ nr = 0;
+ } else {
+ nr = get_winnr(tp, &argvars[1]);
+ }
+ rettv->vval.v_number = nr;
+}
+
+/*
+ * "tagfiles()" function
+ */
+static void f_tagfiles(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ char *fname;
+ tagname_T tn;
+
+ tv_list_alloc_ret(rettv, kListLenUnknown);
+ fname = xmalloc(MAXPATHL);
+
+ bool first = true;
+ while (get_tagfname(&tn, first, (char_u *)fname) == OK) {
+ tv_list_append_string(rettv->vval.v_list, fname, -1);
+ first = false;
+ }
+
+ tagname_free(&tn);
+ xfree(fname);
+}
+
+/*
+ * "taglist()" function
+ */
+static void f_taglist(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const char *const tag_pattern = tv_get_string(&argvars[0]);
+
+ rettv->vval.v_number = false;
+ if (*tag_pattern == NUL) {
+ return;
+ }
+
+ const char *fname = NULL;
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ fname = tv_get_string(&argvars[1]);
+ }
+ (void)get_tags(tv_list_alloc_ret(rettv, kListLenUnknown),
+ (char_u *)tag_pattern, (char_u *)fname);
+}
+
+/*
+ * "tempname()" function
+ */
+static void f_tempname(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = vim_tempname();
+}
+
+// "termopen(cmd[, cwd])" function
+static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ if (check_restricted() || check_secure()) {
+ return;
+ }
+
+ if (curbuf->b_changed) {
+ EMSG(_("Can only call this function in an unmodified buffer"));
+ return;
+ }
+
+ const char *cmd;
+ bool executable = true;
+ char **argv = tv_to_argv(&argvars[0], &cmd, &executable);
+ if (!argv) {
+ rettv->vval.v_number = executable ? 0 : -1;
+ return; // Did error message in tv_to_argv.
+ }
+
+ if (argvars[1].v_type != VAR_DICT && argvars[1].v_type != VAR_UNKNOWN) {
+ // Wrong argument type
+ EMSG2(_(e_invarg2), "expected dictionary");
+ shell_free_argv(argv);
+ return;
+ }
+
+ CallbackReader on_stdout = CALLBACK_READER_INIT,
+ on_stderr = CALLBACK_READER_INIT;
+ Callback on_exit = CALLBACK_NONE;
+ dict_T *job_opts = NULL;
+ const char *cwd = ".";
+ if (argvars[1].v_type == VAR_DICT) {
+ job_opts = argvars[1].vval.v_dict;
+
+ const char *const new_cwd = tv_dict_get_string(job_opts, "cwd", false);
+ if (new_cwd && *new_cwd != NUL) {
+ cwd = new_cwd;
+ // The new cwd must be a directory.
+ if (!os_isdir_executable((const char *)cwd)) {
+ EMSG2(_(e_invarg2), "expected valid directory");
+ shell_free_argv(argv);
+ return;
+ }
+ }
+
+ if (!common_job_callbacks(job_opts, &on_stdout, &on_stderr, &on_exit)) {
+ shell_free_argv(argv);
+ return;
+ }
+ }
+
+ uint16_t term_width = MAX(0, curwin->w_width_inner - win_col_off(curwin));
+ Channel *chan = channel_job_start(argv, on_stdout, on_stderr, on_exit,
+ true, false, false, false, cwd,
+ term_width, curwin->w_height_inner,
+ xstrdup("xterm-256color"), NULL,
+ &rettv->vval.v_number);
+ if (rettv->vval.v_number <= 0) {
+ return;
+ }
+
+ int pid = chan->stream.pty.process.pid;
+
+ // "./…" => "/home/foo/…"
+ vim_FullName(cwd, (char *)NameBuff, sizeof(NameBuff), false);
+ // "/home/foo/…" => "~/…"
+ size_t len = home_replace(NULL, NameBuff, IObuff, sizeof(IObuff), true);
+ // Trim slash.
+ if (IObuff[len - 1] == '\\' || IObuff[len - 1] == '/') {
+ IObuff[len - 1] = '\0';
+ }
+
+ // Terminal URI: "term://$CWD//$PID:$CMD"
+ snprintf((char *)NameBuff, sizeof(NameBuff), "term://%s//%d:%s",
+ (char *)IObuff, pid, cmd);
+ // at this point the buffer has no terminal instance associated yet, so unset
+ // the 'swapfile' option to ensure no swap file will be created
+ curbuf->b_p_swf = false;
+ (void)setfname(curbuf, NameBuff, NULL, true);
+ // Save the job id and pid in b:terminal_job_{id,pid}
+ Error err = ERROR_INIT;
+ // deprecated: use 'channel' buffer option
+ dict_set_var(curbuf->b_vars, cstr_as_string("terminal_job_id"),
+ INTEGER_OBJ(chan->id), false, false, &err);
+ api_clear_error(&err);
+ dict_set_var(curbuf->b_vars, cstr_as_string("terminal_job_pid"),
+ INTEGER_OBJ(pid), false, false, &err);
+ api_clear_error(&err);
+
+ channel_terminal_open(chan);
+ channel_create_event(chan, NULL);
+}
+
+// "test_garbagecollect_now()" function
+static void f_test_garbagecollect_now(typval_T *argvars,
+ typval_T *rettv, FunPtr fptr)
+{
+ // This is dangerous, any Lists and Dicts used internally may be freed
+ // while still in use.
+ garbage_collect(true);
+}
+
+// "test_write_list_log()" function
+static void f_test_write_list_log(typval_T *const argvars,
+ typval_T *const rettv,
+ FunPtr fptr)
+{
+ const char *const fname = tv_get_string_chk(&argvars[0]);
+ if (fname == NULL) {
+ return;
+ }
+ list_write_log(fname);
+}
+
+/// "timer_info([timer])" function
+static void f_timer_info(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ if (argvars[0].v_type != VAR_UNKNOWN) {
+ if (argvars[0].v_type != VAR_NUMBER) {
+ EMSG(_(e_number_exp));
+ return;
+ }
+ tv_list_alloc_ret(rettv, 1);
+ timer_T *timer = find_timer_by_nr(tv_get_number(&argvars[0]));
+ if (timer != NULL && !timer->stopped) {
+ add_timer_info(rettv, timer);
+ }
+ } else {
+ add_timer_info_all(rettv);
+ }
+}
+
+/// "timer_pause(timer, paused)" function
+static void f_timer_pause(typval_T *argvars, typval_T *unused, FunPtr fptr)
+{
+ if (argvars[0].v_type != VAR_NUMBER) {
+ EMSG(_(e_number_exp));
+ return;
+ }
+ int paused = (bool)tv_get_number(&argvars[1]);
+ timer_T *timer = find_timer_by_nr(tv_get_number(&argvars[0]));
+ if (timer != NULL) {
+ if (!timer->paused && paused) {
+ time_watcher_stop(&timer->tw);
+ } else if (timer->paused && !paused) {
+ time_watcher_start(&timer->tw, timer_due_cb, timer->timeout,
+ timer->timeout);
+ }
+ timer->paused = paused;
+ }
+}
+
+/// "timer_start(timeout, callback, opts)" function
+static void f_timer_start(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ int repeat = 1;
+ dict_T *dict;
+
+ rettv->vval.v_number = -1;
+
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ if (argvars[2].v_type != VAR_DICT
+ || (dict = argvars[2].vval.v_dict) == NULL) {
+ EMSG2(_(e_invarg2), tv_get_string(&argvars[2]));
+ return;
+ }
+ dictitem_T *const di = tv_dict_find(dict, S_LEN("repeat"));
+ if (di != NULL) {
+ repeat = tv_get_number(&di->di_tv);
+ if (repeat == 0) {
+ repeat = 1;
+ }
+ }
+ }
+
+ Callback callback;
+ if (!callback_from_typval(&callback, &argvars[1])) {
+ return;
+ }
+ rettv->vval.v_number =
+ timer_start(tv_get_number(&argvars[0]), repeat, &callback);
+}
+
+
+// "timer_stop(timerid)" function
+static void f_timer_stop(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ if (argvars[0].v_type != VAR_NUMBER) {
+ EMSG(_(e_number_exp));
+ return;
+ }
+
+ timer_T *timer = find_timer_by_nr(tv_get_number(&argvars[0]));
+ if (timer == NULL) {
+ return;
+ }
+
+ timer_stop(timer);
+}
+
+static void f_timer_stopall(typval_T *argvars, typval_T *unused, FunPtr fptr)
+{
+ timer_stop_all();
+}
+
+/*
+ * "tolower(string)" function
+ */
+static void f_tolower(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = (char_u *)strcase_save(tv_get_string(&argvars[0]),
+ false);
+}
+
+/*
+ * "toupper(string)" function
+ */
+static void f_toupper(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = (char_u *)strcase_save(tv_get_string(&argvars[0]),
+ true);
+}
+
+/*
+ * "tr(string, fromstr, tostr)" function
+ */
+static void f_tr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ char buf[NUMBUFLEN];
+ char buf2[NUMBUFLEN];
+
+ const char *in_str = tv_get_string(&argvars[0]);
+ const char *fromstr = tv_get_string_buf_chk(&argvars[1], buf);
+ const char *tostr = tv_get_string_buf_chk(&argvars[2], buf2);
+
+ // Default return value: empty string.
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = NULL;
+ if (fromstr == NULL || tostr == NULL) {
+ return; // Type error; errmsg already given.
+ }
+ garray_T ga;
+ ga_init(&ga, (int)sizeof(char), 80);
+
+ if (!has_mbyte) {
+ // Not multi-byte: fromstr and tostr must be the same length.
+ if (strlen(fromstr) != strlen(tostr)) {
+ goto error;
+ }
+ }
+
+ // fromstr and tostr have to contain the same number of chars.
+ bool first = true;
+ while (*in_str != NUL) {
+ if (has_mbyte) {
+ const char *cpstr = in_str;
+ const int inlen = (*mb_ptr2len)((const char_u *)in_str);
+ int cplen = inlen;
+ int idx = 0;
+ int fromlen;
+ for (const char *p = fromstr; *p != NUL; p += fromlen) {
+ fromlen = (*mb_ptr2len)((const char_u *)p);
+ if (fromlen == inlen && STRNCMP(in_str, p, inlen) == 0) {
+ int tolen;
+ for (p = tostr; *p != NUL; p += tolen) {
+ tolen = (*mb_ptr2len)((const char_u *)p);
+ if (idx-- == 0) {
+ cplen = tolen;
+ cpstr = (char *)p;
+ break;
+ }
+ }
+ if (*p == NUL) { // tostr is shorter than fromstr.
+ goto error;
+ }
+ break;
+ }
+ idx++;
+ }
+
+ if (first && cpstr == in_str) {
+ // Check that fromstr and tostr have the same number of
+ // (multi-byte) characters. Done only once when a character
+ // of in_str doesn't appear in fromstr.
+ first = false;
+ int tolen;
+ for (const char *p = tostr; *p != NUL; p += tolen) {
+ tolen = (*mb_ptr2len)((const char_u *)p);
+ idx--;
+ }
+ if (idx != 0) {
+ goto error;
+ }
+ }
+
+ ga_grow(&ga, cplen);
+ memmove((char *)ga.ga_data + ga.ga_len, cpstr, (size_t)cplen);
+ ga.ga_len += cplen;
+
+ in_str += inlen;
+ } else {
+ // When not using multi-byte chars we can do it faster.
+ const char *const p = strchr(fromstr, *in_str);
+ if (p != NULL) {
+ ga_append(&ga, tostr[p - fromstr]);
+ } else {
+ ga_append(&ga, *in_str);
+ }
+ in_str++;
+ }
+ }
+
+ // add a terminating NUL
+ ga_append(&ga, NUL);
+
+ rettv->vval.v_string = ga.ga_data;
+ return;
+error:
+ EMSG2(_(e_invarg2), fromstr);
+ ga_clear(&ga);
+ return;
+}
+
+// "trim({expr})" function
+static void f_trim(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ char buf1[NUMBUFLEN];
+ char buf2[NUMBUFLEN];
+ const char_u *head = (const char_u *)tv_get_string_buf_chk(&argvars[0], buf1);
+ const char_u *mask = NULL;
+ const char_u *tail;
+ const char_u *prev;
+ const char_u *p;
+ int c1;
+
+ rettv->v_type = VAR_STRING;
+ if (head == NULL) {
+ rettv->vval.v_string = NULL;
+ return;
+ }
+
+ if (argvars[1].v_type == VAR_STRING) {
+ mask = (const char_u *)tv_get_string_buf_chk(&argvars[1], buf2);
+ }
+
+ while (*head != NUL) {
+ c1 = PTR2CHAR(head);
+ if (mask == NULL) {
+ if (c1 > ' ' && c1 != 0xa0) {
+ break;
+ }
+ } else {
+ for (p = mask; *p != NUL; MB_PTR_ADV(p)) {
+ if (c1 == PTR2CHAR(p)) {
+ break;
+ }
+ }
+ if (*p == NUL) {
+ break;
+ }
+ }
+ MB_PTR_ADV(head);
+ }
+
+ for (tail = head + STRLEN(head); tail > head; tail = prev) {
+ prev = tail;
+ MB_PTR_BACK(head, prev);
+ c1 = PTR2CHAR(prev);
+ if (mask == NULL) {
+ if (c1 > ' ' && c1 != 0xa0) {
+ break;
+ }
+ } else {
+ for (p = mask; *p != NUL; MB_PTR_ADV(p)) {
+ if (c1 == PTR2CHAR(p)) {
+ break;
+ }
+ }
+ if (*p == NUL) {
+ break;
+ }
+ }
+ }
+ rettv->vval.v_string = vim_strnsave(head, (int)(tail - head));
+}
+
+/*
+ * "type(expr)" function
+ */
+static void f_type(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ int n = -1;
+
+ switch (argvars[0].v_type) {
+ case VAR_NUMBER: n = VAR_TYPE_NUMBER; break;
+ case VAR_STRING: n = VAR_TYPE_STRING; break;
+ case VAR_PARTIAL:
+ case VAR_FUNC: n = VAR_TYPE_FUNC; break;
+ case VAR_LIST: n = VAR_TYPE_LIST; break;
+ case VAR_DICT: n = VAR_TYPE_DICT; break;
+ case VAR_FLOAT: n = VAR_TYPE_FLOAT; break;
+ case VAR_BOOL: n = VAR_TYPE_BOOL; break;
+ case VAR_SPECIAL:n = VAR_TYPE_SPECIAL; break;
+ case VAR_UNKNOWN: {
+ internal_error("f_type(UNKNOWN)");
+ break;
+ }
+ }
+ rettv->vval.v_number = n;
+}
+
+/*
+ * "undofile(name)" function
+ */
+static void f_undofile(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->v_type = VAR_STRING;
+ const char *const fname = tv_get_string(&argvars[0]);
+
+ if (*fname == NUL) {
+ // If there is no file name there will be no undo file.
+ rettv->vval.v_string = NULL;
+ } else {
+ char *ffname = FullName_save(fname, true);
+
+ if (ffname != NULL) {
+ rettv->vval.v_string = (char_u *)u_get_undo_file_name(ffname, false);
+ }
+ xfree(ffname);
+ }
+}
+
+/*
+ * "undotree()" function
+ */
+static void f_undotree(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ tv_dict_alloc_ret(rettv);
+
+ dict_T *dict = rettv->vval.v_dict;
+
+ tv_dict_add_nr(dict, S_LEN("synced"), (varnumber_T)curbuf->b_u_synced);
+ tv_dict_add_nr(dict, S_LEN("seq_last"), (varnumber_T)curbuf->b_u_seq_last);
+ tv_dict_add_nr(dict, S_LEN("save_last"),
+ (varnumber_T)curbuf->b_u_save_nr_last);
+ tv_dict_add_nr(dict, S_LEN("seq_cur"), (varnumber_T)curbuf->b_u_seq_cur);
+ tv_dict_add_nr(dict, S_LEN("time_cur"), (varnumber_T)curbuf->b_u_time_cur);
+ tv_dict_add_nr(dict, S_LEN("save_cur"), (varnumber_T)curbuf->b_u_save_nr_cur);
+
+ tv_dict_add_list(dict, S_LEN("entries"), u_eval_tree(curbuf->b_u_oldhead));
+}
+
+/*
+ * "values(dict)" function
+ */
+static void f_values(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ dict_list(argvars, rettv, 1);
+}
+
+/*
+ * "virtcol(string)" function
+ */
+static void f_virtcol(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ colnr_T vcol = 0;
+ pos_T *fp;
+ int fnum = curbuf->b_fnum;
+
+ fp = var2fpos(&argvars[0], FALSE, &fnum);
+ if (fp != NULL && fp->lnum <= curbuf->b_ml.ml_line_count
+ && fnum == curbuf->b_fnum) {
+ // Limit the column to a valid value, getvvcol() doesn't check.
+ if (fp->col < 0) {
+ fp->col = 0;
+ } else {
+ const size_t len = STRLEN(ml_get(fp->lnum));
+ if (fp->col > (colnr_T)len) {
+ fp->col = (colnr_T)len;
+ }
+ }
+ getvvcol(curwin, fp, NULL, NULL, &vcol);
+ ++vcol;
+ }
+
+ rettv->vval.v_number = vcol;
+}
+
+/*
+ * "visualmode()" function
+ */
+static void f_visualmode(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ char_u str[2];
+
+ rettv->v_type = VAR_STRING;
+ str[0] = curbuf->b_visual_mode_eval;
+ str[1] = NUL;
+ rettv->vval.v_string = vim_strsave(str);
+
+ // A non-zero number or non-empty string argument: reset mode.
+ if (non_zero_arg(&argvars[0])) {
+ curbuf->b_visual_mode_eval = NUL;
+ }
+}
+
+/*
+ * "wildmenumode()" function
+ */
+static void f_wildmenumode(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ if (wild_menu_showing || ((State & CMDLINE) && pum_visible())) {
+ rettv->vval.v_number = 1;
+ }
+}
+
+/// "win_findbuf()" function
+static void f_win_findbuf(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ tv_list_alloc_ret(rettv, kListLenMayKnow);
+ win_findbuf(argvars, rettv->vval.v_list);
+}
+
+/// "win_getid()" function
+static void f_win_getid(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = win_getid(argvars);
+}
+
+/// "win_gotoid()" function
+static void f_win_gotoid(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = win_gotoid(argvars);
+}
+
+/// "win_id2tabwin()" function
+static void f_win_id2tabwin(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ win_id2tabwin(argvars, rettv);
+}
+
+/// "win_id2win()" function
+static void f_win_id2win(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = win_id2win(argvars);
+}
+
+/// "winbufnr(nr)" function
+static void f_winbufnr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ win_T *wp = find_win_by_nr_or_id(&argvars[0]);
+ if (wp == NULL) {
+ rettv->vval.v_number = -1;
+ } else {
+ rettv->vval.v_number = wp->w_buffer->b_fnum;
+ }
+}
+
+/*
+ * "wincol()" function
+ */
+static void f_wincol(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ validate_cursor();
+ rettv->vval.v_number = curwin->w_wcol + 1;
+}
+
+/// "winheight(nr)" function
+static void f_winheight(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ win_T *wp = find_win_by_nr_or_id(&argvars[0]);
+ if (wp == NULL) {
+ rettv->vval.v_number = -1;
+ } else {
+ rettv->vval.v_number = wp->w_height;
+ }
+}
+
+// "winlayout()" function
+static void f_winlayout(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ tabpage_T *tp;
+
+ tv_list_alloc_ret(rettv, 2);
+
+ if (argvars[0].v_type == VAR_UNKNOWN) {
+ tp = curtab;
+ } else {
+ tp = find_tabpage((int)tv_get_number(&argvars[0]));
+ if (tp == NULL) {
+ return;
+ }
+ }
+
+ get_framelayout(tp->tp_topframe, rettv->vval.v_list, true);
+}
+
+/*
+ * "winline()" function
+ */
+static void f_winline(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ validate_cursor();
+ rettv->vval.v_number = curwin->w_wrow + 1;
+}
+
+/*
+ * "winnr()" function
+ */
+static void f_winnr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ int nr = 1;
+
+ nr = get_winnr(curtab, &argvars[0]);
+ rettv->vval.v_number = nr;
+}
+
+/*
+ * "winrestcmd()" function
+ */
+static void f_winrestcmd(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ int winnr = 1;
+ garray_T ga;
+ char_u buf[50];
+
+ ga_init(&ga, (int)sizeof(char), 70);
+ FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
+ sprintf((char *)buf, "%dresize %d|", winnr, wp->w_height);
+ ga_concat(&ga, buf);
+ sprintf((char *)buf, "vert %dresize %d|", winnr, wp->w_width);
+ ga_concat(&ga, buf);
+ ++winnr;
+ }
+ ga_append(&ga, NUL);
+
+ rettv->vval.v_string = ga.ga_data;
+ rettv->v_type = VAR_STRING;
+}
+
+/*
+ * "winrestview()" function
+ */
+static void f_winrestview(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ dict_T *dict;
+
+ if (argvars[0].v_type != VAR_DICT
+ || (dict = argvars[0].vval.v_dict) == NULL) {
+ EMSG(_(e_invarg));
+ } else {
+ dictitem_T *di;
+ if ((di = tv_dict_find(dict, S_LEN("lnum"))) != NULL) {
+ curwin->w_cursor.lnum = tv_get_number(&di->di_tv);
+ }
+ if ((di = tv_dict_find(dict, S_LEN("col"))) != NULL) {
+ curwin->w_cursor.col = tv_get_number(&di->di_tv);
+ }
+ if ((di = tv_dict_find(dict, S_LEN("coladd"))) != NULL) {
+ curwin->w_cursor.coladd = tv_get_number(&di->di_tv);
+ }
+ if ((di = tv_dict_find(dict, S_LEN("curswant"))) != NULL) {
+ curwin->w_curswant = tv_get_number(&di->di_tv);
+ curwin->w_set_curswant = false;
+ }
+ if ((di = tv_dict_find(dict, S_LEN("topline"))) != NULL) {
+ set_topline(curwin, tv_get_number(&di->di_tv));
+ }
+ if ((di = tv_dict_find(dict, S_LEN("topfill"))) != NULL) {
+ curwin->w_topfill = tv_get_number(&di->di_tv);
+ }
+ if ((di = tv_dict_find(dict, S_LEN("leftcol"))) != NULL) {
+ curwin->w_leftcol = tv_get_number(&di->di_tv);
+ }
+ if ((di = tv_dict_find(dict, S_LEN("skipcol"))) != NULL) {
+ curwin->w_skipcol = tv_get_number(&di->di_tv);
+ }
+
+ check_cursor();
+ win_new_height(curwin, curwin->w_height);
+ win_new_width(curwin, curwin->w_width);
+ changed_window_setting();
+
+ if (curwin->w_topline <= 0)
+ curwin->w_topline = 1;
+ if (curwin->w_topline > curbuf->b_ml.ml_line_count)
+ curwin->w_topline = curbuf->b_ml.ml_line_count;
+ check_topfill(curwin, true);
+ }
+}
+
+/*
+ * "winsaveview()" function
+ */
+static void f_winsaveview(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ dict_T *dict;
+
+ tv_dict_alloc_ret(rettv);
+ dict = rettv->vval.v_dict;
+
+ tv_dict_add_nr(dict, S_LEN("lnum"), (varnumber_T)curwin->w_cursor.lnum);
+ tv_dict_add_nr(dict, S_LEN("col"), (varnumber_T)curwin->w_cursor.col);
+ tv_dict_add_nr(dict, S_LEN("coladd"), (varnumber_T)curwin->w_cursor.coladd);
+ update_curswant();
+ tv_dict_add_nr(dict, S_LEN("curswant"), (varnumber_T)curwin->w_curswant);
+
+ tv_dict_add_nr(dict, S_LEN("topline"), (varnumber_T)curwin->w_topline);
+ tv_dict_add_nr(dict, S_LEN("topfill"), (varnumber_T)curwin->w_topfill);
+ tv_dict_add_nr(dict, S_LEN("leftcol"), (varnumber_T)curwin->w_leftcol);
+ tv_dict_add_nr(dict, S_LEN("skipcol"), (varnumber_T)curwin->w_skipcol);
+}
+
+/// "winwidth(nr)" function
+static void f_winwidth(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ win_T *wp = find_win_by_nr_or_id(&argvars[0]);
+ if (wp == NULL) {
+ rettv->vval.v_number = -1;
+ } else {
+ rettv->vval.v_number = wp->w_width;
+ }
+}
+
+/// "wordcount()" function
+static void f_wordcount(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ tv_dict_alloc_ret(rettv);
+ cursor_pos_info(rettv->vval.v_dict);
+}
+
+/// "writefile()" function
+static void f_writefile(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = -1;
+
+ if (check_secure()) {
+ return;
+ }
+
+ if (argvars[0].v_type != VAR_LIST) {
+ EMSG2(_(e_listarg), "writefile()");
+ return;
+ }
+ const list_T *const list = argvars[0].vval.v_list;
+ TV_LIST_ITER_CONST(list, li, {
+ if (!tv_check_str_or_nr(TV_LIST_ITEM_TV(li))) {
+ return;
+ }
+ });
+
+ bool binary = false;
+ bool append = false;
+ bool do_fsync = !!p_fs;
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ const char *const flags = tv_get_string_chk(&argvars[2]);
+ if (flags == NULL) {
+ return;
+ }
+ for (const char *p = flags; *p; p++) {
+ switch (*p) {
+ case 'b': { binary = true; break; }
+ case 'a': { append = true; break; }
+ case 's': { do_fsync = true; break; }
+ case 'S': { do_fsync = false; break; }
+ default: {
+ // Using %s, p and not %c, *p to preserve multibyte characters
+ emsgf(_("E5060: Unknown flag: %s"), p);
+ return;
+ }
+ }
+ }
+ }
+
+ char buf[NUMBUFLEN];
+ const char *const fname = tv_get_string_buf_chk(&argvars[1], buf);
+ if (fname == NULL) {
+ return;
+ }
+ FileDescriptor fp;
+ int error;
+ if (*fname == NUL) {
+ EMSG(_("E482: Can't open file with an empty name"));
+ } else if ((error = file_open(&fp, fname,
+ ((append ? kFileAppend : kFileTruncate)
+ | kFileCreate), 0666)) != 0) {
+ emsgf(_("E482: Can't open file %s for writing: %s"),
+ fname, os_strerror(error));
+ } else {
+ if (write_list(&fp, list, binary)) {
+ rettv->vval.v_number = 0;
+ }
+ if ((error = file_close(&fp, do_fsync)) != 0) {
+ emsgf(_("E80: Error when closing file %s: %s"),
+ fname, os_strerror(error));
+ }
+ }
+}
+/*
+ * "xor(expr, expr)" function
+ */
+static void f_xor(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = tv_get_number_chk(&argvars[0], NULL)
+ ^ tv_get_number_chk(&argvars[1], NULL);
+}
diff --git a/src/nvim/eval/funcs.h b/src/nvim/eval/funcs.h
new file mode 100644
index 0000000000..a343290734
--- /dev/null
+++ b/src/nvim/eval/funcs.h
@@ -0,0 +1,24 @@
+#ifndef NVIM_EVAL_FUNCS_H
+#define NVIM_EVAL_FUNCS_H
+
+#include "nvim/buffer_defs.h"
+#include "nvim/eval/typval.h"
+
+typedef void (*FunPtr)(void);
+
+/// Prototype of C function that implements VimL function
+typedef void (*VimLFunc)(typval_T *args, typval_T *rvar, FunPtr data);
+
+/// Structure holding VimL function definition
+typedef struct fst {
+ char *name; ///< Name of the function.
+ uint8_t min_argc; ///< Minimal number of arguments.
+ uint8_t max_argc; ///< Maximal number of arguments.
+ VimLFunc func; ///< Function implementation.
+ FunPtr data; ///< Userdata for function implementation.
+} VimLFuncDef;
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "eval/funcs.h.generated.h"
+#endif
+#endif // NVIM_EVAL_FUNCS_H
diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c
index 106b8f6eed..8dde78de3d 100644
--- a/src/nvim/eval/typval.c
+++ b/src/nvim/eval/typval.c
@@ -15,6 +15,8 @@
#include "nvim/eval/encode.h"
#include "nvim/eval/typval_encode.h"
#include "nvim/eval.h"
+#include "nvim/eval/userfunc.h"
+#include "nvim/lua/executor.h"
#include "nvim/types.h"
#include "nvim/assert.h"
#include "nvim/memory.h"
@@ -300,6 +302,7 @@ void tv_list_free_list(list_T *const l)
}
list_log(l, NULL, NULL, "freelist");
+ nlua_free_typval_list(l);
xfree(l);
}
@@ -637,6 +640,57 @@ tv_list_copy_error:
return NULL;
}
+/// Flatten "list" in place to depth "maxdepth".
+/// 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
+{
+ listitem_T *item;
+ listitem_T *to_free;
+ int n;
+ if (maxdepth == 0) {
+ return OK;
+ }
+
+ n = 0;
+ item = list->lv_first;
+ while (item != NULL) {
+ fast_breakcheck();
+ if (got_int) {
+ return FAIL;
+ }
+ if (item->li_tv.v_type == VAR_LIST) {
+ listitem_T *next = item->li_next;
+
+ 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;
+
+ if (item->li_prev == NULL) {
+ item = list->lv_first;
+ } else {
+ item = item->li_prev->li_next;
+ }
+ xfree(to_free);
+
+ if (++n >= maxdepth) {
+ n = 0;
+ item = next;
+ }
+ } else {
+ n = 0;
+ item = item->li_next;
+ }
+ }
+ return OK;
+}
+
/// Extend first list with the second
///
/// @param[out] l1 List to extend.
@@ -796,10 +850,14 @@ bool tv_list_equal(list_T *const l1, list_T *const l2, const bool ic,
if (l1 == l2) {
return true;
}
- if (l1 == NULL || l2 == NULL) {
+ if (tv_list_len(l1) != tv_list_len(l2)) {
return false;
}
- if (tv_list_len(l1) != tv_list_len(l2)) {
+ if (tv_list_len(l1) == 0) {
+ // empty and NULL list are considered equal
+ return true;
+ }
+ if (l1 == NULL || l2 == NULL) {
return false;
}
@@ -1200,6 +1258,7 @@ void tv_dict_watcher_notify(dict_T *const dict, const char *const key,
typval_T rettv;
+ dict->dv_refcount++;
QUEUE *w;
QUEUE_FOREACH(w, &dict->watchers) {
DictWatcher *watcher = tv_dict_watcher_node_data(w);
@@ -1211,6 +1270,7 @@ void tv_dict_watcher_notify(dict_T *const dict, const char *const key,
tv_clear(&rettv);
}
}
+ tv_dict_unref(dict);
for (size_t i = 1; i < ARRAY_SIZE(argv); i++) {
tv_clear(argv + i);
@@ -1304,7 +1364,7 @@ void tv_dict_item_remove(dict_T *const dict, dictitem_T *const item)
dict_T *tv_dict_alloc(void)
FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT
{
- dict_T *const d = xmalloc(sizeof(dict_T));
+ dict_T *const d = xcalloc(1, sizeof(dict_T));
// Add the dict to the list of dicts for garbage collection.
if (gc_first_dict != NULL) {
@@ -1371,6 +1431,7 @@ void tv_dict_free_dict(dict_T *const d)
d->dv_used_next->dv_used_prev = d->dv_used_prev;
}
+ nlua_free_typval_dict(d);
xfree(d);
}
@@ -1426,6 +1487,23 @@ dictitem_T *tv_dict_find(const dict_T *const d, const char *const key,
return TV_DICT_HI2DI(hi);
}
+/// Get a typval item from a dictionary and copy it into "rettv".
+///
+/// @param[in] d Dictionary to check.
+/// @param[in] key Dictionary key.
+/// @param[in] rettv Return value.
+/// @return OK in case of success or FAIL if nothing was found.
+int tv_dict_get_tv(dict_T *d, const char *const key, typval_T *rettv)
+{
+ dictitem_T *const di = tv_dict_find(d, key, -1);
+ if (di == NULL) {
+ return FAIL;
+ }
+
+ tv_copy(&di->di_tv, rettv);
+ return OK;
+}
+
/// Get a number item from a dictionary
///
/// Returns 0 if the entry does not exist.
@@ -1585,6 +1663,26 @@ int tv_dict_add_list(dict_T *const d, const char *const key,
return OK;
}
+/// Add a typval entry to dictionary.
+///
+/// @param[out] d Dictionary to add entry to.
+/// @param[in] key Key to add.
+/// @param[in] key_len Key length.
+///
+/// @return FAIL if out of memory or key already exists.
+int tv_dict_add_tv(dict_T *d, const char *key, const size_t key_len,
+ typval_T *tv)
+{
+ dictitem_T *const item = tv_dict_item_alloc_len(key, key_len);
+
+ tv_copy(tv, &item->di_tv);
+ if (tv_dict_add(d, item) == FAIL) {
+ tv_dict_item_free(item);
+ return FAIL;
+ }
+ return OK;
+}
+
/// Add a dictionary entry to dictionary
///
/// @param[out] d Dictionary to add entry to.
@@ -1631,21 +1729,43 @@ int tv_dict_add_nr(dict_T *const d, const char *const key,
return OK;
}
-/// Add a special entry to dictionary
+/// Add a floating point number entry to dictionary
+///
+/// @param[out] d Dictionary to add entry to.
+/// @param[in] key Key to add.
+/// @param[in] key_len Key length.
+/// @param[in] nr Floating point number to add.
+///
+/// @return OK in case of success, FAIL when key already exists.
+int tv_dict_add_float(dict_T *const d, const char *const key,
+ const size_t key_len, const float_T nr)
+{
+ dictitem_T *const item = tv_dict_item_alloc_len(key, key_len);
+
+ item->di_tv.v_type = VAR_FLOAT;
+ item->di_tv.vval.v_float = nr;
+ if (tv_dict_add(d, item) == FAIL) {
+ tv_dict_item_free(item);
+ return FAIL;
+ }
+ return OK;
+}
+
+/// Add a boolean entry to dictionary
///
/// @param[out] d Dictionary to add entry to.
/// @param[in] key Key to add.
/// @param[in] key_len Key length.
-/// @param[in] val SpecialVarValue to add.
+/// @param[in] val BoolVarValue to add.
///
/// @return OK in case of success, FAIL when key already exists.
-int tv_dict_add_special(dict_T *const d, const char *const key,
- const size_t key_len, SpecialVarValue val)
+int tv_dict_add_bool(dict_T *const d, const char *const key,
+ const size_t key_len, BoolVarValue val)
{
dictitem_T *const item = tv_dict_item_alloc_len(key, key_len);
- item->di_tv.v_type = VAR_SPECIAL;
- item->di_tv.vval.v_special = val;
+ item->di_tv.v_type = VAR_BOOL;
+ item->di_tv.vval.v_bool = val;
if (tv_dict_add(d, item) == FAIL) {
tv_dict_item_free(item);
return FAIL;
@@ -1659,9 +1779,9 @@ int tv_dict_add_special(dict_T *const d, const char *const key,
int tv_dict_add_str(dict_T *const d,
const char *const key, const size_t key_len,
const char *const val)
- FUNC_ATTR_NONNULL_ALL
+ FUNC_ATTR_NONNULL_ARG(1, 2)
{
- return tv_dict_add_allocated_str(d, key, key_len, xstrdup(val));
+ return tv_dict_add_str_len(d, key, key_len, val, -1);
}
/// Add a string entry to dictionary
@@ -1675,10 +1795,10 @@ int tv_dict_add_str(dict_T *const d,
/// @return OK in case of success, FAIL when key already exists.
int tv_dict_add_str_len(dict_T *const d,
const char *const key, const size_t key_len,
- char *const val, int len)
+ const char *const val, int len)
FUNC_ATTR_NONNULL_ARG(1, 2)
{
- char *s = val ? val : "";
+ char *s = NULL;
if (val != NULL) {
s = (len < 0) ? xstrdup(val) : xstrndup(val, (size_t)len);
}
@@ -1701,7 +1821,7 @@ int tv_dict_add_str_len(dict_T *const d,
int tv_dict_add_allocated_str(dict_T *const d,
const char *const key, const size_t key_len,
char *const val)
- FUNC_ATTR_NONNULL_ALL
+ FUNC_ATTR_NONNULL_ARG(1, 2)
{
dictitem_T *const item = tv_dict_item_alloc_len(key, key_len);
@@ -1951,12 +2071,15 @@ void tv_dict_alloc_ret(typval_T *const ret_tv)
#define TYPVAL_ENCODE_CONV_NIL(tv) \
do { \
- tv->vval.v_special = kSpecialVarFalse; \
+ tv->vval.v_special = kSpecialVarNull; \
tv->v_lock = VAR_UNLOCKED; \
} while (0)
#define TYPVAL_ENCODE_CONV_BOOL(tv, num) \
- TYPVAL_ENCODE_CONV_NIL(tv)
+ do { \
+ tv->vval.v_bool = kBoolVarFalse; \
+ tv->v_lock = VAR_UNLOCKED; \
+ } while (0)
#define TYPVAL_ENCODE_CONV_NUMBER(tv, num) \
do { \
@@ -2231,6 +2354,7 @@ void tv_free(typval_T *tv)
tv_dict_unref(tv->vval.v_dict);
break;
}
+ case VAR_BOOL:
case VAR_SPECIAL:
case VAR_NUMBER:
case VAR_FLOAT:
@@ -2262,6 +2386,7 @@ void tv_copy(const typval_T *const from, typval_T *const to)
switch (from->v_type) {
case VAR_NUMBER:
case VAR_FLOAT:
+ case VAR_BOOL:
case VAR_SPECIAL: {
break;
}
@@ -2363,6 +2488,7 @@ void tv_item_lock(typval_T *const tv, const int deep, const bool lock)
case VAR_STRING:
case VAR_FUNC:
case VAR_PARTIAL:
+ case VAR_BOOL:
case VAR_SPECIAL: {
break;
}
@@ -2526,6 +2652,9 @@ bool tv_equal(typval_T *const tv1, typval_T *const tv2, const bool ic,
const char *s2 = tv_get_string_buf(tv2, buf2);
return mb_strcmp_ic((bool)ic, s1, s2) == 0;
}
+ case VAR_BOOL: {
+ return tv1->vval.v_bool == tv2->vval.v_bool;
+ }
case VAR_SPECIAL: {
return tv1->vval.v_special == tv2->vval.v_special;
}
@@ -2576,6 +2705,10 @@ bool tv_check_str_or_nr(const typval_T *const tv)
EMSG(_("E728: Expected a Number or a String, Dictionary found"));
return false;
}
+ case VAR_BOOL: {
+ EMSG(_("E5299: Expected a Number or a String, Boolean found"));
+ return false;
+ }
case VAR_SPECIAL: {
EMSG(_("E5300: Expected a Number or a String"));
return false;
@@ -2615,6 +2748,7 @@ bool tv_check_num(const typval_T *const tv)
{
switch (tv->v_type) {
case VAR_NUMBER:
+ case VAR_BOOL:
case VAR_SPECIAL:
case VAR_STRING: {
return true;
@@ -2659,6 +2793,7 @@ bool tv_check_str(const typval_T *const tv)
{
switch (tv->v_type) {
case VAR_NUMBER:
+ case VAR_BOOL:
case VAR_SPECIAL:
case VAR_STRING: {
return true;
@@ -2729,17 +2864,11 @@ varnumber_T tv_get_number_chk(const typval_T *const tv, bool *const ret_error)
}
return n;
}
+ case VAR_BOOL: {
+ return tv->vval.v_bool == kBoolVarTrue ? 1 : 0;
+ }
case VAR_SPECIAL: {
- switch (tv->vval.v_special) {
- case kSpecialVarTrue: {
- return 1;
- }
- case kSpecialVarFalse:
- case kSpecialVarNull: {
- return 0;
- }
- }
- break;
+ return 0;
}
case VAR_UNKNOWN: {
emsgf(_(e_intern2), "tv_get_number(UNKNOWN)");
@@ -2807,6 +2936,10 @@ float_T tv_get_float(const typval_T *const tv)
EMSG(_("E894: Using a Dictionary as a Float"));
break;
}
+ case VAR_BOOL: {
+ EMSG(_("E362: Using a boolean value as a Float"));
+ break;
+ }
case VAR_SPECIAL: {
EMSG(_("E907: Using a special value as a Float"));
break;
@@ -2844,6 +2977,10 @@ const char *tv_get_string_buf_chk(const typval_T *const tv, char *const buf)
}
return "";
}
+ case VAR_BOOL: {
+ STRCPY(buf, encode_bool_var_names[tv->vval.v_bool]);
+ return buf;
+ }
case VAR_SPECIAL: {
STRCPY(buf, encode_special_var_names[tv->vval.v_special]);
return buf;
diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h
index 0b04170cac..503a32a81e 100644
--- a/src/nvim/eval/typval.h
+++ b/src/nvim/eval/typval.h
@@ -33,7 +33,7 @@ typedef double float_T;
enum { DO_NOT_FREE_CNT = (INT_MAX / 2) };
/// Additional values for tv_list_alloc() len argument
-enum {
+enum ListLenSpecials {
/// List length is not known in advance
///
/// To be used when there is neither a way to know how many elements will be
@@ -49,7 +49,7 @@ enum {
///
/// To be used when it looks impractical to determine list length.
kListLenMayKnow = -3,
-} ListLenSpecials;
+};
/// Maximal possible value of varnumber_T variable
#define VARNUMBER_MAX INT64_MAX
@@ -91,10 +91,14 @@ typedef struct dict_watcher {
bool busy; // prevent recursion if the dict is changed in the callback
} DictWatcher;
+/// Bool variable values
+typedef enum {
+ kBoolVarFalse, ///< v:false
+ kBoolVarTrue, ///< v:true
+} BoolVarValue;
+
/// Special variable values
typedef enum {
- kSpecialVarFalse, ///< v:false
- kSpecialVarTrue, ///< v:true
kSpecialVarNull, ///< v:null
} SpecialVarValue;
@@ -114,6 +118,7 @@ typedef enum {
VAR_LIST, ///< List, .v_list is used.
VAR_DICT, ///< Dictionary, .v_dict is used.
VAR_FLOAT, ///< Floating-point value, .v_float is used.
+ VAR_BOOL, ///< true, false
VAR_SPECIAL, ///< Special value (true, false, null), .v_special
///< is used.
VAR_PARTIAL, ///< Partial, .v_partial is used.
@@ -125,6 +130,7 @@ typedef struct {
VarLockStatus v_lock; ///< Variable lock status.
union typval_vval_union {
varnumber_T v_number; ///< Number, for VAR_NUMBER.
+ BoolVarValue v_bool; ///< Bool value, for VAR_BOOL
SpecialVarValue v_special; ///< Special value, for VAR_SPECIAL.
float_T v_float; ///< Floating-point number, for VAR_FLOAT.
char_u *v_string; ///< String, for VAR_STRING and VAR_FUNC, can be NULL.
@@ -174,6 +180,8 @@ struct listvar_S {
int lv_idx; ///< Index of a cached item, used for optimising repeated l[idx].
int lv_copyID; ///< ID used by deepcopy().
VarLockStatus lv_lock; ///< Zero, VAR_LOCKED, VAR_FIXED.
+
+ LuaRef lua_table_ref;
};
// Static list with 10 items. Use tv_list_init_static10() to initialize.
@@ -239,6 +247,8 @@ struct dictvar_S {
dict_T *dv_used_next; ///< Next dictionary in used dictionaries list.
dict_T *dv_used_prev; ///< Previous dictionary in used dictionaries list.
QUEUE watchers; ///< Dictionary key watchers set by user code.
+
+ LuaRef lua_table_ref;
};
/// Type used for script ID
@@ -246,7 +256,7 @@ typedef int scid_T;
/// Format argument for scid_T
#define PRIdSCID "d"
-// SCript ConteXt (SCTX): identifies a script script line.
+// SCript ConteXt (SCTX): identifies a script line.
// When sourcing a script "sc_lnum" is zero, "sourcing_lnum" is the current
// line number. When executing a user function "sc_lnum" is the line where the
// function was defined, "sourcing_lnum" is the line number inside the
@@ -258,9 +268,45 @@ typedef struct {
linenr_T sc_lnum; // line number
} sctx_T;
+/// Maximum number of function arguments
+#define MAX_FUNC_ARGS 20
+/// Short variable name length
+#define VAR_SHORT_LEN 20
+/// Number of fixed variables used for arguments
+#define FIXVAR_CNT 12
+
+/// Callback interface for C function reference>
+/// Used for managing functions that were registered with |register_cfunc|
+typedef int (*cfunc_T)(int argcount, typval_T *argvars, typval_T *rettv, void *state); // NOLINT
+/// Callback to clear cfunc_T and any associated state.
+typedef void (*cfunc_free_T)(void *state);
+
// Structure to hold info for a function that is currently being executed.
typedef struct funccall_S funccall_T;
+struct funccall_S {
+ ufunc_T *func; ///< Function being called.
+ int linenr; ///< Next line to be executed.
+ int returned; ///< ":return" used.
+ /// Fixed variables for arguments.
+ TV_DICTITEM_STRUCT(VAR_SHORT_LEN + 1) fixvar[FIXVAR_CNT];
+ dict_T l_vars; ///< l: local function variables.
+ ScopeDictDictItem l_vars_var; ///< Variable for l: scope.
+ dict_T l_avars; ///< a: argument variables.
+ ScopeDictDictItem l_avars_var; ///< Variable for a: scope.
+ list_T l_varlist; ///< List for a:000.
+ listitem_T l_listitems[MAX_FUNC_ARGS]; ///< List items for a:000.
+ typval_T *rettv; ///< Return value.
+ linenr_T breakpoint; ///< Next line with breakpoint or zero.
+ int dbg_tick; ///< Debug_tick when breakpoint was set.
+ int level; ///< Top nesting level of executed function.
+ proftime_T prof_child; ///< Time spent in a child.
+ funccall_T *caller; ///< Calling function or NULL.
+ int fc_refcount; ///< Number of user functions that reference this funccall.
+ int fc_copyID; ///< CopyID used for garbage collection.
+ garray_T fc_funcs; ///< List of ufunc_T* which keep a reference to "func".
+};
+
/// Structure to hold info for a user function.
struct ufunc {
int uf_varargs; ///< variable nr of arguments
@@ -271,6 +317,10 @@ struct ufunc {
garray_T uf_lines; ///< function lines
int uf_profiling; ///< true when func is being profiled
int uf_prof_initialized;
+ // Managing cfuncs
+ cfunc_T uf_cb; ///< C function extension callback
+ cfunc_free_T uf_cb_free; ///< C function extesion free callback
+ void *uf_cb_state; ///< State of C function extension.
// Profiling the function as a whole.
int uf_tm_count; ///< nr of calls
proftime_T uf_tm_total; ///< time spent in function + children
@@ -293,9 +343,6 @@ struct ufunc {
///< (<SNR> is K_SPECIAL KS_EXTRA KE_SNR)
};
-/// Maximum number of function arguments
-#define MAX_FUNC_ARGS 20
-
struct partial_S {
int pt_refcount; ///< Reference count.
char_u *pt_name; ///< Function name; when NULL use pt_func->name.
@@ -798,7 +845,7 @@ static inline bool tv_get_float_chk(const typval_T *const tv,
*ret_f = (float_T)tv->vval.v_number;
return true;
}
- emsgf(_("E808: Number or Float required"));
+ emsgf("%s", _("E808: Number or Float required"));
return false;
}
diff --git a/src/nvim/eval/typval_encode.c.h b/src/nvim/eval/typval_encode.c.h
index 289c3ee99c..91c948ce7e 100644
--- a/src/nvim/eval/typval_encode.c.h
+++ b/src/nvim/eval/typval_encode.c.h
@@ -173,7 +173,7 @@
/// @def TYPVAL_ENCODE_SPECIAL_DICT_KEY_CHECK
/// @brief Macros used to check special dictionary key
///
-/// @param label Label for goto in case check was not successfull.
+/// @param label Label for goto in case check was not successful.
/// @param key typval_T key to check.
/// @def TYPVAL_ENCODE_CONV_DICT_AFTER_KEY
@@ -364,7 +364,7 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE(
_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 && saved_copyID != copyID - 1);
+ assert(saved_copyID != copyID);
_mp_push(*mpstack, ((MPConvStackVal) {
.type = kMPConvList,
.tv = tv,
@@ -379,17 +379,22 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE(
TYPVAL_ENCODE_CONV_REAL_LIST_AFTER_START(tv, _mp_last(*mpstack));
break;
}
+ case VAR_BOOL: {
+ switch (tv->vval.v_bool) {
+ case kBoolVarTrue:
+ case kBoolVarFalse: {
+ TYPVAL_ENCODE_CONV_BOOL(tv, tv->vval.v_bool == kBoolVarTrue);
+ break;
+ }
+ }
+ break;
+ }
case VAR_SPECIAL: {
switch (tv->vval.v_special) {
case kSpecialVarNull: {
TYPVAL_ENCODE_CONV_NIL(tv); // -V1037
break;
}
- case kSpecialVarTrue:
- case kSpecialVarFalse: {
- TYPVAL_ENCODE_CONV_BOOL(tv, tv->vval.v_special == kSpecialVarTrue);
- break;
- }
}
break;
}
@@ -607,7 +612,7 @@ _convert_one_value_regular_dict: {}
kMPConvDict);
TYPVAL_ENCODE_CONV_DICT_START(tv, tv->vval.v_dict,
tv->vval.v_dict->dv_hashtab.ht_used);
- assert(saved_copyID != copyID && saved_copyID != copyID - 1);
+ assert(saved_copyID != copyID);
_mp_push(*mpstack, ((MPConvStackVal) {
.tv = tv,
.type = kMPConvDict,
diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c
new file mode 100644
index 0000000000..229f0e8dde
--- /dev/null
+++ b/src/nvim/eval/userfunc.c
@@ -0,0 +1,3480 @@
+// This is an open source non-commercial project. Dear PVS-Studio, please check
+// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+
+// User defined function support
+
+#include "nvim/ascii.h"
+#include "nvim/charset.h"
+#include "nvim/edit.h"
+#include "nvim/eval.h"
+#include "nvim/eval/encode.h"
+#include "nvim/eval/userfunc.h"
+#include "nvim/ex_cmds2.h"
+#include "nvim/ex_docmd.h"
+#include "nvim/ex_getln.h"
+#include "nvim/ex_getln.h"
+#include "nvim/fileio.h"
+#include "nvim/getchar.h"
+#include "nvim/globals.h"
+#include "nvim/lua/executor.h"
+#include "nvim/misc1.h"
+#include "nvim/os/input.h"
+#include "nvim/regexp.h"
+#include "nvim/search.h"
+#include "nvim/ui.h"
+#include "nvim/vim.h"
+
+// flags used in uf_flags
+#define FC_ABORT 0x01 // abort function on error
+#define FC_RANGE 0x02 // function accepts range
+#define FC_DICT 0x04 // Dict function, uses "self"
+#define FC_CLOSURE 0x08 // closure, uses outer scope variables
+#define FC_DELETED 0x10 // :delfunction used while uf_refcount > 0
+#define FC_REMOVED 0x20 // function redefined while uf_refcount > 0
+#define FC_SANDBOX 0x40 // function defined in the sandbox
+#define FC_CFUNC 0x80 // C function extension
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+#include "eval/userfunc.c.generated.h"
+#endif
+
+hashtab_T func_hashtab;
+
+// Used by get_func_tv()
+static garray_T funcargs = GA_EMPTY_INIT_VALUE;
+
+// pointer to funccal for currently active function
+static funccall_T *current_funccal = NULL;
+
+// Pointer to list of previously used funccal, still around because some
+// 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");
+
+void func_init(void)
+{
+ hash_init(&func_hashtab);
+}
+
+/// Get function arguments.
+static int get_function_args(char_u **argp, char_u endchar, garray_T *newargs,
+ int *varargs, bool skip)
+{
+ bool mustend = false;
+ char_u *arg = *argp;
+ char_u *p = arg;
+ int c;
+ int i;
+
+ if (newargs != NULL) {
+ ga_init(newargs, (int)sizeof(char_u *), 3);
+ }
+
+ if (varargs != NULL) {
+ *varargs = false;
+ }
+
+ // Isolate the arguments: "arg1, arg2, ...)"
+ while (*p != endchar) {
+ if (p[0] == '.' && p[1] == '.' && p[2] == '.') {
+ if (varargs != NULL) {
+ *varargs = true;
+ }
+ p += 3;
+ mustend = true;
+ } else {
+ arg = p;
+ while (ASCII_ISALNUM(*p) || *p == '_') {
+ p++;
+ }
+ if (arg == p || isdigit(*arg)
+ || (p - arg == 9 && STRNCMP(arg, "firstline", 9) == 0)
+ || (p - arg == 8 && STRNCMP(arg, "lastline", 8) == 0)) {
+ if (!skip) {
+ EMSG2(_("E125: Illegal argument: %s"), arg);
+ }
+ break;
+ }
+ if (newargs != NULL) {
+ ga_grow(newargs, 1);
+ c = *p;
+ *p = NUL;
+ arg = vim_strsave(arg);
+
+ // Check for duplicate argument name.
+ for (i = 0; i < newargs->ga_len; i++) {
+ if (STRCMP(((char_u **)(newargs->ga_data))[i], arg) == 0) {
+ EMSG2(_("E853: Duplicate argument name: %s"), arg);
+ xfree(arg);
+ goto err_ret;
+ }
+ }
+ ((char_u **)(newargs->ga_data))[newargs->ga_len] = arg;
+ newargs->ga_len++;
+
+ *p = c;
+ }
+ if (*p == ',') {
+ p++;
+ } else {
+ mustend = true;
+ }
+ }
+ p = skipwhite(p);
+ if (mustend && *p != endchar) {
+ if (!skip) {
+ EMSG2(_(e_invarg2), *argp);
+ }
+ break;
+ }
+ }
+ if (*p != endchar) {
+ goto err_ret;
+ }
+ p++; // skip "endchar"
+
+ *argp = p;
+ return OK;
+
+err_ret:
+ if (newargs != NULL) {
+ ga_clear_strings(newargs);
+ }
+ return FAIL;
+}
+
+/// Register function "fp" as using "current_funccal" as its scope.
+static void register_closure(ufunc_T *fp)
+{
+ if (fp->uf_scoped == current_funccal) {
+ // no change
+ return;
+ }
+ funccal_unref(fp->uf_scoped, fp, false);
+ fp->uf_scoped = current_funccal;
+ current_funccal->fc_refcount++;
+ ga_grow(&current_funccal->fc_funcs, 1);
+ ((ufunc_T **)current_funccal->fc_funcs.ga_data)
+ [current_funccal->fc_funcs.ga_len++] = fp;
+}
+
+
+/// Get a name for a lambda. Returned in static memory.
+char_u * get_lambda_name(void)
+{
+ static char_u name[30];
+ static int lambda_no = 0;
+
+ snprintf((char *)name, sizeof(name), "<lambda>%d", ++lambda_no);
+ return 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_u **arg, typval_T *rettv, bool evaluate)
+{
+ garray_T newargs = GA_EMPTY_INIT_VALUE;
+ garray_T *pnewargs;
+ ufunc_T *fp = NULL;
+ partial_T *pt = NULL;
+ int varargs;
+ int ret;
+ char_u *start = skipwhite(*arg + 1);
+ char_u *s, *e;
+ bool *old_eval_lavars = eval_lavars_used;
+ bool eval_lavars = false;
+
+ // First, check if this is a lambda expression. "->" must exists.
+ ret = get_function_args(&start, '-', NULL, NULL, true);
+ if (ret == FAIL || *start != '>') {
+ return NOTDONE;
+ }
+
+ // Parse the arguments again.
+ if (evaluate) {
+ pnewargs = &newargs;
+ } else {
+ pnewargs = NULL;
+ }
+ *arg = skipwhite(*arg + 1);
+ ret = get_function_args(arg, '-', pnewargs, &varargs, false);
+ if (ret == FAIL || **arg != '>') {
+ goto errret;
+ }
+
+ // Set up a flag for checking local variables and arguments.
+ if (evaluate) {
+ eval_lavars_used = &eval_lavars;
+ }
+
+ // Get the start and the end of the expression.
+ *arg = skipwhite(*arg + 1);
+ s = *arg;
+ ret = skip_expr(arg);
+ if (ret == FAIL) {
+ goto errret;
+ }
+ e = *arg;
+ *arg = skipwhite(*arg);
+ if (**arg != '}') {
+ goto errret;
+ }
+ (*arg)++;
+
+ if (evaluate) {
+ int len, flags = 0;
+ char_u *p;
+ garray_T newlines;
+
+ char_u *name = get_lambda_name();
+
+ fp = xcalloc(1, offsetof(ufunc_T, uf_name) + STRLEN(name) + 1);
+ pt = xcalloc(1, sizeof(partial_T));
+
+ ga_init(&newlines, (int)sizeof(char_u *), 1);
+ ga_grow(&newlines, 1);
+
+ // Add "return " before the expression.
+ len = 7 + e - s + 1;
+ p = (char_u *)xmalloc(len);
+ ((char_u **)(newlines.ga_data))[newlines.ga_len++] = p;
+ STRCPY(p, "return ");
+ STRLCPY(p + 7, s, e - s + 1);
+
+ fp->uf_refcount = 1;
+ STRCPY(fp->uf_name, name);
+ hash_add(&func_hashtab, UF2HIKEY(fp));
+ fp->uf_args = newargs;
+ fp->uf_lines = newlines;
+ if (current_funccal != NULL && eval_lavars) {
+ flags |= FC_CLOSURE;
+ register_closure(fp);
+ } else {
+ fp->uf_scoped = NULL;
+ }
+
+ if (prof_def_func()) {
+ func_do_profile(fp);
+ }
+ if (sandbox) {
+ flags |= FC_SANDBOX;
+ }
+ fp->uf_varargs = true;
+ fp->uf_flags = flags;
+ fp->uf_calls = 0;
+ fp->uf_script_ctx = current_sctx;
+ fp->uf_script_ctx.sc_lnum += sourcing_lnum - newlines.ga_len;
+
+ pt->pt_func = fp;
+ pt->pt_refcount = 1;
+ rettv->vval.v_partial = pt;
+ rettv->v_type = VAR_PARTIAL;
+ }
+
+ eval_lavars_used = old_eval_lavars;
+ return OK;
+
+errret:
+ ga_clear_strings(&newargs);
+ xfree(fp);
+ xfree(pt);
+ eval_lavars_used = old_eval_lavars;
+ return FAIL;
+}
+
+/// Return name of the function corresponding to `name`
+///
+/// If `name` points to variable that is either a function or partial then
+/// corresponding function name is returned. Otherwise it returns `name` itself.
+///
+/// @param[in] name Function name to check.
+/// @param[in,out] lenp Location where length of the returned name is stored.
+/// Must be set to the length of the `name` argument.
+/// @param[out] partialp Location where partial will be stored if found
+/// function appears to be a partial. May be NULL if this
+/// is not needed.
+/// @param[in] no_autoload If true, do not source autoload scripts if function
+/// was not found.
+///
+/// @return name of the function.
+char_u *deref_func_name(const char *name, int *lenp,
+ partial_T **const partialp, bool no_autoload)
+ FUNC_ATTR_NONNULL_ARG(1, 2)
+{
+ if (partialp != NULL) {
+ *partialp = NULL;
+ }
+
+ dictitem_T *const v = find_var(name, (size_t)(*lenp), NULL, no_autoload);
+ if (v != NULL && v->di_tv.v_type == VAR_FUNC) {
+ if (v->di_tv.vval.v_string == NULL) { // just in case
+ *lenp = 0;
+ return (char_u *)"";
+ }
+ *lenp = (int)STRLEN(v->di_tv.vval.v_string);
+ return v->di_tv.vval.v_string;
+ }
+
+ if (v != NULL && v->di_tv.v_type == VAR_PARTIAL) {
+ partial_T *const pt = v->di_tv.vval.v_partial;
+
+ if (pt == NULL) { // just in case
+ *lenp = 0;
+ return (char_u *)"";
+ }
+ if (partialp != NULL) {
+ *partialp = pt;
+ }
+ char_u *s = partial_name(pt);
+ *lenp = (int)STRLEN(s);
+ return s;
+ }
+
+ return (char_u *)name;
+}
+
+/// Give an error message with a function name. Handle <SNR> things.
+///
+/// @param ermsg must be passed without translation (use N_() instead of _()).
+/// @param name function name
+void emsg_funcname(char *ermsg, const char_u *name)
+{
+ char_u *p;
+
+ if (*name == K_SPECIAL) {
+ p = concat_str((char_u *)"<SNR>", name + 3);
+ } else {
+ p = (char_u *)name;
+ }
+
+ EMSG2(_(ermsg), p);
+
+ if (p != name) {
+ xfree(p);
+ }
+}
+
+/*
+ * Allocate a variable for the result of a function.
+ * Return OK or FAIL.
+ */
+int
+get_func_tv(
+ const char_u *name, // name of the function
+ int len, // length of "name"
+ typval_T *rettv,
+ char_u **arg, // argument, pointing to the '('
+ linenr_T firstline, // first line of range
+ linenr_T lastline, // last line of range
+ int *doesrange, // return: function handled range
+ int evaluate,
+ partial_T *partial, // for extra arguments
+ dict_T *selfdict // Dictionary for "self"
+)
+{
+ char_u *argp;
+ int ret = OK;
+ typval_T argvars[MAX_FUNC_ARGS + 1]; /* vars for arguments */
+ int argcount = 0; /* number of arguments found */
+
+ /*
+ * Get the arguments.
+ */
+ argp = *arg;
+ while (argcount < MAX_FUNC_ARGS - (partial == NULL ? 0 : partial->pt_argc)) {
+ argp = skipwhite(argp + 1); // skip the '(' or ','
+ if (*argp == ')' || *argp == ',' || *argp == NUL) {
+ break;
+ }
+ if (eval1(&argp, &argvars[argcount], evaluate) == FAIL) {
+ ret = FAIL;
+ break;
+ }
+ ++argcount;
+ if (*argp != ',')
+ break;
+ }
+ if (*argp == ')')
+ ++argp;
+ else
+ ret = FAIL;
+
+ if (ret == OK) {
+ int i = 0;
+
+ if (get_vim_var_nr(VV_TESTING)) {
+ // Prepare for calling garbagecollect_for_testing(), need to know
+ // what variables are used on the call stack.
+ if (funcargs.ga_itemsize == 0) {
+ ga_init(&funcargs, (int)sizeof(typval_T *), 50);
+ }
+ for (i = 0; i < argcount; i++) {
+ ga_grow(&funcargs, 1);
+ ((typval_T **)funcargs.ga_data)[funcargs.ga_len++] = &argvars[i];
+ }
+ }
+ ret = call_func(name, len, rettv, argcount, argvars, NULL,
+ firstline, lastline, doesrange, evaluate,
+ partial, selfdict);
+
+ funcargs.ga_len -= i;
+ } else if (!aborting()) {
+ if (argcount == MAX_FUNC_ARGS) {
+ emsg_funcname(N_("E740: Too many arguments for function %s"), name);
+ } else {
+ emsg_funcname(N_("E116: Invalid arguments for function %s"), name);
+ }
+ }
+
+ while (--argcount >= 0) {
+ tv_clear(&argvars[argcount]);
+ }
+
+ *arg = skipwhite(argp);
+ return ret;
+}
+
+#define FLEN_FIXED 40
+
+/// Check whether function name starts with <SID> or s:
+///
+/// @warning Only works for names previously checked by eval_fname_script(), if
+/// it returned non-zero.
+///
+/// @param[in] name Name to check.
+///
+/// @return true if it starts with <SID> or s:, false otherwise.
+static inline bool eval_fname_sid(const char *const name)
+ FUNC_ATTR_PURE FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_WARN_UNUSED_RESULT
+ FUNC_ATTR_NONNULL_ALL
+{
+ return *name == 's' || TOUPPER_ASC(name[2]) == 'I';
+}
+
+/// In a script transform script-local names into actually used names
+///
+/// Transforms "<SID>" and "s:" prefixes to `K_SNR {N}` (e.g. K_SNR "123") and
+/// "<SNR>" prefix to `K_SNR`. Uses `fname_buf` buffer that is supposed to have
+/// #FLEN_FIXED + 1 length when it fits, otherwise it allocates memory.
+///
+/// @param[in] name Name to transform.
+/// @param fname_buf Buffer to save resulting function name to, if it fits.
+/// Must have at least #FLEN_FIXED + 1 length.
+/// @param[out] tofree Location where pointer to an allocated memory is saved
+/// in case result does not fit into fname_buf.
+/// @param[out] error Location where error type is saved, @see
+/// FnameTransError.
+///
+/// @return transformed name: either `fname_buf` or a pointer to an allocated
+/// memory.
+static char_u *fname_trans_sid(const char_u *const name,
+ char_u *const fname_buf,
+ char_u **const tofree, int *const error)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ char_u *fname;
+ const int llen = eval_fname_script((const char *)name);
+ if (llen > 0) {
+ fname_buf[0] = K_SPECIAL;
+ fname_buf[1] = KS_EXTRA;
+ fname_buf[2] = (int)KE_SNR;
+ int i = 3;
+ if (eval_fname_sid((const char *)name)) { // "<SID>" or "s:"
+ if (current_sctx.sc_sid <= 0) {
+ *error = ERROR_SCRIPT;
+ } else {
+ snprintf((char *)fname_buf + 3, FLEN_FIXED + 1, "%" PRId64 "_",
+ (int64_t)current_sctx.sc_sid);
+ i = (int)STRLEN(fname_buf);
+ }
+ }
+ if (i + STRLEN(name + llen) < FLEN_FIXED) {
+ STRCPY(fname_buf + i, name + llen);
+ fname = fname_buf;
+ } else {
+ fname = xmalloc(i + STRLEN(name + llen) + 1);
+ *tofree = fname;
+ memmove(fname, fname_buf, (size_t)i);
+ STRCPY(fname + i, name + llen);
+ }
+ } else {
+ fname = (char_u *)name;
+ }
+
+ return fname;
+}
+
+/// Find a function by name, return pointer to it in ufuncs.
+/// @return NULL for unknown function.
+ufunc_T *find_func(const char_u *name)
+{
+ hashitem_T *hi;
+
+ hi = hash_find(&func_hashtab, name);
+ if (!HASHITEM_EMPTY(hi))
+ return HI2UF(hi);
+ return NULL;
+}
+
+/*
+ * Copy the function name of "fp" to buffer "buf".
+ * "buf" must be able to hold the function name plus three bytes.
+ * Takes care of script-local function names.
+ */
+static void cat_func_name(char_u *buf, ufunc_T *fp)
+{
+ if (fp->uf_name[0] == K_SPECIAL) {
+ STRCPY(buf, "<SNR>");
+ STRCAT(buf, fp->uf_name + 3);
+ } else
+ STRCPY(buf, fp->uf_name);
+}
+
+/*
+ * Add a number variable "name" to dict "dp" with value "nr".
+ */
+static void add_nr_var(dict_T *dp, dictitem_T *v, char *name, varnumber_T nr)
+{
+#ifndef __clang_analyzer__
+ STRCPY(v->di_key, name);
+#endif
+ v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX;
+ tv_dict_add(dp, v);
+ v->di_tv.v_type = VAR_NUMBER;
+ v->di_tv.v_lock = VAR_FIXED;
+ v->di_tv.vval.v_number = nr;
+}
+
+// Free "fc"
+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];
+
+ // When garbage collecting a funccall_T may be freed before the
+ // function that references it, clear its uf_scoped field.
+ // The function may have been redefined and point to another
+ // funccal_T, don't clear it then.
+ if (fp != NULL && fp->uf_scoped == fc) {
+ fp->uf_scoped = NULL;
+ }
+ }
+ ga_clear(&fc->fc_funcs);
+
+ func_ptr_unref(fc->func);
+ xfree(fc);
+}
+
+// Free "fc" and what it contains.
+// Can be called only when "fc" is kept beyond the period of it called,
+// i.e. after cleanup_function_call(fc).
+static void free_funccal_contents(funccall_T *fc)
+{
+ // Free all l: variables.
+ vars_clear(&fc->l_vars.dv_hashtab);
+
+ // Free all a: variables.
+ vars_clear(&fc->l_avars.dv_hashtab);
+
+ // Free the a:000 variables.
+ TV_LIST_ITER(&fc->l_varlist, li, {
+ tv_clear(TV_LIST_ITEM_TV(li));
+ });
+
+ free_funccal(fc);
+}
+
+/// Handle the last part of returning from a function: free the local hashtable.
+/// Unless it is still in use by a closure.
+static void cleanup_function_call(funccall_T *fc)
+{
+ bool may_free_fc = fc->fc_refcount <= 0;
+ bool free_fc = true;
+
+ current_funccal = 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);
+ } else {
+ free_fc = false;
+ }
+
+ // 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);
+ } 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_copy(&di->di_tv, &di->di_tv);
+ });
+ }
+
+ if (may_free_fc && fc->l_varlist.lv_refcount // NOLINT(runtime/deprecated)
+ == DO_NOT_FREE_CNT) {
+ 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_copy(TV_LIST_ITEM_TV(li), TV_LIST_ITEM_TV(li));
+ });
+ }
+
+ if (free_fc) {
+ free_funccal(fc);
+ } else {
+ static int made_copy = 0;
+
+ // "fc" is still in use. This can happen when returning "a:000",
+ // assigning "l:" to a global variable or defining a closure.
+ // Link "fc" in the list for garbage collection later.
+ fc->caller = previous_funccal;
+ previous_funccal = fc;
+
+ if (want_garbage_collect) {
+ // If garbage collector is ready, clear count.
+ made_copy = 0;
+ } else if (++made_copy >= (int)((4096 * 1024) / sizeof(*fc))) {
+ // We have made a lot of copies, worth 4 Mbyte. This can happen
+ // when repetitively calling a function that creates a reference to
+ // itself somehow. Call the garbage collector soon to avoid using
+ // too much memory.
+ made_copy = 0;
+ want_garbage_collect = true;
+ }
+ }
+}
+
+/// Unreference "fc": decrement the reference count and free it when it
+/// becomes zero. "fp" is detached from "fc".
+///
+/// @param[in] force When true, we are exiting.
+static void funccal_unref(funccall_T *fc, ufunc_T *fp, bool force)
+{
+ funccall_T **pfc;
+ int i;
+
+ if (fc == NULL) {
+ return;
+ }
+
+ fc->fc_refcount--;
+ if (force ? fc->fc_refcount <= 0 : !fc_referenced(fc)) {
+ for (pfc = &previous_funccal; *pfc != NULL; pfc = &(*pfc)->caller) {
+ if (fc == *pfc) {
+ *pfc = fc->caller;
+ free_funccal_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;
+ }
+ }
+}
+
+/// Remove the function from the function hashtable. If the function was
+/// deleted while it still has references this was already done.
+///
+/// @return true if the entry was deleted, false if it wasn't found.
+static bool func_remove(ufunc_T *fp)
+{
+ hashitem_T *hi = hash_find(&func_hashtab, UF2HIKEY(fp));
+
+ if (!HASHITEM_EMPTY(hi)) {
+ hash_remove(&func_hashtab, hi);
+ return true;
+ }
+
+ return false;
+}
+
+static void func_clear_items(ufunc_T *fp)
+{
+ ga_clear_strings(&(fp->uf_args));
+ ga_clear_strings(&(fp->uf_lines));
+
+ if (fp->uf_cb_free != NULL) {
+ fp->uf_cb_free(fp->uf_cb_state);
+ fp->uf_cb_free = NULL;
+ }
+
+ XFREE_CLEAR(fp->uf_tml_count);
+ XFREE_CLEAR(fp->uf_tml_total);
+ XFREE_CLEAR(fp->uf_tml_self);
+}
+
+/// Free all things that a function contains. Does not free the function
+/// itself, use func_free() for that.
+///
+/// param[in] force When true, we are exiting.
+static void func_clear(ufunc_T *fp, bool force)
+{
+ if (fp->uf_cleared) {
+ return;
+ }
+ fp->uf_cleared = true;
+
+ // clear this function
+ func_clear_items(fp);
+ funccal_unref(fp->uf_scoped, fp, force);
+}
+
+/// Free a function and remove it from the list of functions. Does not free
+/// what a function contains, call func_clear() first.
+///
+/// param[in] fp The function to free.
+static void func_free(ufunc_T *fp)
+{
+ // only remove it when not done already, otherwise we would remove a newer
+ // version of the function
+ if ((fp->uf_flags & (FC_DELETED | FC_REMOVED)) == 0) {
+ func_remove(fp);
+ }
+ xfree(fp);
+}
+
+/// Free all things that a function contains and free the function itself.
+///
+/// param[in] force When true, we are exiting.
+static void func_clear_free(ufunc_T *fp, bool force)
+{
+ func_clear(fp, force);
+ func_free(fp);
+}
+
+/// Call a user function
+///
+/// @param fp Function to call.
+/// @param[in] argcount Number of arguments.
+/// @param argvars Arguments.
+/// @param[out] rettv Return value.
+/// @param[in] firstline First line of range.
+/// @param[in] lastline Last line of range.
+/// @param selfdict Dictionary for "self" for dictionary functions.
+void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars,
+ typval_T *rettv, linenr_T firstline, linenr_T lastline,
+ dict_T *selfdict)
+ FUNC_ATTR_NONNULL_ARG(1, 3, 4)
+{
+ char_u *save_sourcing_name;
+ linenr_T save_sourcing_lnum;
+ bool using_sandbox = false;
+ funccall_T *fc;
+ int save_did_emsg;
+ static int depth = 0;
+ dictitem_T *v;
+ int fixvar_idx = 0; // index in fixvar[]
+ int ai;
+ bool islambda = false;
+ char_u numbuf[NUMBUFLEN];
+ char_u *name;
+ proftime_T wait_start;
+ proftime_T call_start;
+ int started_profiling = false;
+ bool did_save_redo = false;
+ save_redo_T save_redo;
+
+ // If depth of calling is getting too high, don't execute the function
+ if (depth >= p_mfd) {
+ EMSG(_("E132: Function call depth is higher than 'maxfuncdepth'"));
+ rettv->v_type = VAR_NUMBER;
+ rettv->vval.v_number = -1;
+ return;
+ }
+ ++depth;
+ // Save search patterns and redo buffer.
+ save_search_patterns();
+ if (!ins_compl_active()) {
+ saveRedobuff(&save_redo);
+ did_save_redo = true;
+ }
+ ++fp->uf_calls;
+ // check for CTRL-C hit
+ line_breakcheck();
+ // prepare the funccall_T structure
+ fc = xcalloc(1, sizeof(funccall_T));
+ fc->caller = current_funccal;
+ current_funccal = fc;
+ fc->func = fp;
+ fc->rettv = rettv;
+ rettv->vval.v_number = 0;
+ fc->linenr = 0;
+ fc->returned = FALSE;
+ fc->level = ex_nesting_level;
+ // Check if this function has a breakpoint.
+ fc->breakpoint = dbg_find_breakpoint(false, fp->uf_name, (linenr_T)0);
+ fc->dbg_tick = debug_tick;
+
+ // Set up fields for closure.
+ fc->fc_refcount = 0;
+ fc->fc_copyID = 0;
+ ga_init(&fc->fc_funcs, sizeof(ufunc_T *), 1);
+ func_ptr_ref(fp);
+
+ if (STRNCMP(fp->uf_name, "<lambda>", 8) == 0) {
+ islambda = true;
+ }
+
+ // Note about using fc->fixvar[]: This is an array of FIXVAR_CNT variables
+ // with names up to VAR_SHORT_LEN long. This avoids having to alloc/free
+ // each argument variable and saves a lot of time.
+ //
+ // Init l: variables.
+ init_var_dict(&fc->l_vars, &fc->l_vars_var, VAR_DEF_SCOPE);
+ if (selfdict != NULL) {
+ // Set l:self to "selfdict". Use "name" to avoid a warning from
+ // some compiler that checks the destination size.
+ v = (dictitem_T *)&fc->fixvar[fixvar_idx++];
+#ifndef __clang_analyzer__
+ name = v->di_key;
+ STRCPY(name, "self");
+#endif
+ v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX;
+ tv_dict_add(&fc->l_vars, v);
+ v->di_tv.v_type = VAR_DICT;
+ v->di_tv.v_lock = 0;
+ v->di_tv.vval.v_dict = selfdict;
+ ++selfdict->dv_refcount;
+ }
+
+ /*
+ * Init a: variables.
+ * Set a:0 to "argcount".
+ * Set a:000 to a list with room for the "..." arguments.
+ */
+ init_var_dict(&fc->l_avars, &fc->l_avars_var, VAR_SCOPE);
+ add_nr_var(&fc->l_avars, (dictitem_T *)&fc->fixvar[fixvar_idx++], "0",
+ (varnumber_T)(argcount - fp->uf_args.ga_len));
+ fc->l_avars.dv_lock = VAR_FIXED;
+ // Use "name" to avoid a warning from some compiler that checks the
+ // destination size.
+ v = (dictitem_T *)&fc->fixvar[fixvar_idx++];
+#ifndef __clang_analyzer__
+ name = v->di_key;
+ STRCPY(name, "000");
+#endif
+ v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX;
+ tv_dict_add(&fc->l_avars, v);
+ v->di_tv.v_type = VAR_LIST;
+ v->di_tv.v_lock = VAR_FIXED;
+ v->di_tv.vval.v_list = &fc->l_varlist;
+ tv_list_init_static(&fc->l_varlist);
+ tv_list_set_lock(&fc->l_varlist, VAR_FIXED);
+
+ // Set a:firstline to "firstline" and a:lastline to "lastline".
+ // Set a:name to named arguments.
+ // Set a:N to the "..." arguments.
+ add_nr_var(&fc->l_avars, (dictitem_T *)&fc->fixvar[fixvar_idx++],
+ "firstline", (varnumber_T)firstline);
+ add_nr_var(&fc->l_avars, (dictitem_T *)&fc->fixvar[fixvar_idx++],
+ "lastline", (varnumber_T)lastline);
+ for (int i = 0; i < argcount; i++) {
+ bool addlocal = false;
+
+ ai = i - fp->uf_args.ga_len;
+ if (ai < 0) {
+ // named argument a:name
+ name = FUNCARG(fp, i);
+ if (islambda) {
+ addlocal = true;
+ }
+ } else {
+ // "..." argument a:1, a:2, etc.
+ snprintf((char *)numbuf, sizeof(numbuf), "%d", ai + 1);
+ name = numbuf;
+ }
+ if (fixvar_idx < FIXVAR_CNT && STRLEN(name) <= VAR_SHORT_LEN) {
+ v = (dictitem_T *)&fc->fixvar[fixvar_idx++];
+ v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX;
+ } else {
+ v = xmalloc(sizeof(dictitem_T) + STRLEN(name));
+ v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX | DI_FLAGS_ALLOC;
+ }
+ STRCPY(v->di_key, name);
+
+ // Note: the values are copied directly to avoid alloc/free.
+ // "argvars" must have VAR_FIXED for v_lock.
+ v->di_tv = argvars[i];
+ v->di_tv.v_lock = VAR_FIXED;
+
+ if (addlocal) {
+ // Named arguments can be accessed without the "a:" prefix in lambda
+ // expressions. Add to the l: dict.
+ tv_copy(&v->di_tv, &v->di_tv);
+ tv_dict_add(&fc->l_vars, v);
+ } else {
+ tv_dict_add(&fc->l_avars, v);
+ }
+
+ if (ai >= 0 && ai < MAX_FUNC_ARGS) {
+ listitem_T *li = &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);
+ }
+ }
+
+ // Don't redraw while executing the function.
+ RedrawingDisabled++;
+ save_sourcing_name = sourcing_name;
+ save_sourcing_lnum = sourcing_lnum;
+ sourcing_lnum = 1;
+
+ if (fp->uf_flags & FC_SANDBOX) {
+ using_sandbox = true;
+ sandbox++;
+ }
+
+ // need space for new sourcing_name:
+ // * save_sourcing_name
+ // * "["number"].." or "function "
+ // * "<SNR>" + fp->uf_name - 3
+ // * terminating NUL
+ size_t len = (save_sourcing_name == NULL ? 0 : STRLEN(save_sourcing_name))
+ + STRLEN(fp->uf_name) + 27;
+ sourcing_name = xmalloc(len);
+ {
+ if (save_sourcing_name != NULL
+ && STRNCMP(save_sourcing_name, "function ", 9) == 0) {
+ vim_snprintf((char *)sourcing_name,
+ len,
+ "%s[%" PRId64 "]..",
+ save_sourcing_name,
+ (int64_t)save_sourcing_lnum);
+ } else {
+ STRCPY(sourcing_name, "function ");
+ }
+ cat_func_name(sourcing_name + STRLEN(sourcing_name), fp);
+
+ if (p_verbose >= 12) {
+ ++no_wait_return;
+ verbose_enter_scroll();
+
+ smsg(_("calling %s"), sourcing_name);
+ if (p_verbose >= 14) {
+ msg_puts("(");
+ for (int i = 0; i < argcount; i++) {
+ if (i > 0) {
+ msg_puts(", ");
+ }
+ if (argvars[i].v_type == VAR_NUMBER) {
+ msg_outnum((long)argvars[i].vval.v_number);
+ } else {
+ // Do not want errors such as E724 here.
+ emsg_off++;
+ char *tofree = encode_tv2string(&argvars[i], NULL);
+ emsg_off--;
+ if (tofree != NULL) {
+ char *s = tofree;
+ char buf[MSG_BUF_LEN];
+ if (vim_strsize((char_u *)s) > MSG_BUF_CLEN) {
+ trunc_string((char_u *)s, (char_u *)buf, MSG_BUF_CLEN,
+ sizeof(buf));
+ s = buf;
+ }
+ msg_puts(s);
+ xfree(tofree);
+ }
+ }
+ }
+ msg_puts(")");
+ }
+ msg_puts("\n"); // don't overwrite this either
+
+ verbose_leave_scroll();
+ --no_wait_return;
+ }
+ }
+
+ const bool do_profiling_yes = do_profiling == PROF_YES;
+
+ bool func_not_yet_profiling_but_should =
+ do_profiling_yes
+ && !fp->uf_profiling && has_profiling(false, fp->uf_name, NULL);
+
+ if (func_not_yet_profiling_but_should) {
+ started_profiling = true;
+ func_do_profile(fp);
+ }
+
+ bool func_or_func_caller_profiling =
+ do_profiling_yes
+ && (fp->uf_profiling
+ || (fc->caller != NULL && fc->caller->func->uf_profiling));
+
+ if (func_or_func_caller_profiling) {
+ ++fp->uf_tm_count;
+ call_start = profile_start();
+ fp->uf_tm_children = profile_zero();
+ }
+
+ if (do_profiling_yes) {
+ script_prof_save(&wait_start);
+ }
+
+ const sctx_T save_current_sctx = current_sctx;
+ current_sctx = fp->uf_script_ctx;
+ save_did_emsg = did_emsg;
+ did_emsg = FALSE;
+
+ // call do_cmdline() to execute the lines
+ do_cmdline(NULL, get_func_line, (void *)fc,
+ DOCMD_NOWAIT|DOCMD_VERBOSE|DOCMD_REPEAT);
+
+ --RedrawingDisabled;
+
+ // when the function was aborted because of an error, return -1
+ if ((did_emsg
+ && (fp->uf_flags & FC_ABORT)) || rettv->v_type == VAR_UNKNOWN) {
+ tv_clear(rettv);
+ rettv->v_type = VAR_NUMBER;
+ rettv->vval.v_number = -1;
+ }
+
+ if (func_or_func_caller_profiling) {
+ call_start = profile_end(call_start);
+ call_start = profile_sub_wait(wait_start, call_start); // -V614
+ fp->uf_tm_total = profile_add(fp->uf_tm_total, call_start);
+ fp->uf_tm_self = profile_self(fp->uf_tm_self, call_start,
+ fp->uf_tm_children);
+ if (fc->caller != NULL && fc->caller->func->uf_profiling) {
+ fc->caller->func->uf_tm_children =
+ profile_add(fc->caller->func->uf_tm_children, call_start);
+ fc->caller->func->uf_tml_children =
+ profile_add(fc->caller->func->uf_tml_children, call_start);
+ }
+ if (started_profiling) {
+ // make a ":profdel func" stop profiling the function
+ fp->uf_profiling = false;
+ }
+ }
+
+ // when being verbose, mention the return value
+ if (p_verbose >= 12) {
+ ++no_wait_return;
+ verbose_enter_scroll();
+
+ if (aborting())
+ smsg(_("%s aborted"), sourcing_name);
+ else if (fc->rettv->v_type == VAR_NUMBER)
+ smsg(_("%s returning #%" PRId64 ""),
+ sourcing_name, (int64_t)fc->rettv->vval.v_number);
+ else {
+ char_u buf[MSG_BUF_LEN];
+
+ // The value may be very long. Skip the middle part, so that we
+ // have some idea how it starts and ends. smsg() would always
+ // truncate it at the end. Don't want errors such as E724 here.
+ emsg_off++;
+ char_u *s = (char_u *) encode_tv2string(fc->rettv, NULL);
+ char_u *tofree = s;
+ emsg_off--;
+ if (s != NULL) {
+ if (vim_strsize(s) > MSG_BUF_CLEN) {
+ trunc_string(s, buf, MSG_BUF_CLEN, MSG_BUF_LEN);
+ s = buf;
+ }
+ smsg(_("%s returning %s"), sourcing_name, s);
+ xfree(tofree);
+ }
+ }
+ msg_puts("\n"); // don't overwrite this either
+
+ verbose_leave_scroll();
+ --no_wait_return;
+ }
+
+ xfree(sourcing_name);
+ sourcing_name = save_sourcing_name;
+ sourcing_lnum = save_sourcing_lnum;
+ current_sctx = save_current_sctx;
+ if (do_profiling_yes) {
+ script_prof_restore(&wait_start);
+ }
+ if (using_sandbox) {
+ sandbox--;
+ }
+
+ if (p_verbose >= 12 && sourcing_name != NULL) {
+ ++no_wait_return;
+ verbose_enter_scroll();
+
+ smsg(_("continuing in %s"), sourcing_name);
+ msg_puts("\n"); // don't overwrite this either
+
+ verbose_leave_scroll();
+ --no_wait_return;
+ }
+
+ did_emsg |= save_did_emsg;
+ depth--;
+
+ cleanup_function_call(fc);
+
+ if (--fp->uf_calls <= 0 && fp->uf_refcount <= 0) {
+ // Function was unreferenced while being used, free it now.
+ func_clear_free(fp, false);
+ }
+ // restore search patterns and redo buffer
+ if (did_save_redo) {
+ restoreRedobuff(&save_redo);
+ }
+ restore_search_patterns();
+}
+
+/// There are two kinds of function names:
+/// 1. ordinary names, function defined with :function
+/// 2. numbered functions and lambdas
+/// For the first we only count the name stored in func_hashtab as a reference,
+/// using function() does not count as a reference, because the function is
+/// looked up by name.
+static bool func_name_refcount(char_u *name)
+{
+ return isdigit(*name) || *name == '<';
+}
+
+static funccal_entry_T *funccal_stack = NULL;
+
+// Save the current function call pointer, and set it to NULL.
+// Used when executing autocommands and for ":source".
+void save_funccal(funccal_entry_T *entry)
+{
+ entry->top_funccal = current_funccal;
+ entry->next = funccal_stack;
+ funccal_stack = entry;
+ current_funccal = NULL;
+}
+
+void restore_funccal(void)
+{
+ if (funccal_stack == NULL) {
+ IEMSG("INTERNAL: restore_funccal()");
+ } else {
+ current_funccal = funccal_stack->top_funccal;
+ funccal_stack = funccal_stack->next;
+ }
+}
+
+funccall_T *get_current_funccal(void)
+{
+ return current_funccal;
+}
+
+void set_current_funccal(funccall_T *fc)
+{
+ current_funccal = fc;
+}
+
+#if defined(EXITFREE)
+void free_all_functions(void)
+{
+ hashitem_T *hi;
+ ufunc_T *fp;
+ uint64_t skipped = 0;
+ uint64_t todo = 1;
+ uint64_t used;
+
+ // Clean up the current_funccal chain and the funccal stack.
+ while (current_funccal != NULL) {
+ tv_clear(current_funccal->rettv);
+ cleanup_function_call(current_funccal);
+ if (current_funccal == NULL && funccal_stack != NULL) {
+ restore_funccal();
+ }
+ }
+
+ // First clear what the functions contain. Since this may lower the
+ // reference count of a function, it may also free a function and change
+ // the hash table. Restart if that happens.
+ while (todo > 0) {
+ todo = func_hashtab.ht_used;
+ for (hi = func_hashtab.ht_array; todo > 0; hi++) {
+ if (!HASHITEM_EMPTY(hi)) {
+ // Only free functions that are not refcounted, those are
+ // supposed to be freed when no longer referenced.
+ fp = HI2UF(hi);
+ if (func_name_refcount(fp->uf_name)) {
+ skipped++;
+ } else {
+ used = func_hashtab.ht_used;
+ func_clear(fp, true);
+ if (used != func_hashtab.ht_used) {
+ skipped = 0;
+ break;
+ }
+ }
+ todo--;
+ }
+ }
+ }
+
+ // Now actually free the functions. Need to start all over every time,
+ // because func_free() may change the hash table.
+ skipped = 0;
+ while (func_hashtab.ht_used > skipped) {
+ todo = func_hashtab.ht_used;
+ for (hi = func_hashtab.ht_array; todo > 0; hi++) {
+ if (!HASHITEM_EMPTY(hi)) {
+ todo--;
+ // Only free functions that are not refcounted, those are
+ // supposed to be freed when no longer referenced.
+ fp = HI2UF(hi);
+ if (func_name_refcount(fp->uf_name)) {
+ skipped++;
+ } else {
+ func_free(fp);
+ skipped = 0;
+ break;
+ }
+ }
+ }
+ }
+ if (skipped == 0) {
+ hash_clear(&func_hashtab);
+ }
+}
+
+#endif
+
+/// Checks if a builtin function with the given name exists.
+///
+/// @param[in] name name of the builtin function to check.
+/// @param[in] len length of "name", or -1 for NUL terminated.
+///
+/// @return true if "name" looks like a builtin function name: starts with a
+/// lower case letter and doesn't contain AUTOLOAD_CHAR.
+static bool builtin_function(const char *name, int len)
+{
+ if (!ASCII_ISLOWER(name[0])) {
+ return false;
+ }
+
+ const char *p = (len == -1
+ ? strchr(name, AUTOLOAD_CHAR)
+ : memchr(name, AUTOLOAD_CHAR, (size_t)len));
+
+ return p == NULL;
+}
+
+int func_call(char_u *name, typval_T *args, partial_T *partial,
+ dict_T *selfdict, typval_T *rettv)
+{
+ typval_T argv[MAX_FUNC_ARGS + 1];
+ int argc = 0;
+ int dummy;
+ int r = 0;
+
+ TV_LIST_ITER(args->vval.v_list, item, {
+ if (argc == MAX_FUNC_ARGS - (partial == NULL ? 0 : partial->pt_argc)) {
+ EMSG(_("E699: Too many arguments"));
+ goto func_call_skip_call;
+ }
+ // Make a copy of each argument. This is needed to be able to set
+ // v_lock to VAR_FIXED in the copy without changing the original list.
+ tv_copy(TV_LIST_ITEM_TV(item), &argv[argc++]);
+ });
+
+ r = call_func(name, (int)STRLEN(name), rettv, argc, argv, NULL,
+ curwin->w_cursor.lnum, curwin->w_cursor.lnum,
+ &dummy, true, partial, selfdict);
+
+func_call_skip_call:
+ // Free the arguments.
+ while (argc > 0) {
+ tv_clear(&argv[--argc]);
+ }
+
+ return r;
+}
+
+/// Call a function with its resolved parameters
+///
+/// "argv_func", when not NULL, can be used to fill in arguments only when the
+/// invoked function uses them. It is called like this:
+/// new_argcount = argv_func(current_argcount, argv, called_func_argcount)
+///
+/// @return FAIL if function cannot be called, else OK (even if an error
+/// occurred while executing the function! Set `msg_list` to capture
+/// the error, see do_cmdline()).
+int
+call_func(
+ const char_u *funcname, // name of the function
+ int len, // length of "name"
+ typval_T *rettv, // [out] value goes here
+ int argcount_in, // number of "argvars"
+ typval_T *argvars_in, // vars for arguments, must have "argcount"
+ // PLUS ONE elements!
+ ArgvFunc argv_func, // function to fill in argvars
+ linenr_T firstline, // first line of range
+ linenr_T lastline, // last line of range
+ int *doesrange, // [out] function handled range
+ bool evaluate,
+ partial_T *partial, // optional, can be NULL
+ dict_T *selfdict_in // Dictionary for "self"
+)
+ FUNC_ATTR_NONNULL_ARG(1, 3, 5, 9)
+{
+ int ret = FAIL;
+ int error = ERROR_NONE;
+ ufunc_T *fp;
+ char_u fname_buf[FLEN_FIXED + 1];
+ char_u *tofree = NULL;
+ char_u *fname;
+ char_u *name;
+ int argcount = argcount_in;
+ typval_T *argvars = argvars_in;
+ dict_T *selfdict = selfdict_in;
+ typval_T argv[MAX_FUNC_ARGS + 1]; // used when "partial" is not NULL
+ int argv_clear = 0;
+
+ // Initialize rettv so that it is safe for caller to invoke clear_tv(rettv)
+ // even when call_func() returns FAIL.
+ rettv->v_type = VAR_UNKNOWN;
+
+ // Make a copy of the name, if it comes from a funcref variable it could
+ // be changed or deleted in the called function.
+ name = vim_strnsave(funcname, len);
+
+ fname = fname_trans_sid(name, fname_buf, &tofree, &error);
+
+ *doesrange = false;
+
+ if (partial != NULL) {
+ // When the function has a partial with a dict and there is a dict
+ // argument, use the dict argument. That is backwards compatible.
+ // When the dict was bound explicitly use the one from the partial.
+ if (partial->pt_dict != NULL
+ && (selfdict_in == NULL || !partial->pt_auto)) {
+ selfdict = partial->pt_dict;
+ }
+ if (error == ERROR_NONE && partial->pt_argc > 0) {
+ for (argv_clear = 0; argv_clear < partial->pt_argc; argv_clear++) {
+ if (argv_clear + argcount_in >= MAX_FUNC_ARGS) {
+ error = ERROR_TOOMANY;
+ goto theend;
+ }
+ tv_copy(&partial->pt_argv[argv_clear], &argv[argv_clear]);
+ }
+ for (int i = 0; i < argcount_in; i++) {
+ argv[i + argv_clear] = argvars_in[i];
+ }
+ argvars = argv;
+ argcount = partial->pt_argc + argcount_in;
+ }
+ }
+
+ if (error == ERROR_NONE && evaluate) {
+ char_u *rfname = fname;
+
+ // Ignore "g:" before a function name.
+ if (fname[0] == 'g' && fname[1] == ':') {
+ rfname = fname + 2;
+ }
+
+ rettv->v_type = VAR_NUMBER; // default rettv is number zero
+ rettv->vval.v_number = 0;
+ error = ERROR_UNKNOWN;
+
+ if (is_luafunc(partial)) {
+ if (len > 0) {
+ error = ERROR_NONE;
+ executor_call_lua((const char *)funcname, len,
+ argvars, argcount, rettv);
+ }
+ } else if (!builtin_function((const char *)rfname, -1)) {
+ // User defined function.
+ if (partial != NULL && partial->pt_func != NULL) {
+ fp = partial->pt_func;
+ } else {
+ fp = find_func(rfname);
+ }
+
+ // Trigger FuncUndefined event, may load the function.
+ if (fp == NULL
+ && apply_autocmds(EVENT_FUNCUNDEFINED, rfname, rfname, TRUE, NULL)
+ && !aborting()) {
+ // executed an autocommand, search for the function again
+ fp = find_func(rfname);
+ }
+ // Try loading a package.
+ if (fp == NULL && script_autoload((const char *)rfname, STRLEN(rfname),
+ true) && !aborting()) {
+ // Loaded a package, search for the function again.
+ fp = find_func(rfname);
+ }
+
+ if (fp != NULL && (fp->uf_flags & FC_DELETED)) {
+ error = ERROR_DELETED;
+ } else if (fp != NULL && (fp->uf_flags & FC_CFUNC)) {
+ cfunc_T cb = fp->uf_cb;
+ error = (*cb)(argcount, argvars, rettv, fp->uf_cb_state);
+ } else if (fp != NULL) {
+ if (argv_func != NULL) {
+ // postponed filling in the arguments, do it now
+ argcount = argv_func(argcount, argvars, argv_clear,
+ fp->uf_args.ga_len);
+ }
+ if (fp->uf_flags & FC_RANGE) {
+ *doesrange = true;
+ }
+ if (argcount < fp->uf_args.ga_len) {
+ error = ERROR_TOOFEW;
+ } else if (!fp->uf_varargs && argcount > fp->uf_args.ga_len) {
+ error = ERROR_TOOMANY;
+ } else if ((fp->uf_flags & FC_DICT) && selfdict == NULL) {
+ error = ERROR_DICT;
+ } else {
+ // Call the user function.
+ call_user_func(fp, argcount, argvars, rettv, firstline, lastline,
+ (fp->uf_flags & FC_DICT) ? selfdict : NULL);
+ error = ERROR_NONE;
+ }
+ }
+ } else {
+ // Find the function name in the table, call its implementation.
+ const VimLFuncDef *const fdef = find_internal_func((const char *)fname);
+ if (fdef != NULL) {
+ if (argcount < fdef->min_argc) {
+ error = ERROR_TOOFEW;
+ } else if (argcount > fdef->max_argc) {
+ error = ERROR_TOOMANY;
+ } else {
+ argvars[argcount].v_type = VAR_UNKNOWN;
+ fdef->func(argvars, rettv, fdef->data);
+ error = ERROR_NONE;
+ }
+ }
+ }
+ /*
+ * The function call (or "FuncUndefined" autocommand sequence) might
+ * have been aborted by an error, an interrupt, or an explicitly thrown
+ * exception that has not been caught so far. This situation can be
+ * tested for by calling aborting(). For an error in an internal
+ * function or for the "E132" error in call_user_func(), however, the
+ * throw point at which the "force_abort" flag (temporarily reset by
+ * emsg()) is normally updated has not been reached yet. We need to
+ * update that flag first to make aborting() reliable.
+ */
+ update_force_abort();
+ }
+ if (error == ERROR_NONE)
+ ret = OK;
+
+theend:
+ // Report an error unless the argument evaluation or function call has been
+ // cancelled due to an aborting error, an interrupt, or an exception.
+ if (!aborting()) {
+ switch (error) {
+ case ERROR_UNKNOWN:
+ emsg_funcname(N_("E117: Unknown function: %s"), name);
+ break;
+ case ERROR_DELETED:
+ emsg_funcname(N_("E933: Function was deleted: %s"), name);
+ break;
+ case ERROR_TOOMANY:
+ emsg_funcname(_(e_toomanyarg), name);
+ break;
+ case ERROR_TOOFEW:
+ emsg_funcname(N_("E119: Not enough arguments for function: %s"),
+ name);
+ break;
+ case ERROR_SCRIPT:
+ emsg_funcname(N_("E120: Using <SID> not in a script context: %s"),
+ name);
+ break;
+ case ERROR_DICT:
+ emsg_funcname(N_("E725: Calling dict function without Dictionary: %s"),
+ name);
+ break;
+ }
+ }
+
+ while (argv_clear > 0) {
+ tv_clear(&argv[--argv_clear]);
+ }
+ xfree(tofree);
+ xfree(name);
+
+ return ret;
+}
+
+/// List the head of the function: "name(arg1, arg2)".
+///
+/// @param[in] fp Function pointer.
+/// @param[in] indent Indent line.
+/// @param[in] force Include bang "!" (i.e.: "function!").
+static void list_func_head(ufunc_T *fp, int indent, bool force)
+{
+ msg_start();
+ if (indent)
+ MSG_PUTS(" ");
+ MSG_PUTS(force ? "function! " : "function ");
+ if (fp->uf_name[0] == K_SPECIAL) {
+ MSG_PUTS_ATTR("<SNR>", HL_ATTR(HLF_8));
+ msg_puts((const char *)fp->uf_name + 3);
+ } else {
+ msg_puts((const char *)fp->uf_name);
+ }
+ msg_putchar('(');
+ int j;
+ for (j = 0; j < fp->uf_args.ga_len; j++) {
+ if (j) {
+ msg_puts(", ");
+ }
+ msg_puts((const char *)FUNCARG(fp, j));
+ }
+ if (fp->uf_varargs) {
+ if (j) {
+ msg_puts(", ");
+ }
+ msg_puts("...");
+ }
+ msg_putchar(')');
+ if (fp->uf_flags & FC_ABORT) {
+ msg_puts(" abort");
+ }
+ if (fp->uf_flags & FC_RANGE) {
+ msg_puts(" range");
+ }
+ if (fp->uf_flags & FC_DICT) {
+ msg_puts(" dict");
+ }
+ if (fp->uf_flags & FC_CLOSURE) {
+ msg_puts(" closure");
+ }
+ msg_clr_eos();
+ if (p_verbose > 0) {
+ last_set_msg(fp->uf_script_ctx);
+ }
+}
+
+/// Get a function name, translating "<SID>" and "<SNR>".
+/// Also handles a Funcref in a List or Dictionary.
+/// flags:
+/// TFN_INT: internal function name OK
+/// TFN_QUIET: be quiet
+/// TFN_NO_AUTOLOAD: do not use script autoloading
+/// TFN_NO_DEREF: do not dereference a Funcref
+/// Advances "pp" to just after the function name (if no error).
+///
+/// @return the function name in allocated memory, or NULL for failure.
+char_u *
+trans_function_name(
+ char_u **pp,
+ bool skip, // only find the end, don't evaluate
+ int flags,
+ funcdict_T *fdp, // return: info about dictionary used
+ partial_T **partial // return: partial of a FuncRef
+)
+ FUNC_ATTR_NONNULL_ARG(1)
+{
+ char_u *name = NULL;
+ const char_u *start;
+ const char_u *end;
+ int lead;
+ int len;
+ lval_T lv;
+
+ if (fdp != NULL)
+ memset(fdp, 0, sizeof(funcdict_T));
+ start = *pp;
+
+ /* Check for hard coded <SNR>: already translated function ID (from a user
+ * command). */
+ if ((*pp)[0] == K_SPECIAL && (*pp)[1] == KS_EXTRA
+ && (*pp)[2] == (int)KE_SNR) {
+ *pp += 3;
+ len = get_id_len((const char **)pp) + 3;
+ return (char_u *)xmemdupz(start, len);
+ }
+
+ /* A name starting with "<SID>" or "<SNR>" is local to a script. But
+ * don't skip over "s:", get_lval() needs it for "s:dict.func". */
+ lead = eval_fname_script((const char *)start);
+ if (lead > 2) {
+ start += lead;
+ }
+
+ // Note that TFN_ flags use the same values as GLV_ flags.
+ end = get_lval((char_u *)start, NULL, &lv, false, skip, flags | GLV_READ_ONLY,
+ lead > 2 ? 0 : FNE_CHECK_START);
+ if (end == start) {
+ if (!skip)
+ EMSG(_("E129: Function name required"));
+ goto theend;
+ }
+ if (end == NULL || (lv.ll_tv != NULL && (lead > 2 || lv.ll_range))) {
+ /*
+ * Report an invalid expression in braces, unless the expression
+ * evaluation has been cancelled due to an aborting error, an
+ * interrupt, or an exception.
+ */
+ if (!aborting()) {
+ if (end != NULL) {
+ emsgf(_(e_invarg2), start);
+ }
+ } else {
+ *pp = (char_u *)find_name_end(start, NULL, NULL, FNE_INCL_BR);
+ }
+ goto theend;
+ }
+
+ if (lv.ll_tv != NULL) {
+ if (fdp != NULL) {
+ fdp->fd_dict = lv.ll_dict;
+ fdp->fd_newkey = lv.ll_newkey;
+ lv.ll_newkey = NULL;
+ fdp->fd_di = lv.ll_di;
+ }
+ if (lv.ll_tv->v_type == VAR_FUNC && lv.ll_tv->vval.v_string != NULL) {
+ name = vim_strsave(lv.ll_tv->vval.v_string);
+ *pp = (char_u *)end;
+ } else if (lv.ll_tv->v_type == VAR_PARTIAL
+ && lv.ll_tv->vval.v_partial != NULL) {
+ if (is_luafunc(lv.ll_tv->vval.v_partial) && *end == '.') {
+ len = check_luafunc_name((const char *)end+1, true);
+ if (len == 0) {
+ EMSG2(e_invexpr2, "v:lua");
+ goto theend;
+ }
+ name = xmallocz(len);
+ memcpy(name, end+1, len);
+ *pp = (char_u *)end+1+len;
+ } else {
+ name = vim_strsave(partial_name(lv.ll_tv->vval.v_partial));
+ *pp = (char_u *)end;
+ }
+ if (partial != NULL) {
+ *partial = lv.ll_tv->vval.v_partial;
+ }
+ } else {
+ if (!skip && !(flags & TFN_QUIET) && (fdp == NULL
+ || lv.ll_dict == NULL
+ || fdp->fd_newkey == NULL)) {
+ EMSG(_(e_funcref));
+ } else {
+ *pp = (char_u *)end;
+ }
+ name = NULL;
+ }
+ goto theend;
+ }
+
+ if (lv.ll_name == NULL) {
+ // Error found, but continue after the function name.
+ *pp = (char_u *)end;
+ goto theend;
+ }
+
+ /* Check if the name is a Funcref. If so, use the value. */
+ if (lv.ll_exp_name != NULL) {
+ len = (int)strlen(lv.ll_exp_name);
+ name = deref_func_name(lv.ll_exp_name, &len, partial,
+ flags & TFN_NO_AUTOLOAD);
+ if ((const char *)name == lv.ll_exp_name) {
+ name = NULL;
+ }
+ } else if (!(flags & TFN_NO_DEREF)) {
+ len = (int)(end - *pp);
+ name = deref_func_name((const char *)(*pp), &len, partial,
+ flags & TFN_NO_AUTOLOAD);
+ if (name == *pp) {
+ name = NULL;
+ }
+ }
+ if (name != NULL) {
+ name = vim_strsave(name);
+ *pp = (char_u *)end;
+ if (strncmp((char *)name, "<SNR>", 5) == 0) {
+ // Change "<SNR>" to the byte sequence.
+ name[0] = K_SPECIAL;
+ name[1] = KS_EXTRA;
+ name[2] = (int)KE_SNR;
+ memmove(name + 3, name + 5, strlen((char *)name + 5) + 1);
+ }
+ goto theend;
+ }
+
+ if (lv.ll_exp_name != NULL) {
+ len = (int)strlen(lv.ll_exp_name);
+ if (lead <= 2 && lv.ll_name == lv.ll_exp_name
+ && lv.ll_name_len >= 2 && memcmp(lv.ll_name, "s:", 2) == 0) {
+ // When there was "s:" already or the name expanded to get a
+ // leading "s:" then remove it.
+ lv.ll_name += 2;
+ lv.ll_name_len -= 2;
+ len -= 2;
+ lead = 2;
+ }
+ } else {
+ // Skip over "s:" and "g:".
+ if (lead == 2 || (lv.ll_name[0] == 'g' && lv.ll_name[1] == ':')) {
+ lv.ll_name += 2;
+ lv.ll_name_len -= 2;
+ }
+ len = (int)((const char *)end - lv.ll_name);
+ }
+
+ size_t sid_buf_len = 0;
+ char sid_buf[20];
+
+ // Copy the function name to allocated memory.
+ // Accept <SID>name() inside a script, translate into <SNR>123_name().
+ // Accept <SNR>123_name() outside a script.
+ if (skip) {
+ lead = 0; // do nothing
+ } else if (lead > 0) {
+ lead = 3;
+ if ((lv.ll_exp_name != NULL && eval_fname_sid(lv.ll_exp_name))
+ || eval_fname_sid((const char *)(*pp))) {
+ // It's "s:" or "<SID>".
+ if (current_sctx.sc_sid <= 0) {
+ EMSG(_(e_usingsid));
+ goto theend;
+ }
+ sid_buf_len = snprintf(sid_buf, sizeof(sid_buf),
+ "%" PRIdSCID "_", current_sctx.sc_sid);
+ lead += sid_buf_len;
+ }
+ } else if (!(flags & TFN_INT)
+ && builtin_function(lv.ll_name, lv.ll_name_len)) {
+ EMSG2(_("E128: Function name must start with a capital or \"s:\": %s"),
+ start);
+ goto theend;
+ }
+
+ if (!skip && !(flags & TFN_QUIET) && !(flags & TFN_NO_DEREF)) {
+ char_u *cp = xmemrchr(lv.ll_name, ':', lv.ll_name_len);
+
+ if (cp != NULL && cp < end) {
+ EMSG2(_("E884: Function name cannot contain a colon: %s"), start);
+ goto theend;
+ }
+ }
+
+ name = xmalloc(len + lead + 1);
+ if (!skip && lead > 0) {
+ name[0] = K_SPECIAL;
+ name[1] = KS_EXTRA;
+ name[2] = (int)KE_SNR;
+ if (sid_buf_len > 0) { // If it's "<SID>"
+ memcpy(name + 3, sid_buf, sid_buf_len);
+ }
+ }
+ memmove(name + lead, lv.ll_name, len);
+ name[lead + len] = NUL;
+ *pp = (char_u *)end;
+
+theend:
+ clear_lval(&lv);
+ return name;
+}
+
+/*
+ * ":function"
+ */
+void ex_function(exarg_T *eap)
+{
+ char_u *theline;
+ char_u *line_to_free = NULL;
+ int c;
+ int saved_did_emsg;
+ int saved_wait_return = need_wait_return;
+ char_u *name = NULL;
+ char_u *p;
+ char_u *arg;
+ char_u *line_arg = NULL;
+ garray_T newargs;
+ garray_T newlines;
+ int varargs = false;
+ int flags = 0;
+ ufunc_T *fp;
+ bool overwrite = false;
+ int indent;
+ int nesting;
+ dictitem_T *v;
+ funcdict_T fudi;
+ static int func_nr = 0; // number for nameless function
+ int paren;
+ hashtab_T *ht;
+ int todo;
+ hashitem_T *hi;
+ linenr_T sourcing_lnum_off;
+ linenr_T sourcing_lnum_top;
+ bool is_heredoc = false;
+ char_u *skip_until = NULL;
+ char_u *heredoc_trimmed = NULL;
+ bool show_block = false;
+ bool do_concat = true;
+
+ /*
+ * ":function" without argument: list functions.
+ */
+ if (ends_excmd(*eap->arg)) {
+ if (!eap->skip) {
+ todo = (int)func_hashtab.ht_used;
+ for (hi = func_hashtab.ht_array; todo > 0 && !got_int; ++hi) {
+ if (!HASHITEM_EMPTY(hi)) {
+ --todo;
+ fp = HI2UF(hi);
+ if (message_filtered(fp->uf_name)) {
+ continue;
+ }
+ if (!func_name_refcount(fp->uf_name)) {
+ list_func_head(fp, false, false);
+ }
+ }
+ }
+ }
+ eap->nextcmd = check_nextcmd(eap->arg);
+ return;
+ }
+
+ /*
+ * ":function /pat": list functions matching pattern.
+ */
+ if (*eap->arg == '/') {
+ p = skip_regexp(eap->arg + 1, '/', TRUE, NULL);
+ if (!eap->skip) {
+ regmatch_T regmatch;
+
+ c = *p;
+ *p = NUL;
+ regmatch.regprog = vim_regcomp(eap->arg + 1, RE_MAGIC);
+ *p = c;
+ if (regmatch.regprog != NULL) {
+ regmatch.rm_ic = p_ic;
+
+ todo = (int)func_hashtab.ht_used;
+ for (hi = func_hashtab.ht_array; todo > 0 && !got_int; ++hi) {
+ if (!HASHITEM_EMPTY(hi)) {
+ --todo;
+ fp = HI2UF(hi);
+ if (!isdigit(*fp->uf_name)
+ && vim_regexec(&regmatch, fp->uf_name, 0))
+ list_func_head(fp, false, false);
+ }
+ }
+ vim_regfree(regmatch.regprog);
+ }
+ }
+ if (*p == '/')
+ ++p;
+ eap->nextcmd = check_nextcmd(p);
+ return;
+ }
+
+ // Get the function name. There are these situations:
+ // func function name
+ // "name" == func, "fudi.fd_dict" == NULL
+ // dict.func new dictionary entry
+ // "name" == NULL, "fudi.fd_dict" set,
+ // "fudi.fd_di" == NULL, "fudi.fd_newkey" == func
+ // dict.func existing dict entry with a Funcref
+ // "name" == func, "fudi.fd_dict" set,
+ // "fudi.fd_di" set, "fudi.fd_newkey" == NULL
+ // dict.func existing dict entry that's not a Funcref
+ // "name" == NULL, "fudi.fd_dict" set,
+ // "fudi.fd_di" set, "fudi.fd_newkey" == NULL
+ // s:func script-local function name
+ // g:func global function name, same as "func"
+ p = eap->arg;
+ name = trans_function_name(&p, eap->skip, TFN_NO_AUTOLOAD, &fudi, NULL);
+ paren = (vim_strchr(p, '(') != NULL);
+ if (name == NULL && (fudi.fd_dict == NULL || !paren) && !eap->skip) {
+ /*
+ * Return on an invalid expression in braces, unless the expression
+ * evaluation has been cancelled due to an aborting error, an
+ * interrupt, or an exception.
+ */
+ if (!aborting()) {
+ if (fudi.fd_newkey != NULL) {
+ EMSG2(_(e_dictkey), fudi.fd_newkey);
+ }
+ xfree(fudi.fd_newkey);
+ return;
+ } else
+ eap->skip = TRUE;
+ }
+
+ /* An error in a function call during evaluation of an expression in magic
+ * braces should not cause the function not to be defined. */
+ saved_did_emsg = did_emsg;
+ did_emsg = FALSE;
+
+ //
+ // ":function func" with only function name: list function.
+ // If bang is given:
+ // - include "!" in function head
+ // - exclude line numbers from function body
+ //
+ if (!paren) {
+ if (!ends_excmd(*skipwhite(p))) {
+ EMSG(_(e_trailing));
+ goto ret_free;
+ }
+ eap->nextcmd = check_nextcmd(p);
+ if (eap->nextcmd != NULL)
+ *p = NUL;
+ if (!eap->skip && !got_int) {
+ fp = find_func(name);
+ if (fp != NULL) {
+ list_func_head(fp, !eap->forceit, eap->forceit);
+ for (int j = 0; j < fp->uf_lines.ga_len && !got_int; j++) {
+ if (FUNCLINE(fp, j) == NULL) {
+ continue;
+ }
+ msg_putchar('\n');
+ if (!eap->forceit) {
+ msg_outnum((long)j + 1);
+ if (j < 9) {
+ msg_putchar(' ');
+ }
+ if (j < 99) {
+ msg_putchar(' ');
+ }
+ }
+ msg_prt_line(FUNCLINE(fp, j), false);
+ ui_flush(); // show a line at a time
+ os_breakcheck();
+ }
+ if (!got_int) {
+ msg_putchar('\n');
+ msg_puts(eap->forceit ? "endfunction" : " endfunction");
+ }
+ } else
+ emsg_funcname(N_("E123: Undefined function: %s"), name);
+ }
+ goto ret_free;
+ }
+
+ /*
+ * ":function name(arg1, arg2)" Define function.
+ */
+ p = skipwhite(p);
+ if (*p != '(') {
+ if (!eap->skip) {
+ EMSG2(_("E124: Missing '(': %s"), eap->arg);
+ goto ret_free;
+ }
+ // attempt to continue by skipping some text
+ if (vim_strchr(p, '(') != NULL) {
+ p = vim_strchr(p, '(');
+ }
+ }
+ p = skipwhite(p + 1);
+
+ ga_init(&newargs, (int)sizeof(char_u *), 3);
+ ga_init(&newlines, (int)sizeof(char_u *), 3);
+
+ if (!eap->skip) {
+ /* Check the name of the function. Unless it's a dictionary function
+ * (that we are overwriting). */
+ if (name != NULL)
+ arg = name;
+ else
+ arg = fudi.fd_newkey;
+ if (arg != NULL && (fudi.fd_di == NULL || !tv_is_func(fudi.fd_di->di_tv))) {
+ int j = (*arg == K_SPECIAL) ? 3 : 0;
+ while (arg[j] != NUL && (j == 0 ? eval_isnamec1(arg[j])
+ : eval_isnamec(arg[j])))
+ ++j;
+ if (arg[j] != NUL)
+ emsg_funcname((char *)e_invarg2, arg);
+ }
+ // Disallow using the g: dict.
+ if (fudi.fd_dict != NULL && fudi.fd_dict->dv_scope == VAR_DEF_SCOPE) {
+ EMSG(_("E862: Cannot use g: here"));
+ }
+ }
+
+ if (get_function_args(&p, ')', &newargs, &varargs, eap->skip) == FAIL) {
+ goto errret_2;
+ }
+
+ if (KeyTyped && ui_has(kUICmdline)) {
+ show_block = true;
+ ui_ext_cmdline_block_append(0, (const char *)eap->cmd);
+ }
+
+ // find extra arguments "range", "dict", "abort" and "closure"
+ for (;; ) {
+ p = skipwhite(p);
+ if (STRNCMP(p, "range", 5) == 0) {
+ flags |= FC_RANGE;
+ p += 5;
+ } else if (STRNCMP(p, "dict", 4) == 0) {
+ flags |= FC_DICT;
+ p += 4;
+ } else if (STRNCMP(p, "abort", 5) == 0) {
+ flags |= FC_ABORT;
+ p += 5;
+ } else if (STRNCMP(p, "closure", 7) == 0) {
+ flags |= FC_CLOSURE;
+ p += 7;
+ if (current_funccal == NULL) {
+ emsg_funcname(N_
+ ("E932: Closure function should not be at top level: %s"),
+ name == NULL ? (char_u *)"" : name);
+ goto erret;
+ }
+ } else {
+ break;
+ }
+ }
+
+ /* When there is a line break use what follows for the function body.
+ * Makes 'exe "func Test()\n...\nendfunc"' work. */
+ if (*p == '\n') {
+ line_arg = p + 1;
+ } else if (*p != NUL && *p != '"' && !eap->skip && !did_emsg) {
+ EMSG(_(e_trailing));
+ }
+
+ /*
+ * Read the body of the function, until ":endfunction" is found.
+ */
+ if (KeyTyped) {
+ /* Check if the function already exists, don't let the user type the
+ * whole function before telling him it doesn't work! For a script we
+ * need to skip the body to be able to find what follows. */
+ if (!eap->skip && !eap->forceit) {
+ if (fudi.fd_dict != NULL && fudi.fd_newkey == NULL)
+ EMSG(_(e_funcdict));
+ else if (name != NULL && find_func(name) != NULL)
+ emsg_funcname(e_funcexts, name);
+ }
+
+ if (!eap->skip && did_emsg)
+ goto erret;
+
+ if (!ui_has(kUICmdline)) {
+ msg_putchar('\n'); // don't overwrite the function name
+ }
+ cmdline_row = msg_row;
+ }
+
+ // Save the starting line number.
+ sourcing_lnum_top = sourcing_lnum;
+
+ indent = 2;
+ nesting = 0;
+ for (;; ) {
+ if (KeyTyped) {
+ msg_scroll = true;
+ saved_wait_return = false;
+ }
+ need_wait_return = false;
+
+ if (line_arg != NULL) {
+ // Use eap->arg, split up in parts by line breaks.
+ theline = line_arg;
+ p = vim_strchr(theline, '\n');
+ if (p == NULL)
+ line_arg += STRLEN(line_arg);
+ else {
+ *p = NUL;
+ line_arg = p + 1;
+ }
+ } else {
+ xfree(line_to_free);
+ if (eap->getline == NULL) {
+ theline = getcmdline(':', 0L, indent, do_concat);
+ } else {
+ theline = eap->getline(':', eap->cookie, indent, do_concat);
+ }
+ line_to_free = theline;
+ }
+ if (KeyTyped) {
+ lines_left = Rows - 1;
+ }
+ if (theline == NULL) {
+ EMSG(_("E126: Missing :endfunction"));
+ goto erret;
+ }
+ if (show_block) {
+ assert(indent >= 0);
+ ui_ext_cmdline_block_append((size_t)indent, (const char *)theline);
+ }
+
+ // Detect line continuation: sourcing_lnum increased more than one.
+ sourcing_lnum_off = get_sourced_lnum(eap->getline, eap->cookie);
+ if (sourcing_lnum < sourcing_lnum_off) {
+ sourcing_lnum_off -= sourcing_lnum;
+ } else {
+ sourcing_lnum_off = 0;
+ }
+
+ if (skip_until != NULL) {
+ // Don't check for ":endfunc" between
+ // * ":append" and "."
+ // * ":python <<EOF" and "EOF"
+ // * ":let {var-name} =<< [trim] {marker}" and "{marker}"
+ if (heredoc_trimmed == NULL
+ || (is_heredoc && skipwhite(theline) == theline)
+ || STRNCMP(theline, heredoc_trimmed,
+ STRLEN(heredoc_trimmed)) == 0) {
+ if (heredoc_trimmed == NULL) {
+ p = theline;
+ } else if (is_heredoc) {
+ p = skipwhite(theline) == theline
+ ? theline : theline + STRLEN(heredoc_trimmed);
+ } else {
+ p = theline + STRLEN(heredoc_trimmed);
+ }
+ if (STRCMP(p, skip_until) == 0) {
+ XFREE_CLEAR(skip_until);
+ XFREE_CLEAR(heredoc_trimmed);
+ do_concat = true;
+ is_heredoc = false;
+ }
+ }
+ } else {
+ // skip ':' and blanks
+ for (p = theline; ascii_iswhite(*p) || *p == ':'; p++) {
+ }
+
+ // Check for "endfunction".
+ if (checkforcmd(&p, "endfunction", 4) && nesting-- == 0) {
+ if (*p == '!') {
+ p++;
+ }
+ char_u *nextcmd = NULL;
+ if (*p == '|') {
+ nextcmd = p + 1;
+ } else if (line_arg != NULL && *skipwhite(line_arg) != NUL) {
+ nextcmd = line_arg;
+ } else if (*p != NUL && *p != '"' && p_verbose > 0) {
+ give_warning2((char_u *)_("W22: Text found after :endfunction: %s"),
+ p, true);
+ }
+ if (nextcmd != NULL) {
+ // Another command follows. If the line came from "eap" we
+ // can simply point into it, otherwise we need to change
+ // "eap->cmdlinep".
+ eap->nextcmd = nextcmd;
+ if (line_to_free != NULL) {
+ xfree(*eap->cmdlinep);
+ *eap->cmdlinep = line_to_free;
+ line_to_free = NULL;
+ }
+ }
+ break;
+ }
+
+ /* Increase indent inside "if", "while", "for" and "try", decrease
+ * at "end". */
+ if (indent > 2 && STRNCMP(p, "end", 3) == 0)
+ indent -= 2;
+ else if (STRNCMP(p, "if", 2) == 0
+ || STRNCMP(p, "wh", 2) == 0
+ || STRNCMP(p, "for", 3) == 0
+ || STRNCMP(p, "try", 3) == 0)
+ indent += 2;
+
+ // Check for defining a function inside this function.
+ if (checkforcmd(&p, "function", 2)) {
+ if (*p == '!') {
+ p = skipwhite(p + 1);
+ }
+ p += eval_fname_script((const char *)p);
+ xfree(trans_function_name(&p, true, 0, NULL, NULL));
+ if (*skipwhite(p) == '(') {
+ nesting++;
+ indent += 2;
+ }
+ }
+
+ // Check for ":append", ":change", ":insert".
+ p = skip_range(p, NULL);
+ if ((p[0] == 'a' && (!ASCII_ISALPHA(p[1]) || p[1] == 'p'))
+ || (p[0] == 'c'
+ && (!ASCII_ISALPHA(p[1])
+ || (p[1] == 'h' && (!ASCII_ISALPHA(p[2])
+ || (p[2] == 'a'
+ && (STRNCMP(&p[3], "nge", 3) != 0
+ || !ASCII_ISALPHA(p[6])))))))
+ || (p[0] == 'i'
+ && (!ASCII_ISALPHA(p[1]) || (p[1] == 'n'
+ && (!ASCII_ISALPHA(p[2])
+ || (p[2] == 's')))))) {
+ skip_until = vim_strsave((char_u *)".");
+ }
+
+ // heredoc: Check for ":python <<EOF", ":lua <<EOF", etc.
+ arg = skipwhite(skiptowhite(p));
+ if (arg[0] == '<' && arg[1] =='<'
+ && ((p[0] == 'p' && p[1] == 'y'
+ && (!ASCII_ISALNUM(p[2]) || p[2] == 't'
+ || ((p[2] == '3' || p[2] == 'x')
+ && !ASCII_ISALPHA(p[3]))))
+ || (p[0] == 'p' && p[1] == 'e'
+ && (!ASCII_ISALPHA(p[2]) || p[2] == 'r'))
+ || (p[0] == 't' && p[1] == 'c'
+ && (!ASCII_ISALPHA(p[2]) || p[2] == 'l'))
+ || (p[0] == 'l' && p[1] == 'u' && p[2] == 'a'
+ && !ASCII_ISALPHA(p[3]))
+ || (p[0] == 'r' && p[1] == 'u' && p[2] == 'b'
+ && (!ASCII_ISALPHA(p[3]) || p[3] == 'y'))
+ || (p[0] == 'm' && p[1] == 'z'
+ && (!ASCII_ISALPHA(p[2]) || p[2] == 's')))) {
+ // ":python <<" continues until a dot, like ":append"
+ p = skipwhite(arg + 2);
+ if (*p == NUL)
+ skip_until = vim_strsave((char_u *)".");
+ else
+ skip_until = vim_strsave(p);
+ }
+
+ // Check for ":let v =<< [trim] EOF"
+ // and ":let [a, b] =<< [trim] EOF"
+ arg = skipwhite(skiptowhite(p));
+ if (*arg == '[') {
+ arg = vim_strchr(arg, ']');
+ }
+ if (arg != NULL) {
+ arg = skipwhite(skiptowhite(arg));
+ if (arg[0] == '='
+ && arg[1] == '<'
+ && arg[2] =='<'
+ && (p[0] == 'l'
+ && p[1] == 'e'
+ && (!ASCII_ISALNUM(p[2])
+ || (p[2] == 't' && !ASCII_ISALNUM(p[3]))))) {
+ p = skipwhite(arg + 3);
+ if (STRNCMP(p, "trim", 4) == 0) {
+ // Ignore leading white space.
+ p = skipwhite(p + 4);
+ heredoc_trimmed =
+ vim_strnsave(theline, (int)(skipwhite(theline) - theline));
+ }
+ skip_until = vim_strnsave(p, (int)(skiptowhite(p) - p));
+ do_concat = false;
+ is_heredoc = true;
+ }
+ }
+ }
+
+ // Add the line to the function.
+ ga_grow(&newlines, 1 + sourcing_lnum_off);
+
+ /* Copy the line to newly allocated memory. get_one_sourceline()
+ * allocates 250 bytes per line, this saves 80% on average. The cost
+ * is an extra alloc/free. */
+ p = vim_strsave(theline);
+ ((char_u **)(newlines.ga_data))[newlines.ga_len++] = p;
+
+ /* Add NULL lines for continuation lines, so that the line count is
+ * equal to the index in the growarray. */
+ while (sourcing_lnum_off-- > 0)
+ ((char_u **)(newlines.ga_data))[newlines.ga_len++] = NULL;
+
+ // Check for end of eap->arg.
+ if (line_arg != NULL && *line_arg == NUL) {
+ line_arg = NULL;
+ }
+ }
+
+ /* Don't define the function when skipping commands or when an error was
+ * detected. */
+ if (eap->skip || did_emsg)
+ goto erret;
+
+ /*
+ * If there are no errors, add the function
+ */
+ if (fudi.fd_dict == NULL) {
+ v = find_var((const char *)name, STRLEN(name), &ht, false);
+ if (v != NULL && v->di_tv.v_type == VAR_FUNC) {
+ emsg_funcname(N_("E707: Function name conflicts with variable: %s"),
+ name);
+ goto erret;
+ }
+
+ fp = find_func(name);
+ if (fp != NULL) {
+ // Function can be replaced with "function!" and when sourcing the
+ // same script again, but only once.
+ if (!eap->forceit
+ && (fp->uf_script_ctx.sc_sid != current_sctx.sc_sid
+ || fp->uf_script_ctx.sc_seq == current_sctx.sc_seq)) {
+ emsg_funcname(e_funcexts, name);
+ goto erret;
+ }
+ if (fp->uf_calls > 0) {
+ emsg_funcname(N_("E127: Cannot redefine function %s: It is in use"),
+ name);
+ goto erret;
+ }
+ if (fp->uf_refcount > 1) {
+ // This function is referenced somewhere, don't redefine it but
+ // create a new one.
+ (fp->uf_refcount)--;
+ fp->uf_flags |= FC_REMOVED;
+ fp = NULL;
+ overwrite = true;
+ } else {
+ // redefine existing function
+ XFREE_CLEAR(name);
+ func_clear_items(fp);
+ fp->uf_profiling = false;
+ fp->uf_prof_initialized = false;
+ }
+ }
+ } else {
+ char numbuf[20];
+
+ fp = NULL;
+ if (fudi.fd_newkey == NULL && !eap->forceit) {
+ EMSG(_(e_funcdict));
+ goto erret;
+ }
+ if (fudi.fd_di == NULL) {
+ if (tv_check_lock(fudi.fd_dict->dv_lock, (const char *)eap->arg,
+ TV_CSTRING)) {
+ // Can't add a function to a locked dictionary
+ goto erret;
+ }
+ } else if (tv_check_lock(fudi.fd_di->di_tv.v_lock, (const char *)eap->arg,
+ TV_CSTRING)) {
+ // Can't change an existing function if it is locked
+ goto erret;
+ }
+
+ /* Give the function a sequential number. Can only be used with a
+ * Funcref! */
+ xfree(name);
+ sprintf(numbuf, "%d", ++func_nr);
+ name = vim_strsave((char_u *)numbuf);
+ }
+
+ if (fp == NULL) {
+ if (fudi.fd_dict == NULL && vim_strchr(name, AUTOLOAD_CHAR) != NULL) {
+ int slen, plen;
+ char_u *scriptname;
+
+ // Check that the autoload name matches the script name.
+ int j = FAIL;
+ if (sourcing_name != NULL) {
+ scriptname = (char_u *)autoload_name((const char *)name, STRLEN(name));
+ p = vim_strchr(scriptname, '/');
+ plen = (int)STRLEN(p);
+ slen = (int)STRLEN(sourcing_name);
+ if (slen > plen && fnamecmp(p,
+ sourcing_name + slen - plen) == 0)
+ j = OK;
+ xfree(scriptname);
+ }
+ if (j == FAIL) {
+ EMSG2(_(
+ "E746: Function name does not match script file name: %s"),
+ name);
+ goto erret;
+ }
+ }
+
+ fp = xcalloc(1, offsetof(ufunc_T, uf_name) + STRLEN(name) + 1);
+
+ if (fudi.fd_dict != NULL) {
+ if (fudi.fd_di == NULL) {
+ // Add new dict entry
+ fudi.fd_di = tv_dict_item_alloc((const char *)fudi.fd_newkey);
+ if (tv_dict_add(fudi.fd_dict, fudi.fd_di) == FAIL) {
+ xfree(fudi.fd_di);
+ xfree(fp);
+ goto erret;
+ }
+ } else {
+ // Overwrite existing dict entry.
+ tv_clear(&fudi.fd_di->di_tv);
+ }
+ fudi.fd_di->di_tv.v_type = VAR_FUNC;
+ fudi.fd_di->di_tv.vval.v_string = vim_strsave(name);
+
+ // behave like "dict" was used
+ flags |= FC_DICT;
+ }
+
+ // insert the new function in the function list
+ STRCPY(fp->uf_name, name);
+ if (overwrite) {
+ hi = hash_find(&func_hashtab, name);
+ hi->hi_key = UF2HIKEY(fp);
+ } else if (hash_add(&func_hashtab, UF2HIKEY(fp)) == FAIL) {
+ xfree(fp);
+ goto erret;
+ }
+ fp->uf_refcount = 1;
+ }
+ fp->uf_args = newargs;
+ fp->uf_lines = newlines;
+ if ((flags & FC_CLOSURE) != 0) {
+ register_closure(fp);
+ } else {
+ fp->uf_scoped = NULL;
+ }
+ if (prof_def_func()) {
+ func_do_profile(fp);
+ }
+ fp->uf_varargs = varargs;
+ if (sandbox) {
+ flags |= FC_SANDBOX;
+ }
+ fp->uf_flags = flags;
+ fp->uf_calls = 0;
+ fp->uf_script_ctx = current_sctx;
+ fp->uf_script_ctx.sc_lnum += sourcing_lnum_top;
+
+ goto ret_free;
+
+erret:
+ ga_clear_strings(&newargs);
+errret_2:
+ ga_clear_strings(&newlines);
+ret_free:
+ xfree(skip_until);
+ xfree(line_to_free);
+ xfree(fudi.fd_newkey);
+ xfree(name);
+ did_emsg |= saved_did_emsg;
+ need_wait_return |= saved_wait_return;
+ if (show_block) {
+ ui_ext_cmdline_block_leave();
+ }
+} // NOLINT(readability/fn_size)
+
+/*
+ * Return 5 if "p" starts with "<SID>" or "<SNR>" (ignoring case).
+ * Return 2 if "p" starts with "s:".
+ * Return 0 otherwise.
+ */
+int eval_fname_script(const char *const p)
+{
+ // Use mb_strnicmp() because in Turkish comparing the "I" may not work with
+ // the standard library function.
+ if (p[0] == '<'
+ && (mb_strnicmp((char_u *)p + 1, (char_u *)"SID>", 4) == 0
+ || mb_strnicmp((char_u *)p + 1, (char_u *)"SNR>", 4) == 0)) {
+ return 5;
+ }
+ if (p[0] == 's' && p[1] == ':') {
+ return 2;
+ }
+ return 0;
+}
+
+bool translated_function_exists(const char *name)
+{
+ if (builtin_function(name, -1)) {
+ return find_internal_func((char *)name) != NULL;
+ }
+ return find_func((const char_u *)name) != NULL;
+}
+
+/// Check whether function with the given name exists
+///
+/// @param[in] name Function name.
+/// @param[in] no_deref Whether to dereference a Funcref.
+///
+/// @return True if it exists, false otherwise.
+bool function_exists(const char *const name, bool no_deref)
+{
+ const char_u *nm = (const char_u *)name;
+ bool n = false;
+ int flag = TFN_INT | TFN_QUIET | TFN_NO_AUTOLOAD;
+
+ if (no_deref) {
+ flag |= TFN_NO_DEREF;
+ }
+ char *const p = (char *)trans_function_name((char_u **)&nm, false, flag, NULL,
+ NULL);
+ nm = skipwhite(nm);
+
+ /* Only accept "funcname", "funcname ", "funcname (..." and
+ * "funcname(...", not "funcname!...". */
+ if (p != NULL && (*nm == NUL || *nm == '(')) {
+ n = translated_function_exists(p);
+ }
+ xfree(p);
+ return n;
+}
+
+/*
+ * Function given to ExpandGeneric() to obtain the list of user defined
+ * function names.
+ */
+char_u *get_user_func_name(expand_T *xp, int idx)
+{
+ static size_t done;
+ static hashitem_T *hi;
+ ufunc_T *fp;
+
+ if (idx == 0) {
+ done = 0;
+ hi = func_hashtab.ht_array;
+ }
+ assert(hi);
+ if (done < func_hashtab.ht_used) {
+ if (done++ > 0)
+ ++hi;
+ while (HASHITEM_EMPTY(hi))
+ ++hi;
+ fp = HI2UF(hi);
+
+ if ((fp->uf_flags & FC_DICT)
+ || STRNCMP(fp->uf_name, "<lambda>", 8) == 0) {
+ return (char_u *)""; // don't show dict and lambda functions
+ }
+
+ if (STRLEN(fp->uf_name) + 4 >= IOSIZE) {
+ return fp->uf_name; // Prevent overflow.
+ }
+
+ cat_func_name(IObuff, fp);
+ if (xp->xp_context != EXPAND_USER_FUNC) {
+ STRCAT(IObuff, "(");
+ if (!fp->uf_varargs && GA_EMPTY(&fp->uf_args))
+ STRCAT(IObuff, ")");
+ }
+ return IObuff;
+ }
+ return NULL;
+}
+
+/// ":delfunction {name}"
+void ex_delfunction(exarg_T *eap)
+{
+ ufunc_T *fp = NULL;
+ char_u *p;
+ char_u *name;
+ funcdict_T fudi;
+
+ p = eap->arg;
+ name = trans_function_name(&p, eap->skip, 0, &fudi, NULL);
+ xfree(fudi.fd_newkey);
+ if (name == NULL) {
+ if (fudi.fd_dict != NULL && !eap->skip)
+ EMSG(_(e_funcref));
+ return;
+ }
+ if (!ends_excmd(*skipwhite(p))) {
+ xfree(name);
+ EMSG(_(e_trailing));
+ return;
+ }
+ eap->nextcmd = check_nextcmd(p);
+ if (eap->nextcmd != NULL)
+ *p = NUL;
+
+ if (!eap->skip)
+ fp = find_func(name);
+ xfree(name);
+
+ if (!eap->skip) {
+ if (fp == NULL) {
+ if (!eap->forceit) {
+ EMSG2(_(e_nofunc), eap->arg);
+ }
+ return;
+ }
+ if (fp->uf_calls > 0) {
+ EMSG2(_("E131: Cannot delete function %s: It is in use"), eap->arg);
+ return;
+ }
+ // check `uf_refcount > 2` because deleting a function should also reduce
+ // the reference count, and 1 is the initial refcount.
+ if (fp->uf_refcount > 2) {
+ EMSG2(_("Cannot delete function %s: It is being used internally"),
+ eap->arg);
+ return;
+ }
+
+ if (fudi.fd_dict != NULL) {
+ // Delete the dict item that refers to the function, it will
+ // invoke func_unref() and possibly delete the function.
+ tv_dict_item_remove(fudi.fd_dict, fudi.fd_di);
+ } else {
+ // A normal function (not a numbered function or lambda) has a
+ // refcount of 1 for the entry in the hashtable. When deleting
+ // it and the refcount is more than one, it should be kept.
+ // A numbered function or lambda should be kept if the refcount is
+ // one or more.
+ if (fp->uf_refcount > (func_name_refcount(fp->uf_name) ? 0 : 1)) {
+ // Function is still referenced somewhere. Don't free it but
+ // do remove it from the hashtable.
+ if (func_remove(fp)) {
+ fp->uf_refcount--;
+ }
+ fp->uf_flags |= FC_DELETED;
+ } else {
+ func_clear_free(fp, false);
+ }
+ }
+ }
+}
+
+/*
+ * Unreference a Function: decrement the reference count and free it when it
+ * becomes zero.
+ */
+void func_unref(char_u *name)
+{
+ ufunc_T *fp = NULL;
+
+ if (name == NULL || !func_name_refcount(name)) {
+ return;
+ }
+
+ fp = find_func(name);
+ if (fp == NULL && isdigit(*name)) {
+#ifdef EXITFREE
+ if (!entered_free_all_mem) {
+ internal_error("func_unref()");
+ abort();
+ }
+#else
+ internal_error("func_unref()");
+ abort();
+#endif
+ }
+ func_ptr_unref(fp);
+}
+
+/// Unreference a Function: decrement the reference count and free it when it
+/// becomes zero.
+/// Unreference user function, freeing it if needed
+///
+/// Decrements the reference count and frees when it becomes zero.
+///
+/// @param fp Function to unreference.
+void func_ptr_unref(ufunc_T *fp)
+{
+ if (fp != NULL && --fp->uf_refcount <= 0) {
+ // Only delete it when it's not being used. Otherwise it's done
+ // when "uf_calls" becomes zero.
+ if (fp->uf_calls == 0) {
+ func_clear_free(fp, false);
+ }
+ }
+}
+
+/// Count a reference to a Function.
+void func_ref(char_u *name)
+{
+ ufunc_T *fp;
+
+ if (name == NULL || !func_name_refcount(name)) {
+ return;
+ }
+ fp = find_func(name);
+ if (fp != NULL) {
+ (fp->uf_refcount)++;
+ } else if (isdigit(*name)) {
+ // Only give an error for a numbered function.
+ // Fail silently, when named or lambda function isn't found.
+ internal_error("func_ref()");
+ }
+}
+
+/// Count a reference to a Function.
+void func_ptr_ref(ufunc_T *fp)
+{
+ if (fp != NULL) {
+ (fp->uf_refcount)++;
+ }
+}
+
+/// Check whether funccall is still referenced outside
+///
+/// It is supposed to be referenced if either it is referenced itself or if l:,
+/// a: or a:000 are referenced as all these are statically allocated within
+/// funccall structure.
+static inline bool fc_referenced(const funccall_T *const fc)
+ FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
+ FUNC_ATTR_NONNULL_ALL
+{
+ return ((fc->l_varlist.lv_refcount // NOLINT(runtime/deprecated)
+ != DO_NOT_FREE_CNT)
+ || fc->l_vars.dv_refcount != DO_NOT_FREE_CNT
+ || fc->l_avars.dv_refcount != DO_NOT_FREE_CNT
+ || fc->fc_refcount > 0);
+}
+
+/// @return true if items in "fc" do not have "copyID". That means they are not
+/// referenced from anywhere that is in use.
+static int can_free_funccal(funccall_T *fc, int copyID)
+{
+ return fc->l_varlist.lv_copyID != copyID
+ && fc->l_vars.dv_copyID != copyID
+ && fc->l_avars.dv_copyID != copyID
+ && fc->fc_copyID != copyID;
+}
+
+/*
+ * ":return [expr]"
+ */
+void ex_return(exarg_T *eap)
+{
+ char_u *arg = eap->arg;
+ typval_T rettv;
+ int returning = FALSE;
+
+ if (current_funccal == NULL) {
+ EMSG(_("E133: :return not inside a function"));
+ return;
+ }
+
+ if (eap->skip)
+ ++emsg_skip;
+
+ eap->nextcmd = NULL;
+ if ((*arg != NUL && *arg != '|' && *arg != '\n')
+ && eval0(arg, &rettv, &eap->nextcmd, !eap->skip) != FAIL) {
+ if (!eap->skip) {
+ returning = do_return(eap, false, true, &rettv);
+ } else {
+ tv_clear(&rettv);
+ }
+ } else if (!eap->skip) { // It's safer to return also on error.
+ // In return statement, cause_abort should be force_abort.
+ update_force_abort();
+
+ // Return unless the expression evaluation has been cancelled due to an
+ // aborting error, an interrupt, or an exception.
+ if (!aborting()) {
+ returning = do_return(eap, false, true, NULL);
+ }
+ }
+
+ /* When skipping or the return gets pending, advance to the next command
+ * in this line (!returning). Otherwise, ignore the rest of the line.
+ * Following lines will be ignored by get_func_line(). */
+ if (returning) {
+ eap->nextcmd = NULL;
+ } else if (eap->nextcmd == NULL) { // no argument
+ eap->nextcmd = check_nextcmd(arg);
+ }
+
+ if (eap->skip)
+ --emsg_skip;
+}
+
+// TODO(ZyX-I): move to eval/ex_cmds
+
+/*
+ * ":1,25call func(arg1, arg2)" function call.
+ */
+void ex_call(exarg_T *eap)
+{
+ char_u *arg = eap->arg;
+ char_u *startarg;
+ char_u *name;
+ char_u *tofree;
+ int len;
+ typval_T rettv;
+ linenr_T lnum;
+ int doesrange;
+ bool failed = false;
+ funcdict_T fudi;
+ partial_T *partial = NULL;
+
+ if (eap->skip) {
+ // trans_function_name() doesn't work well when skipping, use eval0()
+ // instead to skip to any following command, e.g. for:
+ // :if 0 | call dict.foo().bar() | endif.
+ emsg_skip++;
+ if (eval0(eap->arg, &rettv, &eap->nextcmd, false) != FAIL) {
+ tv_clear(&rettv);
+ }
+ emsg_skip--;
+ return;
+ }
+
+ tofree = trans_function_name(&arg, false, TFN_INT, &fudi, &partial);
+ if (fudi.fd_newkey != NULL) {
+ // Still need to give an error message for missing key.
+ EMSG2(_(e_dictkey), fudi.fd_newkey);
+ xfree(fudi.fd_newkey);
+ }
+ if (tofree == NULL) {
+ return;
+ }
+
+ // Increase refcount on dictionary, it could get deleted when evaluating
+ // the arguments.
+ if (fudi.fd_dict != NULL) {
+ fudi.fd_dict->dv_refcount++;
+ }
+
+ // If it is the name of a variable of type VAR_FUNC or VAR_PARTIAL use its
+ // contents. For VAR_PARTIAL get its partial, unless we already have one
+ // from trans_function_name().
+ len = (int)STRLEN(tofree);
+ name = deref_func_name((const char *)tofree, &len,
+ partial != NULL ? NULL : &partial, false);
+
+ // Skip white space to allow ":call func ()". Not good, but required for
+ // backward compatibility.
+ startarg = skipwhite(arg);
+ rettv.v_type = VAR_UNKNOWN; // tv_clear() uses this.
+
+ if (*startarg != '(') {
+ EMSG2(_("E107: Missing parentheses: %s"), eap->arg);
+ goto end;
+ }
+
+ lnum = eap->line1;
+ for (; lnum <= eap->line2; lnum++) {
+ if (eap->addr_count > 0) { // -V560
+ if (lnum > curbuf->b_ml.ml_line_count) {
+ // If the function deleted lines or switched to another buffer
+ // the line number may become invalid.
+ EMSG(_(e_invrange));
+ break;
+ }
+ curwin->w_cursor.lnum = lnum;
+ curwin->w_cursor.col = 0;
+ curwin->w_cursor.coladd = 0;
+ }
+ arg = startarg;
+ if (get_func_tv(name, (int)STRLEN(name), &rettv, &arg,
+ eap->line1, eap->line2, &doesrange,
+ true, partial, fudi.fd_dict) == FAIL) {
+ failed = true;
+ break;
+ }
+
+ // Handle a function returning a Funcref, Dictionary or List.
+ if (handle_subscript((const char **)&arg, &rettv, true, true)
+ == FAIL) {
+ failed = true;
+ break;
+ }
+
+ tv_clear(&rettv);
+ if (doesrange) {
+ break;
+ }
+
+ // Stop when immediately aborting on error, or when an interrupt
+ // occurred or an exception was thrown but not caught.
+ // get_func_tv() returned OK, so that the check for trailing
+ // characters below is executed.
+ if (aborting()) {
+ break;
+ }
+ }
+
+ // When inside :try we need to check for following "| catch".
+ if (!failed || eap->cstack->cs_trylevel > 0) {
+ // Check for trailing illegal characters and a following command.
+ if (!ends_excmd(*arg)) {
+ emsg_severe = TRUE;
+ EMSG(_(e_trailing));
+ } else {
+ eap->nextcmd = check_nextcmd(arg);
+ }
+ }
+
+end:
+ tv_dict_unref(fudi.fd_dict);
+ xfree(tofree);
+}
+
+/*
+ * Return from a function. Possibly makes the return pending. Also called
+ * for a pending return at the ":endtry" or after returning from an extra
+ * do_cmdline(). "reanimate" is used in the latter case. "is_cmd" is set
+ * when called due to a ":return" command. "rettv" may point to a typval_T
+ * with the return rettv. Returns TRUE when the return can be carried out,
+ * FALSE when the return gets pending.
+ */
+int do_return(exarg_T *eap, int reanimate, int is_cmd, void *rettv)
+{
+ int idx;
+ cstack_T *const cstack = eap->cstack;
+
+ if (reanimate) {
+ // Undo the return.
+ current_funccal->returned = false;
+ }
+
+ /*
+ * Cleanup (and inactivate) conditionals, but stop when a try conditional
+ * not in its finally clause (which then is to be executed next) is found.
+ * In this case, make the ":return" pending for execution at the ":endtry".
+ * Otherwise, return normally.
+ */
+ idx = cleanup_conditionals(eap->cstack, 0, TRUE);
+ if (idx >= 0) {
+ cstack->cs_pending[idx] = CSTP_RETURN;
+
+ if (!is_cmd && !reanimate)
+ /* A pending return again gets pending. "rettv" points to an
+ * allocated variable with the rettv of the original ":return"'s
+ * argument if present or is NULL else. */
+ cstack->cs_rettv[idx] = rettv;
+ else {
+ /* When undoing a return in order to make it pending, get the stored
+ * return rettv. */
+ if (reanimate) {
+ assert(current_funccal->rettv);
+ rettv = current_funccal->rettv;
+ }
+
+ if (rettv != NULL) {
+ // Store the value of the pending return.
+ cstack->cs_rettv[idx] = xcalloc(1, sizeof(typval_T));
+ *(typval_T *)cstack->cs_rettv[idx] = *(typval_T *)rettv;
+ } else
+ cstack->cs_rettv[idx] = NULL;
+
+ if (reanimate) {
+ /* The pending return value could be overwritten by a ":return"
+ * without argument in a finally clause; reset the default
+ * return value. */
+ current_funccal->rettv->v_type = VAR_NUMBER;
+ current_funccal->rettv->vval.v_number = 0;
+ }
+ }
+ report_make_pending(CSTP_RETURN, rettv);
+ } else {
+ current_funccal->returned = TRUE;
+
+ /* If the return is carried out now, store the return value. For
+ * a return immediately after reanimation, the value is already
+ * there. */
+ if (!reanimate && rettv != NULL) {
+ tv_clear(current_funccal->rettv);
+ *current_funccal->rettv = *(typval_T *)rettv;
+ if (!is_cmd)
+ xfree(rettv);
+ }
+ }
+
+ return idx < 0;
+}
+
+/*
+ * Generate a return command for producing the value of "rettv". The result
+ * is an allocated string. Used by report_pending() for verbose messages.
+ */
+char_u *get_return_cmd(void *rettv)
+{
+ char_u *s = NULL;
+ char_u *tofree = NULL;
+
+ if (rettv != NULL) {
+ tofree = s = (char_u *) encode_tv2echo((typval_T *) rettv, NULL);
+ }
+ if (s == NULL) {
+ s = (char_u *)"";
+ }
+
+ STRCPY(IObuff, ":return ");
+ STRLCPY(IObuff + 8, s, IOSIZE - 8);
+ if (STRLEN(s) + 8 >= IOSIZE)
+ STRCPY(IObuff + IOSIZE - 4, "...");
+ xfree(tofree);
+ return vim_strsave(IObuff);
+}
+
+/*
+ * Get next function line.
+ * Called by do_cmdline() to get the next line.
+ * Returns allocated string, or NULL for end of function.
+ */
+char_u *get_func_line(int c, void *cookie, int indent, bool do_concat)
+{
+ funccall_T *fcp = (funccall_T *)cookie;
+ ufunc_T *fp = fcp->func;
+ char_u *retval;
+ garray_T *gap; // growarray with function lines
+
+ // If breakpoints have been added/deleted need to check for it.
+ if (fcp->dbg_tick != debug_tick) {
+ fcp->breakpoint = dbg_find_breakpoint(FALSE, fp->uf_name,
+ sourcing_lnum);
+ fcp->dbg_tick = debug_tick;
+ }
+ if (do_profiling == PROF_YES)
+ func_line_end(cookie);
+
+ gap = &fp->uf_lines;
+ if (((fp->uf_flags & FC_ABORT) && did_emsg && !aborted_in_try())
+ || fcp->returned) {
+ retval = NULL;
+ } else {
+ // Skip NULL lines (continuation lines).
+ while (fcp->linenr < gap->ga_len
+ && ((char_u **)(gap->ga_data))[fcp->linenr] == NULL) {
+ fcp->linenr++;
+ }
+ if (fcp->linenr >= gap->ga_len) {
+ retval = NULL;
+ } else {
+ retval = vim_strsave(((char_u **)(gap->ga_data))[fcp->linenr++]);
+ sourcing_lnum = fcp->linenr;
+ if (do_profiling == PROF_YES)
+ func_line_start(cookie);
+ }
+ }
+
+ // Did we encounter a breakpoint?
+ if (fcp->breakpoint != 0 && fcp->breakpoint <= sourcing_lnum) {
+ dbg_breakpoint(fp->uf_name, sourcing_lnum);
+ // Find next breakpoint.
+ fcp->breakpoint = dbg_find_breakpoint(false, fp->uf_name,
+ sourcing_lnum);
+ fcp->dbg_tick = debug_tick;
+ }
+
+ return retval;
+}
+
+/*
+ * Return TRUE if the currently active function should be ended, because a
+ * return was encountered or an error occurred. Used inside a ":while".
+ */
+int func_has_ended(void *cookie)
+{
+ funccall_T *fcp = (funccall_T *)cookie;
+
+ /* Ignore the "abort" flag if the abortion behavior has been changed due to
+ * an error inside a try conditional. */
+ return ((fcp->func->uf_flags & FC_ABORT) && did_emsg && !aborted_in_try())
+ || fcp->returned;
+}
+
+/*
+ * return TRUE if cookie indicates a function which "abort"s on errors.
+ */
+int func_has_abort(void *cookie)
+{
+ return ((funccall_T *)cookie)->func->uf_flags & FC_ABORT;
+}
+
+/// Turn "dict.Func" into a partial for "Func" bound to "dict".
+/// Changes "rettv" in-place.
+void make_partial(dict_T *const selfdict, typval_T *const rettv)
+{
+ char_u *fname;
+ char_u *tofree = NULL;
+ ufunc_T *fp;
+ char_u fname_buf[FLEN_FIXED + 1];
+ int error;
+
+ if (rettv->v_type == VAR_PARTIAL && rettv->vval.v_partial->pt_func != NULL) {
+ fp = rettv->vval.v_partial->pt_func;
+ } else {
+ fname = rettv->v_type == VAR_FUNC || rettv->v_type == VAR_STRING
+ ? rettv->vval.v_string
+ : rettv->vval.v_partial->pt_name;
+ // Translate "s:func" to the stored function name.
+ fname = fname_trans_sid(fname, fname_buf, &tofree, &error);
+ fp = find_func(fname);
+ xfree(tofree);
+ }
+
+ // Turn "dict.Func" into a partial for "Func" with "dict".
+ if (fp != NULL && (fp->uf_flags & FC_DICT)) {
+ partial_T *pt = (partial_T *)xcalloc(1, sizeof(partial_T));
+ pt->pt_refcount = 1;
+ pt->pt_dict = selfdict;
+ (selfdict->dv_refcount)++;
+ pt->pt_auto = true;
+ if (rettv->v_type == VAR_FUNC || rettv->v_type == VAR_STRING) {
+ // Just a function: Take over the function name and use selfdict.
+ pt->pt_name = rettv->vval.v_string;
+ } else {
+ partial_T *ret_pt = rettv->vval.v_partial;
+ int i;
+
+ // Partial: copy the function name, use selfdict and copy
+ // args. Can't take over name or args, the partial might
+ // be referenced elsewhere.
+ if (ret_pt->pt_name != NULL) {
+ pt->pt_name = vim_strsave(ret_pt->pt_name);
+ func_ref(pt->pt_name);
+ } else {
+ pt->pt_func = ret_pt->pt_func;
+ func_ptr_ref(pt->pt_func);
+ }
+ if (ret_pt->pt_argc > 0) {
+ size_t arg_size = sizeof(typval_T) * ret_pt->pt_argc;
+ pt->pt_argv = (typval_T *)xmalloc(arg_size);
+ pt->pt_argc = ret_pt->pt_argc;
+ for (i = 0; i < pt->pt_argc; i++) {
+ tv_copy(&ret_pt->pt_argv[i], &pt->pt_argv[i]);
+ }
+ }
+ partial_unref(ret_pt);
+ }
+ rettv->v_type = VAR_PARTIAL;
+ rettv->vval.v_partial = pt;
+ }
+}
+
+/*
+ * Return the name of the executed function.
+ */
+char_u *func_name(void *cookie)
+{
+ return ((funccall_T *)cookie)->func->uf_name;
+}
+
+/*
+ * Return the address holding the next breakpoint line for a funccall cookie.
+ */
+linenr_T *func_breakpoint(void *cookie)
+{
+ return &((funccall_T *)cookie)->breakpoint;
+}
+
+/*
+ * Return the address holding the debug tick for a funccall cookie.
+ */
+int *func_dbg_tick(void *cookie)
+{
+ return &((funccall_T *)cookie)->dbg_tick;
+}
+
+/*
+ * Return the nesting level for a funccall cookie.
+ */
+int func_level(void *cookie)
+{
+ return ((funccall_T *)cookie)->level;
+}
+
+/*
+ * Return TRUE when a function was ended by a ":return" command.
+ */
+int current_func_returned(void)
+{
+ return current_funccal->returned;
+}
+
+bool free_unref_funccal(int copyID, int testing)
+{
+ bool did_free = false;
+ bool did_free_funccal = false;
+
+ for (funccall_T **pfc = &previous_funccal; *pfc != NULL;) {
+ if (can_free_funccal(*pfc, copyID)) {
+ funccall_T *fc = *pfc;
+ *pfc = fc->caller;
+ free_funccal_contents(fc);
+ did_free = true;
+ did_free_funccal = true;
+ } else {
+ pfc = &(*pfc)->caller;
+ }
+ }
+ if (did_free_funccal) {
+ // When a funccal was freed some more items might be garbage
+ // collected, so run again.
+ (void)garbage_collect(testing);
+ }
+ return did_free;
+}
+
+// Get function call environment based on backtrace debug level
+funccall_T *get_funccal(void)
+{
+ funccall_T *funccal = current_funccal;
+ if (debug_backtrace_level > 0) {
+ for (int i = 0; i < debug_backtrace_level; i++) {
+ funccall_T *temp_funccal = funccal->caller;
+ if (temp_funccal) {
+ funccal = temp_funccal;
+ } else {
+ // backtrace level overflow. reset to max
+ debug_backtrace_level = i;
+ }
+ }
+ }
+
+ return funccal;
+}
+
+/// Return the hashtable used for local variables in the current funccal.
+/// Return NULL if there is no current funccal.
+hashtab_T *get_funccal_local_ht(void)
+{
+ if (current_funccal == NULL) {
+ return NULL;
+ }
+ return &get_funccal()->l_vars.dv_hashtab;
+}
+
+/// Return the l: scope variable.
+/// Return NULL if there is no current funccal.
+dictitem_T *get_funccal_local_var(void)
+{
+ if (current_funccal == NULL) {
+ return NULL;
+ }
+ return (dictitem_T *)&get_funccal()->l_vars_var;
+}
+
+/// Return the hashtable used for argument in the current funccal.
+/// Return NULL if there is no current funccal.
+hashtab_T *get_funccal_args_ht(void)
+{
+ if (current_funccal == NULL) {
+ return NULL;
+ }
+ return &get_funccal()->l_avars.dv_hashtab;
+}
+
+/// Return the a: scope variable.
+/// Return NULL if there is no current funccal.
+dictitem_T *get_funccal_args_var(void)
+{
+ if (current_funccal == NULL) {
+ return NULL;
+ }
+ return (dictitem_T *)&current_funccal->l_avars_var;
+}
+
+/*
+ * List function variables, if there is a function.
+ */
+void list_func_vars(int *first)
+{
+ if (current_funccal != NULL) {
+ list_hashtable_vars(&current_funccal->l_vars.dv_hashtab, "l:", false,
+ first);
+ }
+}
+
+/// If "ht" is the hashtable for local variables in the current funccal, return
+/// the dict that contains it.
+/// Otherwise return NULL.
+dict_T *get_current_funccal_dict(hashtab_T *ht)
+{
+ if (current_funccal != NULL && ht == &current_funccal->l_vars.dv_hashtab) {
+ return &current_funccal->l_vars;
+ }
+ return NULL;
+}
+
+/// Search hashitem in parent scope.
+hashitem_T *find_hi_in_scoped_ht(const char *name, hashtab_T **pht)
+{
+ if (current_funccal == NULL || current_funccal->func->uf_scoped == NULL) {
+ return NULL;
+ }
+
+ funccall_T *old_current_funccal = current_funccal;
+ hashitem_T *hi = NULL;
+ const size_t namelen = strlen(name);
+ const char *varname;
+
+ // Search in parent scope which is possible to reference from lambda
+ current_funccal = current_funccal->func->uf_scoped;
+ while (current_funccal != NULL) {
+ hashtab_T *ht = find_var_ht(name, namelen, &varname);
+ if (ht != NULL && *varname != NUL) {
+ hi = hash_find_len(ht, varname, namelen - (varname - name));
+ if (!HASHITEM_EMPTY(hi)) {
+ *pht = ht;
+ break;
+ }
+ }
+ if (current_funccal == current_funccal->func->uf_scoped) {
+ break;
+ }
+ current_funccal = current_funccal->func->uf_scoped;
+ }
+ current_funccal = old_current_funccal;
+
+ return hi;
+}
+
+/// Search variable in parent scope.
+dictitem_T *find_var_in_scoped_ht(const char *name, const size_t namelen,
+ int no_autoload)
+{
+ if (current_funccal == NULL || current_funccal->func->uf_scoped == NULL) {
+ return NULL;
+ }
+
+ dictitem_T *v = NULL;
+ funccall_T *old_current_funccal = current_funccal;
+ const char *varname;
+
+ // Search in parent scope which is possible to reference from lambda
+ current_funccal = current_funccal->func->uf_scoped;
+ while (current_funccal) {
+ hashtab_T *ht = find_var_ht(name, namelen, &varname);
+ if (ht != NULL && *varname != NUL) {
+ v = find_var_in_ht(ht, *name, varname,
+ namelen - (size_t)(varname - name), no_autoload);
+ if (v != NULL) {
+ break;
+ }
+ }
+ if (current_funccal == current_funccal->func->uf_scoped) {
+ break;
+ }
+ current_funccal = current_funccal->func->uf_scoped;
+ }
+ current_funccal = old_current_funccal;
+
+ return v;
+}
+
+/// Set "copyID + 1" in previous_funccal and callers.
+bool set_ref_in_previous_funccal(int copyID)
+{
+ bool abort = false;
+
+ for (funccall_T *fc = previous_funccal; fc != NULL; fc = fc->caller) {
+ abort = abort || set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID + 1, NULL);
+ abort = abort || set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID + 1, NULL);
+ }
+ return abort;
+}
+
+static bool set_ref_in_funccal(funccall_T *fc, int copyID)
+{
+ bool abort = false;
+
+ if (fc->fc_copyID != copyID) {
+ fc->fc_copyID = copyID;
+ abort = abort || set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID, NULL);
+ abort = abort || set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID, NULL);
+ abort = abort || set_ref_in_func(NULL, fc->func, copyID);
+ }
+ return abort;
+}
+
+/// Set "copyID" in all local vars and arguments in the call stack.
+bool set_ref_in_call_stack(int copyID)
+{
+ bool abort = false;
+
+ for (funccall_T *fc = current_funccal; fc != NULL; fc = fc->caller) {
+ abort = abort || set_ref_in_funccal(fc, copyID);
+ }
+
+ // Also go through the funccal_stack.
+ for (funccal_entry_T *entry = funccal_stack; entry != NULL;
+ entry = entry->next) {
+ for (funccall_T *fc = entry->top_funccal; !abort && fc != NULL;
+ fc = fc->caller) {
+ abort = abort || set_ref_in_funccal(fc, copyID);
+ }
+ }
+
+ return abort;
+}
+
+/// Set "copyID" in all functions available by name.
+bool set_ref_in_functions(int copyID)
+{
+ int todo;
+ hashitem_T *hi = NULL;
+ bool abort = false;
+ ufunc_T *fp;
+
+ todo = (int)func_hashtab.ht_used;
+ for (hi = func_hashtab.ht_array; todo > 0 && !got_int; hi++) {
+ if (!HASHITEM_EMPTY(hi)) {
+ todo--;
+ fp = HI2UF(hi);
+ if (!func_name_refcount(fp->uf_name)) {
+ abort = abort || set_ref_in_func(NULL, fp, copyID);
+ }
+ }
+ }
+ return abort;
+}
+
+/// Set "copyID" in all function arguments.
+bool set_ref_in_func_args(int copyID)
+{
+ bool abort = false;
+
+ for (int i = 0; i < funcargs.ga_len; i++) {
+ abort = abort || set_ref_in_item(((typval_T **)funcargs.ga_data)[i],
+ copyID, NULL, NULL);
+ }
+ return abort;
+}
+
+/// Mark all lists and dicts referenced through function "name" with "copyID".
+/// "list_stack" is used to add lists to be marked. Can be NULL.
+/// "ht_stack" is used to add hashtabs to be marked. Can be NULL.
+///
+/// @return true if setting references failed somehow.
+bool set_ref_in_func(char_u *name, ufunc_T *fp_in, int copyID)
+{
+ ufunc_T *fp = fp_in;
+ funccall_T *fc;
+ int error = ERROR_NONE;
+ char_u fname_buf[FLEN_FIXED + 1];
+ char_u *tofree = NULL;
+ char_u *fname;
+ bool abort = false;
+ if (name == NULL && fp_in == NULL) {
+ return false;
+ }
+
+ if (fp_in == NULL) {
+ fname = fname_trans_sid(name, fname_buf, &tofree, &error);
+ fp = find_func(fname);
+ }
+ if (fp != NULL) {
+ for (fc = fp->uf_scoped; fc != NULL; fc = fc->func->uf_scoped) {
+ abort = abort || set_ref_in_funccal(fc, copyID);
+ }
+ }
+ xfree(tofree);
+ return abort;
+}
+
+/// Registers a C extension user function.
+char_u *register_cfunc(cfunc_T cb, cfunc_free_T cb_free, void *state)
+{
+ char_u *name = get_lambda_name();
+ ufunc_T *fp = NULL;
+
+ fp = xcalloc(1, offsetof(ufunc_T, uf_name) + STRLEN(name) + 1);
+ if (fp == NULL) {
+ return NULL;
+ }
+
+ fp->uf_refcount = 1;
+ fp->uf_varargs = true;
+ fp->uf_flags = FC_CFUNC;
+ fp->uf_calls = 0;
+ fp->uf_script_ctx = current_sctx;
+ fp->uf_cb = cb;
+ fp->uf_cb_free = cb_free;
+ fp->uf_cb_state = state;
+
+ STRCPY(fp->uf_name, name);
+ hash_add(&func_hashtab, UF2HIKEY(fp));
+
+ return fp->uf_name;
+}
diff --git a/src/nvim/eval/userfunc.h b/src/nvim/eval/userfunc.h
new file mode 100644
index 0000000000..e8ad0bf1da
--- /dev/null
+++ b/src/nvim/eval/userfunc.h
@@ -0,0 +1,42 @@
+#ifndef NVIM_EVAL_USERFUNC_H
+#define NVIM_EVAL_USERFUNC_H
+
+#include "nvim/eval/typval.h"
+#include "nvim/ex_cmds_defs.h"
+
+///< Structure used by trans_function_name()
+typedef struct {
+ dict_T *fd_dict; ///< Dictionary used.
+ char_u *fd_newkey; ///< New key in "dict" in allocated memory.
+ dictitem_T *fd_di; ///< Dictionary item used.
+} funcdict_T;
+
+typedef struct funccal_entry funccal_entry_T;
+struct funccal_entry {
+ void *top_funccal;
+ funccal_entry_T *next;
+};
+
+/// errors for when calling a function
+typedef enum {
+ ERROR_UNKNOWN = 0,
+ ERROR_TOOMANY,
+ ERROR_TOOFEW,
+ ERROR_SCRIPT,
+ ERROR_DICT,
+ ERROR_NONE,
+ ERROR_OTHER,
+ ERROR_BOTH,
+ ERROR_DELETED,
+} FnameTransError;
+
+typedef int (*ArgvFunc)(int current_argcount, typval_T *argv, int argskip,
+ int called_func_argcount);
+
+#define FUNCARG(fp, j) ((char_u **)(fp->uf_args.ga_data))[j]
+#define FUNCLINE(fp, j) ((char_u **)(fp->uf_lines.ga_data))[j]
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "eval/userfunc.h.generated.h"
+#endif
+#endif // NVIM_EVAL_USERFUNC_H
diff --git a/src/nvim/event/libuv_process.c b/src/nvim/event/libuv_process.c
index 63efee59a8..13517d3df1 100644
--- a/src/nvim/event/libuv_process.c
+++ b/src/nvim/event/libuv_process.c
@@ -41,7 +41,7 @@ int libuv_process_spawn(LibuvProcess *uvproc)
#endif
uvproc->uvopts.exit_cb = exit_cb;
uvproc->uvopts.cwd = proc->cwd;
- uvproc->uvopts.env = NULL; // Inherits the parent (nvim) env.
+ uvproc->uvopts.env = proc->env;
uvproc->uvopts.stdio = uvproc->uvstdio;
uvproc->uvopts.stdio_count = 3;
uvproc->uvstdio[0].flags = UV_IGNORE;
@@ -52,7 +52,7 @@ int libuv_process_spawn(LibuvProcess *uvproc)
if (!proc->in.closed) {
uvproc->uvstdio[0].flags = UV_CREATE_PIPE | UV_READABLE_PIPE;
#ifdef WIN32
- uvproc->uvstdio[0].flags |= UV_OVERLAPPED_PIPE;
+ 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);
@@ -61,8 +61,9 @@ int libuv_process_spawn(LibuvProcess *uvproc)
if (!proc->out.closed) {
uvproc->uvstdio[1].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE;
#ifdef WIN32
- // pipe must be readable for IOCP to work.
- uvproc->uvstdio[1].flags |= UV_READABLE_PIPE | UV_OVERLAPPED_PIPE;
+ // pipe must be readable for IOCP to work on Windows.
+ 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);
diff --git a/src/nvim/event/loop.c b/src/nvim/event/loop.c
index a01bbc9888..e341513ae1 100644
--- a/src/nvim/event/loop.c
+++ b/src/nvim/event/loop.c
@@ -116,6 +116,20 @@ void loop_on_put(MultiQueue *queue, void *data)
uv_stop(&loop->uv);
}
+#if !defined(EXITFREE)
+static void loop_walk_cb(uv_handle_t *handle, void *arg)
+{
+ if (!uv_is_closing(handle)) {
+ uv_close(handle, NULL);
+ }
+}
+#endif
+
+/// Closes `loop` and its handles, and frees its structures.
+///
+/// @param loop Loop to destroy
+/// @param wait Wait briefly for handles to deref
+///
/// @returns false if the loop could not be closed gracefully
bool loop_close(Loop *loop, bool wait)
{
@@ -126,18 +140,34 @@ bool loop_close(Loop *loop, bool wait)
uv_close((uv_handle_t *)&loop->poll_timer, timer_close_cb);
uv_close((uv_handle_t *)&loop->async, NULL);
uint64_t start = wait ? os_hrtime() : 0;
+ bool didstop = false;
while (true) {
- uv_run(&loop->uv, wait ? UV_RUN_DEFAULT : UV_RUN_NOWAIT);
- if (!uv_loop_close(&loop->uv) || !wait) {
+ // Run the loop to tickle close-callbacks (which should then free memory).
+ // Use UV_RUN_NOWAIT to avoid a hang. #11820
+ uv_run(&loop->uv, didstop ? UV_RUN_DEFAULT : UV_RUN_NOWAIT);
+ if ((uv_loop_close(&loop->uv) != UV_EBUSY) || !wait) {
break;
}
- if (os_hrtime() - start >= 2 * 1000000000) {
+ uint64_t elapsed_s = (os_hrtime() - start) / 1000000000; // seconds
+ if (elapsed_s >= 2) {
// Some libuv resource was not correctly deref'd. Log and bail.
rv = false;
ELOG("uv_loop_close() hang?");
log_uv_handles(&loop->uv);
break;
}
+#if defined(EXITFREE)
+ (void)didstop;
+#else
+ if (!didstop) {
+ // Loop won’t block for I/O after this.
+ uv_stop(&loop->uv);
+ // XXX: Close all (lua/luv!) handles. But loop_walk_cb() does not call
+ // resource-specific close-callbacks, so this leaks memory...
+ uv_walk(&loop->uv, loop_walk_cb, NULL);
+ didstop = true;
+ }
+#endif
}
multiqueue_free(loop->fast_events);
multiqueue_free(loop->thread_events);
@@ -162,10 +192,6 @@ size_t loop_size(Loop *loop)
return rv;
}
-void loop_dummy_event(void **argv)
-{
-}
-
static void async_cb(uv_async_t *handle)
{
Loop *l = handle->loop->data;
diff --git a/src/nvim/event/process.h b/src/nvim/event/process.h
index ef9d953ab7..84e81238e9 100644
--- a/src/nvim/event/process.h
+++ b/src/nvim/event/process.h
@@ -23,10 +23,11 @@ struct process {
uint64_t stopped_time; // process_stop() timestamp
const char *cwd;
char **argv;
+ char **env;
Stream in, out, err;
process_exit_cb cb;
internal_process_cb internal_exit_cb, internal_close_cb;
- bool closed, detach;
+ bool closed, detach, overlapped;
MultiQueue *events;
};
diff --git a/src/nvim/event/stream.c b/src/nvim/event/stream.c
index d1a53fa4b6..1e9e530a42 100644
--- a/src/nvim/event/stream.c
+++ b/src/nvim/event/stream.c
@@ -11,11 +11,19 @@
#include "nvim/rbuffer.h"
#include "nvim/macros.h"
#include "nvim/event/stream.h"
+#ifdef WIN32
+# include "nvim/os/os_win_console.h"
+#endif
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "event/stream.c.generated.h"
#endif
+// For compatbility with libuv < 1.19.0 (tested on 1.18.0)
+#if UV_VERSION_MINOR < 19
+#define uv_stream_get_write_queue_size(stream) stream->write_queue_size
+#endif
+
/// Sets the stream associated with `fd` to "blocking" mode.
///
/// @return `0` on success, or libuv error code on failure.
@@ -57,6 +65,11 @@ void stream_init(Loop *loop, Stream *stream, int fd, uv_stream_t *uvstream)
if (type == UV_TTY) {
uv_tty_init(&loop->uv, &stream->uv.tty, fd, 0);
uv_tty_set_mode(&stream->uv.tty, UV_TTY_MODE_RAW);
+ DWORD dwMode;
+ if (GetConsoleMode(stream->uv.tty.handle, &dwMode)) {
+ dwMode |= ENABLE_VIRTUAL_TERMINAL_INPUT;
+ SetConsoleMode(stream->uv.tty.handle, dwMode);
+ }
stream->uvstream = STRUCT_CAST(uv_stream_t, &stream->uv.tty);
} else {
#endif
diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c
index 16487ce447..519978f4fb 100644
--- a/src/nvim/ex_cmds.c
+++ b/src/nvim/ex_cmds.c
@@ -14,6 +14,7 @@
#include <math.h>
#include "nvim/api/private/defs.h"
+#include "nvim/api/vim.h"
#include "nvim/api/buffer.h"
#include "nvim/log.h"
#include "nvim/vim.h"
@@ -39,6 +40,7 @@
#include "nvim/buffer_updates.h"
#include "nvim/main.h"
#include "nvim/mark.h"
+#include "nvim/extmark.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/message.h"
@@ -107,6 +109,8 @@ typedef struct {
# include "ex_cmds.c.generated.h"
#endif
+static int preview_bufnr = 0;
+
/// ":ascii" and "ga" implementation
void do_ascii(const exarg_T *const eap)
{
@@ -658,10 +662,10 @@ void ex_sort(exarg_T *eap)
deleted = (long)(count - (lnum - eap->line2));
if (deleted > 0) {
mark_adjust(eap->line2 - deleted, eap->line2, (long)MAXLNUM, -deleted,
- false);
+ kExtmarkUndo);
msgmore(-deleted);
} else if (deleted < 0) {
- mark_adjust(eap->line2, MAXLNUM, -deleted, 0L, false);
+ mark_adjust(eap->line2, MAXLNUM, -deleted, 0L, kExtmarkUndo);
}
if (change_occurred || deleted != 0) {
changed_lines(eap->line1, 0, eap->line2 + 1, -deleted, true);
@@ -874,22 +878,22 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest)
* 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, true);
+ mark_adjust_nofold(line1, line2, last_line - line2, 0L, kExtmarkNOOP);
changed_lines(last_line - num_lines + 1, 0, last_line + 1, num_lines, false);
if (dest >= line2) {
- mark_adjust_nofold(line2 + 1, dest, -num_lines, 0L, false);
+ mark_adjust_nofold(line2 + 1, dest, -num_lines, 0L, kExtmarkNOOP);
FOR_ALL_TAB_WINDOWS(tab, win) {
if (win->w_buffer == curbuf) {
- foldMoveRange(&win->w_folds, line1, line2, dest);
+ foldMoveRange(win, &win->w_folds, line1, line2, dest);
}
}
curbuf->b_op_start.lnum = dest - num_lines + 1;
curbuf->b_op_end.lnum = dest;
} else {
- mark_adjust_nofold(dest + 1, line1 - 1, num_lines, 0L, false);
+ mark_adjust_nofold(dest + 1, line1 - 1, num_lines, 0L, kExtmarkNOOP);
FOR_ALL_TAB_WINDOWS(tab, win) {
if (win->w_buffer == curbuf) {
- foldMoveRange(&win->w_folds, dest + 1, line1 - 1, line2);
+ foldMoveRange(win, &win->w_folds, dest + 1, line1 - 1, line2);
}
}
curbuf->b_op_start.lnum = dest + 1;
@@ -897,7 +901,15 @@ 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, true);
+ -(last_line - dest - extra), 0L, kExtmarkNOOP);
+
+ // extmarks are handled separately
+ int size = line2-line1+1;
+ int off = dest >= line2 ? -size : 0;
+ extmark_move_region(curbuf, line1-1, 0,
+ line2-line1+1, 0,
+ dest+off, 0, kExtmarkUndo);
+
changed_lines(last_line - num_lines + 1, 0, last_line + 1, -extra, false);
// send update regarding the new lines that were added
@@ -1279,14 +1291,19 @@ static void do_filter(
if (do_in) {
if (cmdmod.keepmarks || vim_strchr(p_cpo, CPO_REMMARK) == NULL) {
+ // TODO(bfredl): Currently not active for extmarks. What would we
+ // do if columns don't match, assume added/deleted bytes at the
+ // end of each line?
if (read_linecount >= linecount) {
// move all marks from old lines to new lines
- mark_adjust(line1, line2, linecount, 0L, false);
+ mark_adjust(line1, line2, linecount, 0L, 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, false);
- mark_adjust(line1 + read_linecount, line2, MAXLNUM, 0L, false);
+ mark_adjust(line1, line1 + read_linecount - 1, linecount, 0L,
+ kExtmarkNOOP);
+ mark_adjust(line1 + read_linecount, line2, MAXLNUM, 0L,
+ kExtmarkNOOP);
}
}
@@ -1579,7 +1596,7 @@ int rename_buffer(char_u *new_fname)
xfname = curbuf->b_fname;
curbuf->b_ffname = NULL;
curbuf->b_sfname = NULL;
- if (setfname(curbuf, new_fname, NULL, TRUE) == FAIL) {
+ if (setfname(curbuf, new_fname, NULL, true) == FAIL) {
curbuf->b_ffname = fname;
curbuf->b_sfname = sfname;
return FAIL;
@@ -2162,6 +2179,7 @@ int do_ecmd(
int did_get_winopts = FALSE;
int readfile_flags = 0;
bool did_inc_redrawing_disabled = false;
+ long *so_ptr = curwin->w_p_so >= 0 ? &curwin->w_p_so : &p_so;
if (eap != NULL)
command = eap->do_ecmd_cmd;
@@ -2654,6 +2672,8 @@ int do_ecmd(
msg_scrolled_ign = FALSE;
}
+ curbuf->b_last_used = time(NULL);
+
if (command != NULL)
do_cmdline(command, NULL, NULL, DOCMD_VERBOSE);
@@ -2663,13 +2683,14 @@ int do_ecmd(
RedrawingDisabled--;
did_inc_redrawing_disabled = false;
if (!skip_redraw) {
- n = p_so;
- if (topline == 0 && command == NULL)
- p_so = 999; // force cursor to be vertically centered in the window
+ n = *so_ptr;
+ if (topline == 0 && command == NULL) {
+ *so_ptr = 999; // force cursor to be vertically centered in the window
+ }
update_topline();
curwin->w_scbind_pos = curwin->w_topline;
- p_so = n;
- redraw_curbuf_later(NOT_VALID); /* redraw this buffer later */
+ *so_ptr = n;
+ redraw_curbuf_later(NOT_VALID); // redraw this buffer later
}
if (p_im)
@@ -2767,7 +2788,7 @@ void ex_append(exarg_T *eap)
State = CMDLINE;
theline = eap->getline(
eap->cstack->cs_looplevel > 0 ? -1 :
- NUL, eap->cookie, indent);
+ NUL, eap->cookie, indent, true);
State = save_State;
}
lines_left = Rows - 1;
@@ -2993,18 +3014,18 @@ void ex_z(exarg_T *eap)
ex_no_reprint = true;
}
-/*
- * Check if the restricted flag is set.
- * If so, give an error message and return TRUE.
- * Otherwise, return FALSE.
- */
-int check_restricted(void)
+// Check if the restricted flag is set.
+// If so, give an error message and return true.
+// Otherwise, return false.
+bool check_restricted(void)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
if (restricted) {
- EMSG(_("E145: Shell commands not allowed in restricted mode"));
- return TRUE;
+ EMSG(_("E145: Shell commands and some functionality not allowed"
+ " in restricted mode"));
+ return true;
}
- return FALSE;
+ return false;
}
/*
@@ -3214,6 +3235,7 @@ static char_u *sub_parse_flags(char_u *cmd, subflags_T *subflags,
return cmd;
}
+
/// Perform a substitution from line eap->line1 to line eap->line2 using the
/// command pointed to by eap->arg which should be of the form:
///
@@ -3224,7 +3246,7 @@ static char_u *sub_parse_flags(char_u *cmd, subflags_T *subflags,
/// @param do_buf_event If `true`, send buffer updates.
/// @return buffer used for 'inccommand' preview
static buf_T *do_sub(exarg_T *eap, proftime_T timeout,
- bool do_buf_event)
+ bool do_buf_event, handle_T bufnr)
{
long i = 0;
regmmatch_T regmatch;
@@ -3261,6 +3283,12 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout,
int save_b_changed = curbuf->b_changed;
bool preview = (State & CMDPREVIEW);
+ // inccommand tests fail without this check
+ if (!preview) {
+ // Required for Undo to work for extmarks.
+ u_save_cursor();
+ }
+
if (!global_busy) {
sub_nsubs = 0;
sub_nlines = 0;
@@ -3418,6 +3446,7 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout,
// Check for a match on each line.
// If preview: limit to max('cmdwinheight', viewport).
linenr_T line2 = eap->line2;
+
for (linenr_T lnum = eap->line1;
lnum <= line2 && !got_quit && !aborting()
&& (!preview || preview_lines.lines_needed <= (linenr_T)p_cwh
@@ -3524,6 +3553,11 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout,
// Note: If not first match on a line, column can't be known here
current_match.start.lnum = sub_firstlnum;
+ // Match might be after the last line for "\n\zs" matching at
+ // the end of the last line.
+ if (lnum > curbuf->b_ml.ml_line_count) {
+ break;
+ }
if (sub_firstline == NULL) {
sub_firstline = vim_strsave(ml_get(sub_firstlnum));
}
@@ -3630,7 +3664,7 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout,
for (; i <= (long)ec; ++i)
msg_putchar('^');
- resp = getexmodeline('?', NULL, 0);
+ resp = getexmodeline('?', NULL, 0, true);
if (resp != NULL) {
typed = *resp;
xfree(resp);
@@ -3804,9 +3838,11 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout,
goto skip;
}
+
// 3. Substitute the string. During 'inccommand' preview only do this if
// there is a replace pattern.
if (!preview || has_second_delim) {
+ long lnum_start = lnum; // save the start lnum
save_ma = curbuf->b_p_ma;
if (subflags.do_count) {
// prevent accidentally changing the buffer by a function
@@ -3854,7 +3890,8 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout,
// Finally, at this point we can know where the match actually will
// start in the new text
- current_match.start.col = new_end - new_start;
+ int start_col = new_end - new_start;
+ current_match.start.col = start_col;
(void)vim_regsub_multi(&regmatch,
sub_firstlnum - regmatch.startpos[0].lnum,
@@ -3871,6 +3908,7 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout,
ADJUST_SUB_FIRSTLNUM();
+
// Now the trick is to replace CTRL-M chars with a real line
// break. This would make it impossible to insert a CTRL-M in
// the text. The line break can be avoided by preceding the
@@ -3885,7 +3923,8 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout,
*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, false);
+ mark_adjust(lnum + 1, (linenr_T)MAXLNUM, 1L, 0L, kExtmarkNOOP);
+
if (subflags.do_ask) {
appended_lines(lnum - 1, 1L);
} else {
@@ -3908,10 +3947,24 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout,
p1 += (*mb_ptr2len)(p1) - 1;
}
}
- current_match.end.col = STRLEN(new_start);
+ size_t new_endcol = STRLEN(new_start);
+ current_match.end.col = new_endcol;
current_match.end.lnum = lnum;
+
+ // TODO(bfredl): adjust in preview, because decorations?
+ // this has some robustness issues, will look into later.
+ if (!preview) {
+ lpos_T start = regmatch.startpos[0], end = regmatch.endpos[0];
+ int matchcols = end.col - ((end.lnum == start.lnum)
+ ? start.col : 0);
+ int subcols = new_endcol - ((lnum == lnum_start) ? start_col : 0);
+ extmark_splice(curbuf, lnum_start-1, start_col,
+ end.lnum-start.lnum, matchcols,
+ lnum-lnum_start, subcols, kExtmarkUndo);
+ }
}
+
// 4. If subflags.do_all is set, find next match.
// Prevent endless loop with patterns that match empty
// strings, e.g. :s/$/pat/g or :s/[a-z]* /(&)/g.
@@ -3978,7 +4031,7 @@ skip:
ml_delete(lnum, false);
}
mark_adjust(lnum, lnum + nmatch_tl - 1,
- (long)MAXLNUM, -nmatch_tl, false);
+ (long)MAXLNUM, -nmatch_tl, kExtmarkNOOP);
if (subflags.do_ask) {
deleted_lines(lnum, nmatch_tl);
}
@@ -4140,17 +4193,17 @@ skip:
} else if (*p_icm != NUL && pat != NULL) {
if (pre_src_id == 0) {
// Get a unique new src_id, saved in a static
- pre_src_id = bufhl_add_hl(NULL, 0, -1, 0, 0, 0);
+ pre_src_id = (int)nvim_create_namespace((String)STRING_INIT);
}
if (pre_hl_id == 0) {
pre_hl_id = syn_check_group((char_u *)S_LEN("Substitute"));
}
curbuf->b_changed = save_b_changed; // preserve 'modified' during preview
preview_buf = show_sub(eap, old_cursor, &preview_lines,
- pre_hl_id, pre_src_id);
+ pre_hl_id, pre_src_id, bufnr);
if (subsize > 0) {
- bufhl_clear_line_range(orig_buf, pre_src_id, eap->line1,
- kv_last(preview_lines.subresults).end.lnum);
+ extmark_clear(orig_buf, pre_src_id, eap->line1-1, 0,
+ kv_last(preview_lines.subresults).end.lnum-1, MAXCOL);
}
}
}
@@ -4435,8 +4488,9 @@ prepare_tagpreview (
curwin->w_p_wfh = TRUE;
RESET_BINDING(curwin); /* don't take over 'scrollbind'
and 'cursorbind' */
- curwin->w_p_diff = FALSE; /* no 'diff' */
- curwin->w_p_fdc = 0; /* no 'foldcolumn' */
+ curwin->w_p_diff = false; // no 'diff'
+ set_string_option_direct((char_u *)"fdc", -1, // no 'foldcolumn'
+ (char_u *)"0", OPT_FREE, SID_NONE);
return true;
}
}
@@ -4669,17 +4723,21 @@ help_heuristic(
* If the match is more than 2 chars from the start, multiply by 200 to
* put it after matches at the start.
*/
- if (ASCII_ISALNUM(matched_string[offset]) && offset > 0
- && ASCII_ISALNUM(matched_string[offset - 1]))
+ if (offset > 0
+ && ASCII_ISALNUM(matched_string[offset])
+ && ASCII_ISALNUM(matched_string[offset - 1])) {
offset += 10000;
- else if (offset > 2)
+ } else if (offset > 2) {
offset *= 200;
- if (wrong_case)
+ }
+ if (wrong_case) {
offset += 5000;
- /* Features are less interesting than the subjects themselves, but "+"
- * alone is not a feature. */
- if (matched_string[0] == '+' && matched_string[1] != NUL)
+ }
+ // Features are less interesting than the subjects themselves, but "+"
+ // alone is not a feature.
+ if (matched_string[0] == '+' && matched_string[1] != NUL) {
offset += 100;
+ }
return (int)(100 * num_letters + STRLEN(matched_string) + offset);
}
@@ -4739,11 +4797,19 @@ int find_help_tags(const char_u *arg, int *num_matches, char_u ***matches,
if (STRNICMP(arg, "expr-", 5) == 0) {
// When the string starting with "expr-" and containing '?' and matches
- // the table, it is taken literally. Otherwise '?' is recognized as a
- // wildcard.
+ // the table, it is taken literally (but ~ is escaped). Otherwise '?'
+ // is recognized as a wildcard.
for (i = (int)ARRAY_SIZE(expr_table); --i >= 0; ) {
if (STRCMP(arg + 5, expr_table[i]) == 0) {
- STRCPY(d, arg);
+ for (int si = 0, di = 0; ; si++) {
+ if (arg[si] == '~') {
+ d[di++] = '\\';
+ }
+ d[di++] = arg[si];
+ if (arg[si] == NUL) {
+ break;
+ }
+ }
break;
}
}
@@ -4897,7 +4963,7 @@ int find_help_tags(const char_u *arg, int *num_matches, char_u ***matches,
*matches = NULL;
*num_matches = 0;
- int flags = TAG_HELP | TAG_REGEXP | TAG_NAMES | TAG_VERBOSE;
+ int flags = TAG_HELP | TAG_REGEXP | TAG_NAMES | TAG_VERBOSE | TAG_NO_TAGFUNC;
if (keep_lang) {
flags |= TAG_KEEP_LANG;
}
@@ -5023,7 +5089,7 @@ void fix_help_buffer(void)
copy_option_part(&p, NameBuff, MAXPATHL, ",");
char_u *const rt = (char_u *)vim_getenv("VIMRUNTIME");
if (rt != NULL
- && path_full_compare(rt, NameBuff, false) != kEqualFiles) {
+ && path_full_compare(rt, NameBuff, false, true) != kEqualFiles) {
int fcount;
char_u **fnames;
char_u *s;
@@ -5233,7 +5299,7 @@ static void helptags_one(char_u *const dir, const char_u *const ext,
ga_init(&ga, (int)sizeof(char_u *), 100);
if (add_help_tags
|| path_full_compare((char_u *)"$VIMRUNTIME/doc",
- dir, false) == kEqualFiles) {
+ dir, false, true) == kEqualFiles) {
s = xmalloc(18 + STRLEN(tagfname));
sprintf((char *)s, "help-tags\t%s\t1\n", tagfname);
GA_APPEND(char_u *, &ga, s);
@@ -5511,14 +5577,31 @@ void ex_helpclose(exarg_T *eap)
}
}
+/// Tries to enter to an existing window of given buffer. If no existing buffer
+/// is found, creates a new split.
+///
+/// Returns OK/FAIL.
+int sub_preview_win(buf_T *preview_buf)
+{
+ if (preview_buf != NULL) {
+ FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
+ if (wp->w_buffer == preview_buf) {
+ win_enter(wp, false);
+
+ return OK;
+ }
+ }
+ }
+ return win_split((int)p_cwh, WSP_BOT);
+}
+
/// Shows the effects of the :substitute command being typed ('inccommand').
/// If inccommand=split, shows a preview window and later restores the layout.
static buf_T *show_sub(exarg_T *eap, pos_T old_cusr,
- PreviewLines *preview_lines, int hl_id, int src_id)
+ PreviewLines *preview_lines, int hl_id, int src_id,
+ handle_T bufnr)
FUNC_ATTR_NONNULL_ALL
{
- static handle_T bufnr = 0; // special buffer, re-used on each visit
-
win_T *save_curwin = curwin;
cmdmod_T save_cmdmod = cmdmod;
char_u *save_shm_p = vim_strsave(p_shm);
@@ -5527,6 +5610,7 @@ static buf_T *show_sub(exarg_T *eap, pos_T old_cusr,
// We keep a special-purpose buffer around, but don't assume it exists.
buf_T *preview_buf = bufnr ? buflist_findnr(bufnr) : 0;
+ cmdmod.split = 0; // disable :leftabove/botright modifiers
cmdmod.tab = 0; // disable :tab modifier
cmdmod.noswapfile = true; // disable swap for preview buffer
// disable file info message
@@ -5535,9 +5619,9 @@ static buf_T *show_sub(exarg_T *eap, pos_T old_cusr,
bool outside_curline = (eap->line1 != old_cusr.lnum
|| eap->line2 != old_cusr.lnum);
- bool split = outside_curline && (*p_icm != 'n');
+ bool preview = outside_curline && (*p_icm != 'n');
if (preview_buf == curbuf) { // Preview buffer cannot preview itself!
- split = false;
+ preview = false;
preview_buf = NULL;
}
@@ -5555,11 +5639,10 @@ static buf_T *show_sub(exarg_T *eap, pos_T old_cusr,
linenr_T highest_num_line = 0;
int col_width = 0;
- if (split && win_split((int)p_cwh, WSP_BOT) != FAIL) {
+ if (preview && sub_preview_win(preview_buf) != FAIL) {
buf_open_scratch(preview_buf ? bufnr : 0, "[Preview]");
buf_clear();
preview_buf = curbuf;
- bufnr = preview_buf->handle;
curbuf->b_p_bl = false;
curbuf->b_p_ma = true;
curbuf->b_p_ul = -1;
@@ -5573,6 +5656,9 @@ static buf_T *show_sub(exarg_T *eap, pos_T old_cusr,
highest_num_line = kv_last(lines.subresults).end.lnum;
col_width = log10(highest_num_line) + 1 + 3;
}
+ } else {
+ // Failed to split the window, don't show 'inccommand' preview.
+ preview_buf = NULL;
}
char *str = NULL; // construct the line to show in here
@@ -5585,7 +5671,7 @@ static buf_T *show_sub(exarg_T *eap, pos_T old_cusr,
for (size_t matchidx = 0; matchidx < lines.subresults.size; matchidx++) {
SubResult match = lines.subresults.items[matchidx];
- if (split && preview_buf) {
+ if (preview_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
@@ -5645,7 +5731,7 @@ static buf_T *show_sub(exarg_T *eap, pos_T old_cusr,
win_enter(save_curwin, false); // Return to original window
update_topline();
- // Update screen now. Must do this _before_ close_windows().
+ // Update screen now.
int save_rd = RedrawingDisabled;
RedrawingDisabled = 0;
update_screen(SOME_VALID);
@@ -5659,6 +5745,17 @@ static buf_T *show_sub(exarg_T *eap, pos_T old_cusr,
return preview_buf;
}
+/// Closes any open windows for inccommand preview buffer.
+void close_preview_windows(void)
+{
+ block_autocmds();
+ buf_T *buf = preview_bufnr ? buflist_findnr(preview_bufnr) : NULL;
+ if (buf != NULL) {
+ close_windows(buf, false);
+ }
+ unblock_autocmds();
+}
+
/// :substitute command
///
/// If 'inccommand' is empty: calls do_sub().
@@ -5668,7 +5765,9 @@ void ex_substitute(exarg_T *eap)
{
bool preview = (State & CMDPREVIEW);
if (*p_icm == NUL || !preview) { // 'inccommand' is disabled
- (void)do_sub(eap, profile_zero(), true);
+ close_preview_windows();
+ (void)do_sub(eap, profile_zero(), true, preview_bufnr);
+
return;
}
@@ -5692,9 +5791,14 @@ void ex_substitute(exarg_T *eap)
// Don't show search highlighting during live substitution
bool save_hls = p_hls;
p_hls = false;
- buf_T *preview_buf = do_sub(eap, profile_setlimit(p_rdt), false);
+ buf_T *preview_buf = do_sub(eap, profile_setlimit(p_rdt), false,
+ preview_bufnr);
p_hls = save_hls;
+ if (preview_buf != NULL) {
+ preview_bufnr = preview_buf->handle;
+ }
+
if (save_changedtick != buf_get_changedtick(curbuf)) {
// Undo invisibly. This also moves the cursor!
if (!u_undo_and_forget(1)) { abort(); }
@@ -5704,10 +5808,7 @@ void ex_substitute(exarg_T *eap)
curbuf->b_u_time_cur = save_b_u_time_cur;
buf_set_changedtick(curbuf, save_changedtick);
}
- if (buf_valid(preview_buf)) {
- // XXX: Must do this *after* u_undo_and_forget(), why?
- close_windows(preview_buf, false);
- }
+
curbuf->b_p_ul = save_b_p_ul;
curwin->w_p_cul = save_w_p_cul; // Restore 'cursorline'
curwin->w_p_cuc = save_w_p_cuc; // Restore 'cursorcolumn'
diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua
index 8c0d22809f..252af409c0 100644
--- a/src/nvim/ex_cmds.lua
+++ b/src/nvim/ex_cmds.lua
@@ -24,6 +24,7 @@ local SBOXOK = 0x80000
local CMDWIN = 0x100000
local MODIFY = 0x200000
local EXFLAGS = 0x400000
+local RESTRICT = 0x800000
local FILES = bit.bor(XFILE, EXTRA)
local WORD1 = bit.bor(EXTRA, NOSPC)
local FILE1 = bit.bor(FILES, NOSPC)
@@ -323,6 +324,12 @@ return {
func='ex_abclear',
},
{
+ command='cabove',
+ flags=bit.bor(RANGE, TRLBAR),
+ addr_type=ADDR_OTHER ,
+ func='ex_cbelow',
+ },
+ {
command='caddbuffer',
flags=bit.bor(RANGE, NOTADR, WORD1, TRLBAR),
addr_type=ADDR_LINES,
@@ -359,6 +366,12 @@ return {
func='ex_cbuffer',
},
{
+ command='cbelow',
+ flags=bit.bor(RANGE, TRLBAR),
+ addr_type=ADDR_OTHER ,
+ func='ex_cbelow',
+ },
+ {
command='cbottom',
flags=bit.bor(TRLBAR),
addr_type=ADDR_LINES,
@@ -758,7 +771,7 @@ return {
},
{
command='digraphs',
- flags=bit.bor(EXTRA, TRLBAR, CMDWIN),
+ flags=bit.bor(BANG, EXTRA, TRLBAR, CMDWIN),
addr_type=ADDR_LINES,
func='ex_digraphs',
},
@@ -1273,6 +1286,12 @@ return {
func='ex_last',
},
{
+ command='labove',
+ flags=bit.bor(RANGE, TRLBAR),
+ addr_type=ADDR_OTHER ,
+ func='ex_cbelow',
+ },
+ {
command='language',
flags=bit.bor(EXTRA, TRLBAR, CMDWIN),
addr_type=ADDR_LINES,
@@ -1309,6 +1328,12 @@ return {
func='ex_cbuffer',
},
{
+ command='lbelow',
+ flags=bit.bor(RANGE, TRLBAR),
+ addr_type=ADDR_OTHER ,
+ func='ex_cbelow',
+ },
+ {
command='lbottom',
flags=bit.bor(TRLBAR),
addr_type=ADDR_LINES,
@@ -1558,19 +1583,19 @@ return {
},
{
command='lua',
- flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN),
+ flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN, RESTRICT),
addr_type=ADDR_LINES,
func='ex_lua',
},
{
command='luado',
- flags=bit.bor(RANGE, DFLALL, EXTRA, NEEDARG, CMDWIN),
+ flags=bit.bor(RANGE, DFLALL, EXTRA, NEEDARG, CMDWIN, RESTRICT),
addr_type=ADDR_LINES,
func='ex_luado',
},
{
command='luafile',
- flags=bit.bor(RANGE, FILE1, NEEDARG, CMDWIN),
+ flags=bit.bor(RANGE, FILE1, NEEDARG, CMDWIN, RESTRICT),
addr_type=ADDR_LINES,
func='ex_luafile',
},
@@ -1632,7 +1657,7 @@ return {
command='marks',
flags=bit.bor(EXTRA, TRLBAR, CMDWIN),
addr_type=ADDR_LINES,
- func='do_marks',
+ func='ex_marks',
},
{
command='match',
@@ -1900,13 +1925,13 @@ return {
},
{
command='perl',
- flags=bit.bor(RANGE, EXTRA, DFLALL, NEEDARG, SBOXOK, CMDWIN),
+ flags=bit.bor(RANGE, EXTRA, DFLALL, NEEDARG, SBOXOK, CMDWIN, RESTRICT),
addr_type=ADDR_LINES,
func='ex_script_ni',
},
{
command='perldo',
- flags=bit.bor(RANGE, EXTRA, DFLALL, NEEDARG, CMDWIN),
+ flags=bit.bor(RANGE, EXTRA, DFLALL, NEEDARG, CMDWIN, RESTRICT),
addr_type=ADDR_LINES,
func='ex_ni',
},
@@ -2032,67 +2057,67 @@ return {
},
{
command='python',
- flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN),
+ flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN, RESTRICT),
addr_type=ADDR_LINES,
func='ex_python',
},
{
command='pydo',
- flags=bit.bor(RANGE, DFLALL, EXTRA, NEEDARG, CMDWIN),
+ flags=bit.bor(RANGE, DFLALL, EXTRA, NEEDARG, CMDWIN, RESTRICT),
addr_type=ADDR_LINES,
func='ex_pydo',
},
{
command='pyfile',
- flags=bit.bor(RANGE, FILE1, NEEDARG, CMDWIN),
+ flags=bit.bor(RANGE, FILE1, NEEDARG, CMDWIN, RESTRICT),
addr_type=ADDR_LINES,
func='ex_pyfile',
},
{
command='py3',
- flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN),
+ flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN, RESTRICT),
addr_type=ADDR_LINES,
func='ex_python3',
},
{
command='py3do',
- flags=bit.bor(RANGE, DFLALL, EXTRA, NEEDARG, CMDWIN),
+ flags=bit.bor(RANGE, DFLALL, EXTRA, NEEDARG, CMDWIN, RESTRICT),
addr_type=ADDR_LINES,
func='ex_pydo3',
},
{
command='python3',
- flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN),
+ flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN, RESTRICT),
addr_type=ADDR_LINES,
func='ex_python3',
},
{
command='py3file',
- flags=bit.bor(RANGE, FILE1, NEEDARG, CMDWIN),
+ flags=bit.bor(RANGE, FILE1, NEEDARG, CMDWIN, RESTRICT),
addr_type=ADDR_LINES,
func='ex_py3file',
},
{
command='pyx',
- flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN),
+ flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN, RESTRICT),
addr_type=ADDR_LINES,
func='ex_pyx',
},
{
command='pyxdo',
- flags=bit.bor(RANGE, DFLALL, EXTRA, NEEDARG, CMDWIN),
+ flags=bit.bor(RANGE, DFLALL, EXTRA, NEEDARG, CMDWIN, RESTRICT),
addr_type=ADDR_LINES,
func='ex_pyxdo',
},
{
command='pythonx',
- flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN),
+ flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN, RESTRICT),
addr_type=ADDR_LINES,
func='ex_pyx',
},
{
command='pyxfile',
- flags=bit.bor(RANGE, FILE1, NEEDARG, CMDWIN),
+ flags=bit.bor(RANGE, FILE1, NEEDARG, CMDWIN, RESTRICT),
addr_type=ADDR_LINES,
func='ex_pyxfile',
},
@@ -2218,19 +2243,19 @@ return {
},
{
command='ruby',
- flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN),
+ flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN, RESTRICT),
addr_type=ADDR_LINES,
func='ex_ruby',
},
{
command='rubydo',
- flags=bit.bor(RANGE, DFLALL, EXTRA, NEEDARG, CMDWIN),
+ flags=bit.bor(RANGE, DFLALL, EXTRA, NEEDARG, CMDWIN, RESTRICT),
addr_type=ADDR_LINES,
func='ex_rubydo',
},
{
command='rubyfile',
- flags=bit.bor(RANGE, FILE1, NEEDARG, CMDWIN),
+ flags=bit.bor(RANGE, FILE1, NEEDARG, CMDWIN, RESTRICT),
addr_type=ADDR_LINES,
func='ex_rubyfile',
},
diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c
index 813d1a9b0b..7f4b01e306 100644
--- a/src/nvim/ex_cmds2.c
+++ b/src/nvim/ex_cmds2.c
@@ -20,7 +20,7 @@
#include "nvim/buffer.h"
#include "nvim/change.h"
#include "nvim/charset.h"
-#include "nvim/eval.h"
+#include "nvim/eval/userfunc.h"
#include "nvim/ex_cmds.h"
#include "nvim/ex_docmd.h"
#include "nvim/ex_eval.h"
@@ -97,10 +97,11 @@ typedef struct sn_prl_S {
struct source_cookie {
FILE *fp; ///< opened file for sourcing
char_u *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)
int fileformat; ///< EOL_UNKNOWN, EOL_UNIX or EOL_DOS
- bool error; ///< true if LF found after CR-LF
+ bool error; ///< true if LF found after CR-LF
#endif
linenr_T breakpoint; ///< next line with breakpoint or zero
char_u *fname; ///< name of sourced file
@@ -1037,16 +1038,17 @@ static void profile_reset(void)
if (!HASHITEM_EMPTY(hi)) {
n--;
ufunc_T *uf = HI2UF(hi);
- if (uf->uf_profiling) {
+ if (uf->uf_prof_initialized) {
uf->uf_profiling = 0;
uf->uf_tm_count = 0;
uf->uf_tm_total = profile_zero();
uf->uf_tm_self = profile_zero();
uf->uf_tm_children = profile_zero();
- XFREE_CLEAR(uf->uf_tml_count);
- XFREE_CLEAR(uf->uf_tml_total);
- XFREE_CLEAR(uf->uf_tml_self);
+ for (int i = 0; i < uf->uf_lines.ga_len; i++) {
+ uf->uf_tml_count[i] = 0;
+ uf->uf_tml_total[i] = uf->uf_tml_self[i] = 0;
+ }
uf->uf_tml_start = profile_zero();
uf->uf_tml_children = profile_zero();
@@ -1413,6 +1415,7 @@ bool check_changed_any(bool hidden, bool unload)
size_t bufcount = 0;
int *bufnrs;
+ // Make a list of all buffers, with the most important ones first.
FOR_ALL_BUFFERS(buf) {
bufcount++;
}
@@ -1425,14 +1428,15 @@ bool check_changed_any(bool hidden, bool unload)
// curbuf
bufnrs[bufnum++] = curbuf->b_fnum;
- // buf in curtab
+
+ // buffers in current tab
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
if (wp->w_buffer != curbuf) {
add_bufnum(bufnrs, &bufnum, wp->w_buffer->b_fnum);
}
}
- // buf in other tab
+ // buffers in other tabs
FOR_ALL_TABS(tp) {
if (tp != curtab) {
FOR_ALL_WINDOWS_IN_TAB(wp, tp) {
@@ -1441,7 +1445,7 @@ bool check_changed_any(bool hidden, bool unload)
}
}
- // any other buf
+ // any other buffer
FOR_ALL_BUFFERS(buf) {
add_bufnum(bufnrs, &bufnum, buf->b_fnum);
}
@@ -1470,6 +1474,7 @@ bool check_changed_any(bool hidden, bool unload)
goto theend;
}
+ // Get here if "buf" cannot be abandoned.
ret = true;
exiting = false;
// When ":confirm" used, don't give an error message.
@@ -1743,7 +1748,7 @@ static bool editing_arg_idx(win_T *win)
&& (win->w_buffer->b_ffname == NULL
|| !(path_full_compare(
alist_name(&WARGLIST(win)[win->w_arg_idx]),
- win->w_buffer->b_ffname, true) & kEqualFiles))));
+ win->w_buffer->b_ffname, true, true) & kEqualFiles))));
}
/// Check if window "win" is editing the w_arg_idx file in its argument list.
@@ -1761,7 +1766,7 @@ void check_arg_idx(win_T *win)
&& (win->w_buffer->b_fnum == GARGLIST[GARGCOUNT - 1].ae_fnum
|| (win->w_buffer->b_ffname != NULL
&& (path_full_compare(alist_name(&GARGLIST[GARGCOUNT - 1]),
- win->w_buffer->b_ffname, true)
+ win->w_buffer->b_ffname, true, true)
& kEqualFiles)))) {
arg_had_last = true;
}
@@ -2390,7 +2395,7 @@ int do_in_path(char_u *path, char_u *name, int flags,
char_u *rtp_copy = vim_strsave(path);
char_u *buf = xmallocz(MAXPATHL);
{
- if (p_verbose > 1 && name != NULL) {
+ if (p_verbose > 10 && name != NULL) {
verbose_enter();
smsg(_("Searching for \"%s\" in \"%s\""),
(char *)name, (char *)path);
@@ -2432,7 +2437,7 @@ int do_in_path(char_u *path, char_u *name, int flags,
copy_option_part(&np, tail, (size_t)(MAXPATHL - (tail - buf)),
"\t ");
- if (p_verbose > 2) {
+ if (p_verbose > 10) {
verbose_enter();
smsg(_("Searching for \"%s\""), buf);
verbose_leave();
@@ -3014,11 +3019,78 @@ static FILE *fopen_noinh_readbin(char *filename)
return fdopen(fd_tmp, READBIN);
}
+typedef struct {
+ char_u *buf;
+ size_t offset;
+} GetStrLineCookie;
+
+/// Get one full line from a sourced string (in-memory, no file).
+/// Called by do_cmdline() when it's called from do_source_str().
+///
+/// @return pointer to allocated line, or NULL for end-of-file or
+/// some error.
+static char_u *get_str_line(int c, void *cookie, int indent, bool do_concat)
+{
+ GetStrLineCookie *p = cookie;
+ size_t i = p->offset;
+ if (strlen((char *)p->buf) <= p->offset) {
+ return NULL;
+ }
+ while (!(p->buf[i] == '\n' || p->buf[i] == '\0')) {
+ i++;
+ }
+ char buf[2046];
+ char *dst;
+ dst = xstpncpy(buf, (char *)p->buf + p->offset, i - p->offset);
+ if ((uint32_t)(dst - buf) != i - p->offset) {
+ smsg(_(":source error parsing command %s"), p->buf);
+ return NULL;
+ }
+ buf[i - p->offset] = '\0';
+ p->offset = i + 1;
+ return (char_u *)xstrdup(buf);
+}
+
+/// Executes lines in `src` as Ex commands.
+///
+/// @see do_source()
+int do_source_str(const char *cmd, const char *traceback_name)
+{
+ char_u *save_sourcing_name = sourcing_name;
+ linenr_T save_sourcing_lnum = sourcing_lnum;
+ char_u sourcing_name_buf[256];
+ if (save_sourcing_name == NULL) {
+ sourcing_name = (char_u *)traceback_name;
+ } else {
+ snprintf((char *)sourcing_name_buf, sizeof(sourcing_name_buf),
+ "%s called at %s:%"PRIdLINENR, traceback_name, save_sourcing_name,
+ save_sourcing_lnum);
+ sourcing_name = sourcing_name_buf;
+ }
+ sourcing_lnum = 0;
+
+ GetStrLineCookie cookie = {
+ .buf = (char_u *)cmd,
+ .offset = 0,
+ };
+ const sctx_T save_current_sctx = current_sctx;
+ current_sctx.sc_sid = SID_STR;
+ current_sctx.sc_seq = 0;
+ current_sctx.sc_lnum = save_sourcing_lnum;
+ int retval = do_cmdline(NULL, get_str_line, (void *)&cookie,
+ DOCMD_VERBOSE | DOCMD_NOWAIT | DOCMD_REPEAT);
+ current_sctx = save_current_sctx;
+ sourcing_lnum = save_sourcing_lnum;
+ sourcing_name = save_sourcing_name;
+ return retval;
+}
-/// Read the file "fname" and execute its lines as EX commands.
+/// Reads the file `fname` and executes its lines as Ex commands.
///
/// This function may be called recursively!
///
+/// @see do_source_str
+///
/// @param fname
/// @param check_other check for .vimrc and _vimrc
/// @param is_vimrc DOSO_ value
@@ -3035,7 +3107,6 @@ int do_source(char_u *fname, int check_other, int is_vimrc)
int retval = FAIL;
static scid_T last_current_SID = 0;
static int last_current_SID_seq = 0;
- void *save_funccalp;
int save_debug_break_level = debug_break_level;
scriptitem_T *si = NULL;
proftime_T wait_start;
@@ -3124,6 +3195,7 @@ int do_source(char_u *fname, int check_other, int is_vimrc)
#endif
cookie.nextline = NULL;
+ cookie.sourcing_lnum = 0;
cookie.finished = false;
// Check if this script has a breakpoint.
@@ -3155,7 +3227,8 @@ int do_source(char_u *fname, int check_other, int is_vimrc)
// Don't use local function variables, if called from a function.
// Also starts profiling timer for nested script.
- save_funccalp = save_funccal();
+ funccal_entry_T funccalp_entry;
+ save_funccal(&funccalp_entry);
// Check if this script was sourced before to finds its SID.
// If it's new, generate a new SID.
@@ -3218,7 +3291,7 @@ int do_source(char_u *fname, int check_other, int is_vimrc)
cookie.conv.vc_type = CONV_NONE; // no conversion
// Read the first line so we can check for a UTF-8 BOM.
- firstline = getsourceline(0, (void *)&cookie, 0);
+ firstline = getsourceline(0, (void *)&cookie, 0, true);
if (firstline != NULL && STRLEN(firstline) >= 3 && firstline[0] == 0xef
&& firstline[1] == 0xbb && firstline[2] == 0xbf) {
// Found BOM; setup conversion, skip over BOM and recode the line.
@@ -3280,7 +3353,7 @@ int do_source(char_u *fname, int check_other, int is_vimrc)
}
current_sctx = save_current_sctx;
- restore_funccal(save_funccalp);
+ restore_funccal();
if (l_do_profiling == PROF_YES) {
prof_child_exit(&wait_start); // leaving a child now
}
@@ -3358,6 +3431,8 @@ char_u *get_scriptname(LastSet last_set, bool *should_free)
_("API client (channel id %" PRIu64 ")"),
last_set.channel_id);
return IObuff;
+ case SID_STR:
+ return (char_u *)_("anonymous :source");
default:
*should_free = true;
return home_replace_save(NULL,
@@ -3375,13 +3450,20 @@ void free_scriptnames(void)
}
# endif
+linenr_T get_sourced_lnum(LineGetter fgetline, void *cookie)
+{
+ return fgetline == getsourceline
+ ? ((struct source_cookie *)cookie)->sourcing_lnum
+ : sourcing_lnum;
+}
+
/// Get one full line from a sourced file.
/// Called by do_cmdline() when it's called from do_source().
///
/// @return pointer to the line in allocated memory, or NULL for end-of-file or
/// some error.
-char_u *getsourceline(int c, void *cookie, int indent)
+char_u *getsourceline(int c, void *cookie, int indent, bool do_concat)
{
struct source_cookie *sp = (struct source_cookie *)cookie;
char_u *line;
@@ -3395,6 +3477,8 @@ char_u *getsourceline(int c, void *cookie, int indent)
if (do_profiling == PROF_YES) {
script_line_end();
}
+ // Set the current sourcing line number.
+ sourcing_lnum = sp->sourcing_lnum + 1;
// Get current line. If there is a read-ahead line, use it, otherwise get
// one now.
if (sp->finished) {
@@ -3404,7 +3488,7 @@ char_u *getsourceline(int c, void *cookie, int indent)
} else {
line = sp->nextline;
sp->nextline = NULL;
- sourcing_lnum++;
+ sp->sourcing_lnum++;
}
if (line != NULL && do_profiling == PROF_YES) {
script_line_start();
@@ -3412,9 +3496,9 @@ char_u *getsourceline(int c, void *cookie, int indent)
// Only concatenate lines starting with a \ when 'cpoptions' doesn't
// contain the 'C' flag.
- if (line != NULL && (vim_strchr(p_cpo, CPO_CONCAT) == NULL)) {
+ if (line != NULL && do_concat && (vim_strchr(p_cpo, CPO_CONCAT) == NULL)) {
// compensate for the one line read-ahead
- sourcing_lnum--;
+ sp->sourcing_lnum--;
// Get the next line and concatenate it when it starts with a
// backslash. We always need to read the next line, keep it in
@@ -3492,7 +3576,7 @@ static char_u *get_one_sourceline(struct source_cookie *sp)
ga_init(&ga, 1, 250);
// Loop until there is a finished line (or end-of-file).
- sourcing_lnum++;
+ sp->sourcing_lnum++;
for (;; ) {
// make room to read at least 120 (more) characters
ga_grow(&ga, 120);
@@ -3559,7 +3643,7 @@ retry:
// len&c parities (is faster than ((len-c)%2 == 0)) -- Acevedo
for (c = len - 2; c >= 0 && buf[c] == Ctrl_V; c--) {}
if ((len & 1) != (c & 1)) { // escaped NL, read more
- sourcing_lnum++;
+ sp->sourcing_lnum++;
continue;
}
diff --git a/src/nvim/ex_cmds_defs.h b/src/nvim/ex_cmds_defs.h
index 42ba1060e9..1f0560ae48 100644
--- a/src/nvim/ex_cmds_defs.h
+++ b/src/nvim/ex_cmds_defs.h
@@ -12,61 +12,60 @@
# include "ex_cmds_enum.generated.h"
#endif
-/*
- * When adding an Ex command:
- * 1. Add an entry to the table in src/nvim/ex_cmds.lua. Keep it sorted on the
- * shortest version of the command name that works. If it doesn't start with
- * a lower case letter, add it at the end.
- *
- * Each table entry is a table with the following keys:
- *
- * Key | Description
- * ------- | -------------------------------------------------------------
- * command | Name of the command. Required.
- * enum | Name of the enum entry. If not set defaults to CMD_{command}.
- * flags | A set of the flags from below list joined by bitwise or.
- * func | Name of the function containing the implementation.
- *
- * Referenced function should be either non-static one or defined in
- * ex_docmd.c and be coercible to ex_func_T type from below.
- *
- * All keys not described in the above table are reserved for future use.
- *
- * 2. Add a "case: CMD_xxx" in the big switch in ex_docmd.c.
- * 3. Add an entry in the index for Ex commands at ":help ex-cmd-index".
- * 4. Add documentation in ../doc/xxx.txt. Add a tag for both the short and
- * long name of the command.
- */
-
-#define RANGE 0x001 /* allow a linespecs */
-#define BANG 0x002 /* allow a ! after the command name */
-#define EXTRA 0x004 /* allow extra args after command name */
-#define XFILE 0x008 /* expand wildcards in extra part */
-#define NOSPC 0x010 /* no spaces allowed in the extra part */
-#define DFLALL 0x020 /* default file range is 1,$ */
-#define WHOLEFOLD 0x040 /* extend range to include whole fold also
- when less than two numbers given */
-#define NEEDARG 0x080 /* argument required */
-#define TRLBAR 0x100 /* check for trailing vertical bar */
-#define REGSTR 0x200 /* allow "x for register designation */
-#define COUNT 0x400 /* allow count in argument, after command */
-#define NOTRLCOM 0x800 /* no trailing comment allowed */
-#define ZEROR 0x1000 /* zero line number allowed */
-#define USECTRLV 0x2000 /* do not remove CTRL-V from argument */
-#define NOTADR 0x4000 /* number before command is not an address */
-#define EDITCMD 0x8000 /* allow "+command" argument */
-#define BUFNAME 0x10000 /* accepts buffer name */
-#define BUFUNL 0x20000 /* accepts unlisted buffer too */
-#define ARGOPT 0x40000 /* allow "++opt=val" argument */
-#define SBOXOK 0x80000 /* allowed in the sandbox */
-#define CMDWIN 0x100000 /* allowed in cmdline window; when missing
- * disallows editing another buffer when
- * curbuf_lock is set */
-#define MODIFY 0x200000 /* forbidden in non-'modifiable' buffer */
-#define EXFLAGS 0x400000 /* allow flags after count in argument */
-#define FILES (XFILE | EXTRA) /* multiple extra files allowed */
-#define WORD1 (EXTRA | NOSPC) /* one extra word allowed */
-#define FILE1 (FILES | NOSPC) /* 1 file allowed, defaults to current file */
+// When adding an Ex command:
+// 1. Add an entry to the table in src/nvim/ex_cmds.lua. Keep it sorted on the
+// shortest version of the command name that works. If it doesn't start with
+// a lower case letter, add it at the end.
+//
+// Each table entry is a table with the following keys:
+//
+// Key | Description
+// ------- | -------------------------------------------------------------
+// command | Name of the command. Required.
+// enum | Name of the enum entry. If not set defaults to CMD_{command}.
+// flags | A set of the flags from below list joined by bitwise or.
+// func | Name of the function containing the implementation.
+//
+// Referenced function should be either non-static one or defined in
+// ex_docmd.c and be coercible to ex_func_T type from below.
+//
+// All keys not described in the above table are reserved for future use.
+//
+// 2. Add a "case: CMD_xxx" in the big switch in ex_docmd.c.
+// 3. Add an entry in the index for Ex commands at ":help ex-cmd-index".
+// 4. Add documentation in ../doc/xxx.txt. Add a tag for both the short and
+// long name of the command.
+
+#define RANGE 0x001 // allow a linespecs
+#define BANG 0x002 // allow a ! after the command name
+#define EXTRA 0x004 // allow extra args after command name
+#define XFILE 0x008 // expand wildcards in extra part
+#define NOSPC 0x010 // no spaces allowed in the extra part
+#define DFLALL 0x020 // default file range is 1,$
+#define WHOLEFOLD 0x040 // extend range to include whole fold also
+ // when less than two numbers given
+#define NEEDARG 0x080 // argument required
+#define TRLBAR 0x100 // check for trailing vertical bar
+#define REGSTR 0x200 // allow "x for register designation
+#define COUNT 0x400 // allow count in argument, after command
+#define NOTRLCOM 0x800 // no trailing comment allowed
+#define ZEROR 0x1000 // zero line number allowed
+#define USECTRLV 0x2000 // do not remove CTRL-V from argument
+#define NOTADR 0x4000 // number before command is not an address
+#define EDITCMD 0x8000 // allow "+command" argument
+#define BUFNAME 0x10000 // accepts buffer name
+#define BUFUNL 0x20000 // accepts unlisted buffer too
+#define ARGOPT 0x40000 // allow "++opt=val" argument
+#define SBOXOK 0x80000 // allowed in the sandbox
+#define CMDWIN 0x100000 // allowed in cmdline window; when missing
+ // disallows editing another buffer when
+ // curbuf_lock is set
+#define MODIFY 0x200000 // forbidden in non-'modifiable' buffer
+#define EXFLAGS 0x400000 // allow flags after count in argument
+#define RESTRICT 0x800000L // forbidden in restricted mode
+#define FILES (XFILE | EXTRA) // multiple extra files allowed
+#define WORD1 (EXTRA | NOSPC) // one extra word allowed
+#define FILE1 (FILES | NOSPC) // 1 file allowed, defaults to current file
// values for cmd_addr_type
#define ADDR_LINES 0
@@ -88,7 +87,7 @@ typedef struct exarg exarg_T;
typedef void (*ex_func_T)(exarg_T *eap);
-typedef char_u *(*LineGetter)(int, void *, int);
+typedef char_u *(*LineGetter)(int, void *, int, bool);
/// Structure for command definition.
typedef struct cmdname {
@@ -98,6 +97,47 @@ typedef struct cmdname {
int 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_u *arg; ///< argument of the command
@@ -121,14 +161,14 @@ struct exarg {
int regname; ///< register name (NUL if none)
int force_bin; ///< 0, FORCE_BIN or FORCE_NOBIN
int read_edit; ///< ++edit argument
- int force_ff; ///< ++ff= argument (index in cmd[])
+ int force_ff; ///< ++ff= argument (first char of argument)
int force_enc; ///< ++enc= argument (index in cmd[])
int bad_char; ///< BAD_KEEP, BAD_DROP or replacement byte
int useridx; ///< user command index
char_u *errmsg; ///< returned error message
LineGetter getline; ///< Function used to get the next line
void *cookie; ///< argument for getline()
- struct condstack *cstack; ///< condition stack for ":if" etc.
+ cstack_T *cstack; ///< condition stack for ":if" etc.
};
#define FORCE_BIN 1 // ":edit ++bin file"
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
index 72d39adb3e..5bf6aa73c6 100644
--- a/src/nvim/ex_docmd.c
+++ b/src/nvim/ex_docmd.c
@@ -1,9 +1,7 @@
// This is an open source non-commercial project. Dear PVS-Studio, please check
// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-/*
- * ex_docmd.c: functions for executing an Ex command line.
- */
+// ex_docmd.c: functions for executing an Ex command line.
#include <assert.h>
#include <string.h>
@@ -22,6 +20,7 @@
#include "nvim/digraph.h"
#include "nvim/edit.h"
#include "nvim/eval.h"
+#include "nvim/eval/userfunc.h"
#include "nvim/ex_cmds.h"
#include "nvim/ex_cmds2.h"
#include "nvim/ex_eval.h"
@@ -40,6 +39,7 @@
#include "nvim/menu.h"
#include "nvim/message.h"
#include "nvim/misc1.h"
+#include "nvim/ex_session.h"
#include "nvim/keymap.h"
#include "nvim/file_search.h"
#include "nvim/garray.h"
@@ -77,10 +77,7 @@
#include "nvim/api/private/helpers.h"
static int quitmore = 0;
-static int ex_pressedreturn = FALSE;
-
-/// Whether ":lcd" or ":tcd" was produced for a session.
-static int did_lcd;
+static bool ex_pressedreturn = false;
typedef struct ucmd {
char_u *uc_name; // The command name
@@ -117,11 +114,11 @@ typedef struct {
* reads more lines that may come from the while/for loop.
*/
struct loop_cookie {
- garray_T *lines_gap; /* growarray with line info */
- int current_line; /* last read line from growarray */
- int repeating; /* TRUE when looping a second time */
- /* When "repeating" is FALSE use "getline" and "cookie" to get lines */
- char_u *(*getline)(int, void *, int);
+ garray_T *lines_gap; // growarray with line info
+ int current_line; // last read line from growarray
+ int repeating; // TRUE when looping a second time
+ // When "repeating" is FALSE use "getline" and "cookie" to get lines
+ char_u *(*getline)(int, void *, int, bool);
void *cookie;
};
@@ -140,6 +137,31 @@ struct dbg_stuff {
except_T *current_exception;
};
+typedef struct {
+ // parsed results
+ exarg_T *eap;
+ char_u *parsed_upto; // local we've parsed up to so far
+ char_u *cmd; // start of command
+ char_u *after_modifier;
+
+ // errors
+ char_u *errormsg;
+
+ // globals that need to be updated
+ cmdmod_T cmdmod;
+ int sandbox;
+ int msg_silent;
+ int emsg_silent;
+ bool ex_pressedreturn;
+ long p_verbose;
+
+ // other side-effects
+ bool set_eventignore;
+ long verbose_save;
+ int save_msg_silent;
+ int did_esilent;
+ bool did_sandbox;
+} parse_state_T;
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ex_docmd.c.generated.h"
@@ -176,7 +198,7 @@ static void save_dbg_stuff(struct dbg_stuff *dsp)
static void restore_dbg_stuff(struct dbg_stuff *dsp)
{
- suppress_errthrow = FALSE;
+ suppress_errthrow = false;
trylevel = dsp->trylevel;
force_abort = dsp->force_abort;
caught_stack = dsp->caught_stack;
@@ -271,25 +293,23 @@ int do_cmdline_cmd(const char *cmd)
DOCMD_NOWAIT|DOCMD_KEYTYPED);
}
-/*
- * do_cmdline(): execute one Ex command line
- *
- * 1. Execute "cmdline" when it is not NULL.
- * If "cmdline" is NULL, or more lines are needed, fgetline() is used.
- * 2. Split up in parts separated with '|'.
- *
- * This function can be called recursively!
- *
- * flags:
- * DOCMD_VERBOSE - The command will be included in the error message.
- * DOCMD_NOWAIT - Don't call wait_return() and friends.
- * DOCMD_REPEAT - Repeat execution until fgetline() returns NULL.
- * DOCMD_KEYTYPED - Don't reset KeyTyped.
- * DOCMD_EXCRESET - Reset the exception environment (used for debugging).
- * DOCMD_KEEPLINE - Store first typed line (for repeating with ".").
- *
- * return FAIL if cmdline could not be executed, OK otherwise
- */
+/// do_cmdline(): execute one Ex command line
+///
+/// 1. Execute "cmdline" when it is not NULL.
+/// If "cmdline" is NULL, or more lines are needed, fgetline() is used.
+/// 2. Split up in parts separated with '|'.
+///
+/// This function can be called recursively!
+///
+/// flags:
+/// DOCMD_VERBOSE - The command will be included in the error message.
+/// DOCMD_NOWAIT - Don't call wait_return() and friends.
+/// DOCMD_REPEAT - Repeat execution until fgetline() returns NULL.
+/// DOCMD_KEYTYPED - Don't reset KeyTyped.
+/// DOCMD_EXCRESET - Reset the exception environment (used for debugging).
+/// DOCMD_KEEPLINE - Store first typed line (for repeating with ".").
+///
+/// @return FAIL if cmdline could not be executed, OK otherwise
int do_cmdline(char_u *cmdline, LineGetter fgetline,
void *cookie, /* argument for fgetline() */
int flags)
@@ -302,19 +322,19 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline,
int count = 0; /* line number count */
int did_inc = FALSE; /* incremented RedrawingDisabled */
int retval = OK;
- struct condstack cstack; /* conditional stack */
- garray_T lines_ga; /* keep lines for ":while"/":for" */
- int current_line = 0; /* active line in lines_ga */
- char_u *fname = NULL; /* function or script name */
- 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 */
+ cstack_T cstack; // conditional stack
+ garray_T lines_ga; // keep lines for ":while"/":for"
+ int current_line = 0; // active line in lines_ga
+ char_u *fname = NULL; // function or script name
+ 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;
struct msglist **saved_msg_list = NULL;
struct msglist *private_msg_list;
- /* "fgetline" and "cookie" passed to do_one_cmd() */
- char_u *(*cmd_getline)(int, void *, int);
+ // "fgetline" and "cookie" passed to do_one_cmd()
+ char_u *(*cmd_getline)(int, void *, int, bool);
void *cmd_cookie;
struct loop_cookie cmd_loop_cookie;
void *real_cookie;
@@ -338,7 +358,7 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline,
EMSG(_("E169: Command too recursive"));
// When converting to an exception, we do not include the command name
// since this is not an error of the specific command.
- do_errthrow((struct condstack *)NULL, (char_u *)NULL);
+ do_errthrow((cstack_T *)NULL, (char_u *)NULL);
msg_list = saved_msg_list;
return FAIL;
}
@@ -375,8 +395,8 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline,
* Initialize "force_abort" and "suppress_errthrow" at the top level.
*/
if (!recursive) {
- force_abort = FALSE;
- suppress_errthrow = FALSE;
+ force_abort = false;
+ suppress_errthrow = false;
}
// If requested, store and reset the global values controlling the
@@ -396,13 +416,12 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline,
// If force_abort is set, we cancel everything.
did_emsg = false;
- /*
- * KeyTyped is only set when calling vgetc(). Reset it here when not
- * calling vgetc() (sourced command lines).
- */
+ // KeyTyped is only set when calling vgetc(). Reset it here when not
+ // calling vgetc() (sourced command lines).
if (!(flags & DOCMD_KEYTYPED)
- && !getline_equal(fgetline, cookie, getexline))
+ && !getline_equal(fgetline, cookie, getexline)) {
KeyTyped = false;
+ }
/*
* Continue executing command lines:
@@ -507,17 +526,20 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline,
* Need to set msg_didout for the first line after an ":if",
* otherwise the ":if" will be overwritten.
*/
- if (count == 1 && getline_equal(fgetline, cookie, getexline))
- msg_didout = TRUE;
- if (fgetline == NULL || (next_cmdline = fgetline(':', cookie,
- cstack.cs_idx <
- 0 ? 0 : (cstack.cs_idx + 1) * 2
- )) == NULL) {
- /* Don't call wait_return for aborted command line. The NULL
- * returned for the end of a sourced file or executed function
- * doesn't do this. */
- if (KeyTyped && !(flags & DOCMD_REPEAT))
- need_wait_return = FALSE;
+ if (count == 1 && getline_equal(fgetline, cookie, getexline)) {
+ msg_didout = true;
+ }
+ if (fgetline == NULL
+ || (next_cmdline = fgetline(':', cookie,
+ cstack.cs_idx <
+ 0 ? 0 : (cstack.cs_idx + 1) * 2,
+ true)) == NULL) {
+ // Don't call wait_return for aborted command line. The NULL
+ // returned for the end of a sourced file or executed function
+ // doesn't do this.
+ if (KeyTyped && !(flags & DOCMD_REPEAT)) {
+ need_wait_return = false;
+ }
retval = FAIL;
break;
}
@@ -858,16 +880,14 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline,
xfree(sourcing_name);
sourcing_name = saved_sourcing_name;
sourcing_lnum = saved_sourcing_lnum;
+ } else if (got_int || (did_emsg && force_abort)) {
+ // On an interrupt or an aborting error not converted to an exception,
+ // disable the conversion of errors to exceptions. (Interrupts are not
+ // converted any more, here.) This enables also the interrupt message
+ // when force_abort is set and did_emsg unset in case of an interrupt
+ // from a finally clause after an error.
+ suppress_errthrow = true;
}
- /*
- * On an interrupt or an aborting error not converted to an exception,
- * disable the conversion of errors to exceptions. (Interrupts are not
- * converted any more, here.) This enables also the interrupt message
- * when force_abort is set and did_emsg unset in case of an interrupt
- * from a finally clause after an error.
- */
- else if (got_int || (did_emsg && force_abort))
- suppress_errthrow = TRUE;
}
// The current cstack will be freed when do_cmdline() returns. An uncaught
@@ -951,7 +971,7 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline,
/*
* Obtain a line when inside a ":while" or ":for" loop.
*/
-static char_u *get_loop_line(int c, void *cookie, int indent)
+static char_u *get_loop_line(int c, void *cookie, int indent, bool do_concat)
{
struct loop_cookie *cp = (struct loop_cookie *)cookie;
wcmd_T *wp;
@@ -961,11 +981,12 @@ static char_u *get_loop_line(int c, void *cookie, int indent)
if (cp->repeating)
return NULL; /* trying to read past ":endwhile"/":endfor" */
- /* First time inside the ":while"/":for": get line normally. */
- if (cp->getline == NULL)
- line = getcmdline(c, 0L, indent);
- else
- line = cp->getline(c, cp->cookie, indent);
+ // First time inside the ":while"/":for": get line normally.
+ if (cp->getline == NULL) {
+ line = getcmdline(c, 0L, indent, do_concat);
+ } else {
+ line = cp->getline(c, cp->cookie, indent, do_concat);
+ }
if (line != NULL) {
store_loop_line(cp->lines_gap, line);
++cp->current_line;
@@ -1197,69 +1218,74 @@ static void get_wincmd_addr_type(char_u *arg, exarg_T *eap)
}
}
-/*
- * Execute one Ex command.
- *
- * If 'sourcing' is TRUE, the command will be included in the error message.
- *
- * 1. skip comment lines and leading space
- * 2. handle command modifiers
- * 3. skip over the range to find the command
- * 4. parse the range
- * 5. parse the command
- * 6. parse arguments
- * 7. switch on command name
- *
- * Note: "fgetline" can be NULL.
- *
- * This function may be called recursively!
- */
-static char_u * do_one_cmd(char_u **cmdlinep,
- int flags,
- struct condstack *cstack,
- LineGetter fgetline,
- void *cookie /* argument for fgetline() */
- )
+/// Skip colons and trailing whitespace, returning a pointer to the first
+/// non-colon, non-whitespace character.
+//
+/// @param skipleadingwhite Skip leading whitespace too
+static char_u *skip_colon_white(const char_u *p, bool skipleadingwhite)
{
- char_u *p;
- linenr_T lnum;
- long n;
- char_u *errormsg = NULL; /* error message */
- exarg_T ea; /* Ex command arguments */
- long verbose_save = -1;
- int save_msg_scroll = msg_scroll;
- int save_msg_silent = -1;
- int did_esilent = 0;
- int did_sandbox = FALSE;
- cmdmod_T save_cmdmod;
- const int save_reg_executing = reg_executing;
- char_u *cmd;
- int address_count = 1;
+ if (skipleadingwhite) {
+ p = skipwhite(p);
+ }
- memset(&ea, 0, sizeof(ea));
- ea.line1 = 1;
- ea.line2 = 1;
- ex_nesting_level++;
+ while (*p == ':') {
+ p = skipwhite(p + 1);
+ }
- /* When the last file has not been edited :q has to be typed twice. */
- if (quitmore
- /* 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)
- )
- --quitmore;
+ return (char_u *)p;
+}
- /*
- * Reset browse, confirm, etc.. They are restored when returning, for
- * recursive calls.
- */
- save_cmdmod = cmdmod;
- memset(&cmdmod, 0, sizeof(cmdmod));
+static void parse_state_to_global(const parse_state_T *parse_state)
+{
+ cmdmod = parse_state->cmdmod;
+ sandbox = parse_state->sandbox;
+ msg_silent = parse_state->msg_silent;
+ emsg_silent = parse_state->emsg_silent;
+ ex_pressedreturn = parse_state->ex_pressedreturn;
+ p_verbose = parse_state->p_verbose;
- /* "#!anything" is handled like a comment. */
- if ((*cmdlinep)[0] == '#' && (*cmdlinep)[1] == '!')
- goto doend;
+ if (parse_state->set_eventignore) {
+ set_string_option_direct(
+ (char_u *)"ei", -1, (char_u *)"all", OPT_FREE, SID_NONE);
+ }
+}
+
+static void parse_state_from_global(parse_state_T *parse_state)
+{
+ memset(parse_state, 0, sizeof(*parse_state));
+ parse_state->cmdmod = cmdmod;
+ parse_state->sandbox = sandbox;
+ parse_state->msg_silent = msg_silent;
+ parse_state->emsg_silent = emsg_silent;
+ parse_state->ex_pressedreturn = ex_pressedreturn;
+ parse_state->p_verbose = p_verbose;
+}
+
+//
+// Parse one Ex command.
+//
+// This has no side-effects, except for modifying parameters
+// passed in by pointer.
+//
+// The `out` should be zeroed, and its `ea` member initialised,
+// before calling this function.
+//
+static bool parse_one_cmd(
+ char_u **cmdlinep,
+ parse_state_T *const out,
+ LineGetter fgetline,
+ void *fgetline_cookie)
+{
+ exarg_T ea = {
+ .line1 = 1,
+ .line2 = 1,
+ };
+ *out->eap = ea;
+
+ // "#!anything" is handled like a comment.
+ if ((*cmdlinep)[0] == '#' && (*cmdlinep)[1] == '!') {
+ return false;
+ }
/*
* Repeat until no more command modifiers are found.
@@ -1269,70 +1295,76 @@ static char_u * do_one_cmd(char_u **cmdlinep,
/*
* 1. Skip comment lines and leading white space and colons.
*/
- while (*ea.cmd == ' ' || *ea.cmd == '\t' || *ea.cmd == ':')
- ++ea.cmd;
+ while (*ea.cmd == ' '
+ || *ea.cmd == '\t'
+ || *ea.cmd == ':') {
+ ea.cmd++;
+ }
- /* in ex mode, an empty line works like :+ */
+ // in ex mode, an empty line works like :+
if (*ea.cmd == NUL && exmode_active
- && (getline_equal(fgetline, cookie, getexmodeline)
- || getline_equal(fgetline, cookie, getexline))
+ && (getline_equal(fgetline, fgetline_cookie, getexmodeline)
+ || getline_equal(fgetline, fgetline_cookie, getexline))
&& curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) {
ea.cmd = (char_u *)"+";
- ex_pressedreturn = TRUE;
+ out->ex_pressedreturn = true;
}
- /* ignore comment and empty lines */
- if (*ea.cmd == '"')
- goto doend;
+ // ignore comment and empty lines
+ if (*ea.cmd == '"') {
+ return false;
+ }
if (*ea.cmd == NUL) {
- ex_pressedreturn = TRUE;
- goto doend;
+ out->ex_pressedreturn = true;
+ return false;
}
/*
* 2. Handle command modifiers.
*/
- p = skip_range(ea.cmd, NULL);
+ char_u *p = skip_range(ea.cmd, NULL);
switch (*p) {
- /* When adding an entry, also modify cmd_exists(). */
+ // When adding an entry, also modify cmd_exists().
case 'a': if (!checkforcmd(&ea.cmd, "aboveleft", 3))
break;
- cmdmod.split |= WSP_ABOVE;
+ out->cmdmod.split |= WSP_ABOVE;
continue;
case 'b': if (checkforcmd(&ea.cmd, "belowright", 3)) {
- cmdmod.split |= WSP_BELOW;
+ out->cmdmod.split |= WSP_BELOW;
continue;
}
if (checkforcmd(&ea.cmd, "browse", 3)) {
- cmdmod.browse = true;
+ out->cmdmod.browse = true;
continue;
}
- if (!checkforcmd(&ea.cmd, "botright", 2))
+ if (!checkforcmd(&ea.cmd, "botright", 2)) {
break;
- cmdmod.split |= WSP_BOT;
+ }
+ out->cmdmod.split |= WSP_BOT;
continue;
case 'c': if (!checkforcmd(&ea.cmd, "confirm", 4))
break;
- cmdmod.confirm = true;
+ out->cmdmod.confirm = true;
continue;
case 'k': if (checkforcmd(&ea.cmd, "keepmarks", 3)) {
- cmdmod.keepmarks = true;
+ out->cmdmod.keepmarks = true;
continue;
}
if (checkforcmd(&ea.cmd, "keepalt", 5)) {
- cmdmod.keepalt = true;
+ out->cmdmod.keepalt = true;
continue;
}
if (checkforcmd(&ea.cmd, "keeppatterns", 5)) {
- cmdmod.keeppatterns = true;
+ out->cmdmod.keeppatterns = true;
continue;
}
- if (!checkforcmd(&ea.cmd, "keepjumps", 5))
+ if (!checkforcmd(&ea.cmd, "keepjumps", 5)) {
break;
- cmdmod.keepjumps = true;
+ }
+ out->cmdmod.keepjumps = true;
continue;
case 'f': { // only accept ":filter {pat} cmd"
@@ -1342,7 +1374,7 @@ static char_u * do_one_cmd(char_u **cmdlinep,
break;
}
if (*p == '!') {
- cmdmod.filter_force = true;
+ out->cmdmod.filter_force = true;
p = skipwhite(p + 1);
if (*p == NUL || ends_excmd(*p)) {
break;
@@ -1352,134 +1384,217 @@ static char_u * do_one_cmd(char_u **cmdlinep,
if (p == NULL || *p == NUL) {
break;
}
- cmdmod.filter_regmatch.regprog = vim_regcomp(reg_pat, RE_MAGIC);
- if (cmdmod.filter_regmatch.regprog == NULL) {
+ out->cmdmod.filter_regmatch.regprog = vim_regcomp(reg_pat, RE_MAGIC);
+ if (out->cmdmod.filter_regmatch.regprog == NULL) {
break;
}
ea.cmd = p;
continue;
}
- /* ":hide" and ":hide | cmd" are not modifiers */
+ // ":hide" and ":hide | cmd" are not modifiers
case 'h': if (p != ea.cmd || !checkforcmd(&p, "hide", 3)
|| *p == NUL || ends_excmd(*p))
break;
ea.cmd = p;
- cmdmod.hide = true;
+ out->cmdmod.hide = true;
continue;
case 'l': if (checkforcmd(&ea.cmd, "lockmarks", 3)) {
- cmdmod.lockmarks = true;
+ out->cmdmod.lockmarks = true;
continue;
}
- if (!checkforcmd(&ea.cmd, "leftabove", 5))
+ if (!checkforcmd(&ea.cmd, "leftabove", 5)) {
break;
- cmdmod.split |= WSP_ABOVE;
+ }
+ out->cmdmod.split |= WSP_ABOVE;
continue;
case 'n':
if (checkforcmd(&ea.cmd, "noautocmd", 3)) {
- if (cmdmod.save_ei == NULL) {
- /* Set 'eventignore' to "all". Restore the
- * existing option value later. */
- cmdmod.save_ei = vim_strsave(p_ei);
- set_string_option_direct(
- (char_u *)"ei", -1, (char_u *)"all", OPT_FREE, SID_NONE);
+ if (out->cmdmod.save_ei == NULL) {
+ // Set 'eventignore' to "all". Restore the
+ // existing option value later.
+ out->cmdmod.save_ei = vim_strsave(p_ei);
+ out->set_eventignore = true;
}
continue;
}
if (!checkforcmd(&ea.cmd, "noswapfile", 3)) {
break;
}
- cmdmod.noswapfile = true;
+ out->cmdmod.noswapfile = true;
continue;
case 'r': if (!checkforcmd(&ea.cmd, "rightbelow", 6))
break;
- cmdmod.split |= WSP_BELOW;
+ out->cmdmod.split |= WSP_BELOW;
continue;
case 's': if (checkforcmd(&ea.cmd, "sandbox", 3)) {
- if (!did_sandbox)
- ++sandbox;
- did_sandbox = TRUE;
+ if (!out->did_sandbox) {
+ out->sandbox++;
+ }
+ out->did_sandbox = true;
continue;
}
- if (!checkforcmd(&ea.cmd, "silent", 3))
+ if (!checkforcmd(&ea.cmd, "silent", 3)) {
break;
- if (save_msg_silent == -1)
- save_msg_silent = msg_silent;
- ++msg_silent;
+ }
+ if (out->save_msg_silent == -1) {
+ out->save_msg_silent = out->msg_silent;
+ }
+ out->msg_silent++;
if (*ea.cmd == '!' && !ascii_iswhite(ea.cmd[-1])) {
- /* ":silent!", but not "silent !cmd" */
+ // ":silent!", but not "silent !cmd"
ea.cmd = skipwhite(ea.cmd + 1);
- ++emsg_silent;
- ++did_esilent;
+ out->emsg_silent++;
+ out->did_esilent++;
}
continue;
case 't': if (checkforcmd(&p, "tab", 3)) {
- long tabnr = get_address(&ea, &ea.cmd, ADDR_TABS, ea.skip, false, 1);
+ long tabnr = get_address(
+ &ea, &ea.cmd, ADDR_TABS, ea.skip, false, 1);
+
if (tabnr == MAXLNUM) {
- cmdmod.tab = tabpage_index(curtab) + 1;
+ out->cmdmod.tab = tabpage_index(curtab) + 1;
} else {
if (tabnr < 0 || tabnr > LAST_TAB_NR) {
- errormsg = (char_u *)_(e_invrange);
- goto doend;
+ out->errormsg = (char_u *)_(e_invrange);
+ return false;
}
- cmdmod.tab = tabnr + 1;
+ out->cmdmod.tab = tabnr + 1;
}
ea.cmd = p;
continue;
}
- if (!checkforcmd(&ea.cmd, "topleft", 2))
+ if (!checkforcmd(&ea.cmd, "topleft", 2)) {
break;
- cmdmod.split |= WSP_TOP;
+ }
+ out->cmdmod.split |= WSP_TOP;
continue;
case 'u': if (!checkforcmd(&ea.cmd, "unsilent", 3))
break;
- if (save_msg_silent == -1)
- save_msg_silent = msg_silent;
- msg_silent = 0;
+ if (out->save_msg_silent == -1) {
+ out->save_msg_silent = out->msg_silent;
+ }
+ out->msg_silent = 0;
continue;
case 'v': if (checkforcmd(&ea.cmd, "vertical", 4)) {
- cmdmod.split |= WSP_VERT;
+ out->cmdmod.split |= WSP_VERT;
continue;
}
if (!checkforcmd(&p, "verbose", 4))
break;
- if (verbose_save < 0)
- verbose_save = p_verbose;
- if (ascii_isdigit(*ea.cmd))
- p_verbose = atoi((char *)ea.cmd);
- else
- p_verbose = 1;
+ if (out->verbose_save < 0) {
+ out->verbose_save = out->p_verbose;
+ }
+ if (ascii_isdigit(*ea.cmd)) {
+ out->p_verbose = atoi((char *)ea.cmd);
+ } else {
+ out->p_verbose = 1;
+ }
ea.cmd = p;
continue;
}
break;
}
- char_u *after_modifier = ea.cmd;
-
- ea.skip = (did_emsg
- || got_int
- || current_exception
- || (cstack->cs_idx >= 0
- && !(cstack->cs_flags[cstack->cs_idx] & CSF_ACTIVE)));
+ out->after_modifier = ea.cmd;
// 3. Skip over the range to find the command. Let "p" point to after it.
//
// We need the command to know what kind of range it uses.
- cmd = ea.cmd;
+ out->cmd = ea.cmd;
ea.cmd = skip_range(ea.cmd, NULL);
if (*ea.cmd == '*') {
ea.cmd = skipwhite(ea.cmd + 1);
}
- p = find_command(&ea, NULL);
+ out->parsed_upto = find_command(&ea, NULL);
+
+ *out->eap = ea;
+
+ return true;
+}
+
+/*
+ * Execute one Ex command.
+ *
+ * If 'sourcing' is TRUE, the command will be included in the error message.
+ *
+ * 1. skip comment lines and leading space
+ * 2. handle command modifiers
+ * 3. skip over the range to find the command
+ * 4. parse the range
+ * 5. parse the command
+ * 6. parse arguments
+ * 7. switch on command name
+ *
+ * Note: "fgetline" can be NULL.
+ *
+ * This function may be called recursively!
+ */
+static char_u * do_one_cmd(char_u **cmdlinep,
+ int flags,
+ cstack_T *cstack,
+ LineGetter fgetline,
+ void *cookie /* argument for fgetline() */
+ )
+{
+ char_u *p;
+ linenr_T lnum;
+ long n;
+ char_u *errormsg = NULL; // error message
+ exarg_T ea;
+ int save_msg_scroll = msg_scroll;
+ parse_state_T parsed;
+ cmdmod_T save_cmdmod;
+ const int save_reg_executing = reg_executing;
+
+ ex_nesting_level++;
+
+ /* When the last file has not been edited :q has to be typed twice. */
+ if (quitmore
+ /* 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)
+ )
+ --quitmore;
+
+ /*
+ * Reset browse, confirm, etc.. They are restored when returning, for
+ * recursive calls.
+ */
+ save_cmdmod = cmdmod;
+ memset(&cmdmod, 0, sizeof(cmdmod));
+
+ parse_state_from_global(&parsed);
+ parsed.eap = &ea;
+ parsed.verbose_save = -1;
+ parsed.save_msg_silent = -1;
+ parsed.did_esilent = 0;
+ parsed.did_sandbox = false;
+ bool parse_success = parse_one_cmd(cmdlinep, &parsed, fgetline, cookie);
+ parse_state_to_global(&parsed);
+
+ // Update locals from parse_one_cmd()
+ errormsg = parsed.errormsg;
+ p = parsed.parsed_upto;
+
+ if (!parse_success) {
+ goto doend;
+ }
+
+ ea.skip = (did_emsg
+ || got_int
+ || current_exception
+ || (cstack->cs_idx >= 0
+ && !(cstack->cs_flags[cstack->cs_idx] & CSF_ACTIVE)));
// Count this line for profiling if skip is TRUE.
if (do_profiling == PROF_YES
@@ -1550,148 +1665,9 @@ static char_u * do_one_cmd(char_u **cmdlinep,
}
}
- /* repeat for all ',' or ';' separated addresses */
- ea.cmd = cmd;
- for (;; ) {
- ea.line1 = ea.line2;
- switch (ea.addr_type) {
- case ADDR_LINES:
- // default is current line number
- ea.line2 = curwin->w_cursor.lnum;
- break;
- case ADDR_WINDOWS:
- ea.line2 = CURRENT_WIN_NR;
- break;
- case ADDR_ARGUMENTS:
- ea.line2 = curwin->w_arg_idx + 1;
- if (ea.line2 > ARGCOUNT) {
- ea.line2 = ARGCOUNT;
- }
- break;
- case ADDR_LOADED_BUFFERS:
- case ADDR_BUFFERS:
- ea.line2 = curbuf->b_fnum;
- break;
- case ADDR_TABS:
- ea.line2 = CURRENT_TAB_NR;
- break;
- case ADDR_TABS_RELATIVE:
- ea.line2 = 1;
- break;
- case ADDR_QUICKFIX:
- ea.line2 = qf_get_cur_valid_idx(&ea);
- break;
- }
- ea.cmd = skipwhite(ea.cmd);
- lnum = get_address(&ea, &ea.cmd, ea.addr_type, ea.skip,
- ea.addr_count == 0, address_count++);
- if (ea.cmd == NULL) { // error detected
- goto doend;
- }
- if (lnum == MAXLNUM) {
- if (*ea.cmd == '%') { /* '%' - all lines */
- ++ea.cmd;
- switch (ea.addr_type) {
- case ADDR_LINES:
- ea.line1 = 1;
- ea.line2 = curbuf->b_ml.ml_line_count;
- break;
- case ADDR_LOADED_BUFFERS: {
- buf_T *buf = firstbuf;
- while (buf->b_next != NULL && buf->b_ml.ml_mfp == NULL) {
- buf = buf->b_next;
- }
- ea.line1 = buf->b_fnum;
- buf = lastbuf;
- while (buf->b_prev != NULL && buf->b_ml.ml_mfp == NULL) {
- buf = buf->b_prev;
- }
- ea.line2 = buf->b_fnum;
- break;
- }
- case ADDR_BUFFERS:
- ea.line1 = firstbuf->b_fnum;
- ea.line2 = lastbuf->b_fnum;
- break;
- case ADDR_WINDOWS:
- case ADDR_TABS:
- if (IS_USER_CMDIDX(ea.cmdidx)) {
- ea.line1 = 1;
- ea.line2 =
- ea.addr_type == ADDR_WINDOWS ? LAST_WIN_NR : LAST_TAB_NR;
- } else {
- // there is no Vim command which uses '%' and
- // ADDR_WINDOWS or ADDR_TABS
- errormsg = (char_u *)_(e_invrange);
- goto doend;
- }
- break;
- case ADDR_TABS_RELATIVE:
- errormsg = (char_u *)_(e_invrange);
- goto doend;
- break;
- case ADDR_ARGUMENTS:
- if (ARGCOUNT == 0) {
- ea.line1 = ea.line2 = 0;
- } else {
- ea.line1 = 1;
- ea.line2 = ARGCOUNT;
- }
- break;
- case ADDR_QUICKFIX:
- ea.line1 = 1;
- ea.line2 = qf_get_size(&ea);
- if (ea.line2 == 0) {
- ea.line2 = 1;
- }
- break;
- }
- ++ea.addr_count;
- }
- /* '*' - visual area */
- else if (*ea.cmd == '*') {
- pos_T *fp;
-
- if (ea.addr_type != ADDR_LINES) {
- errormsg = (char_u *)_(e_invrange);
- goto doend;
- }
-
- ++ea.cmd;
- if (!ea.skip) {
- fp = getmark('<', FALSE);
- if (check_mark(fp) == FAIL)
- goto doend;
- ea.line1 = fp->lnum;
- fp = getmark('>', FALSE);
- if (check_mark(fp) == FAIL)
- goto doend;
- ea.line2 = fp->lnum;
- ++ea.addr_count;
- }
- }
- } else
- ea.line2 = lnum;
- ea.addr_count++;
-
- if (*ea.cmd == ';') {
- if (!ea.skip) {
- curwin->w_cursor.lnum = ea.line2;
- // don't leave the cursor on an illegal line or column
- check_cursor();
- }
- } else if (*ea.cmd != ',') {
- break;
- }
- ea.cmd++;
- }
-
- /* One address given: set start and end lines */
- if (ea.addr_count == 1) {
- ea.line1 = ea.line2;
- /* ... but only implicit: really no address given */
- if (lnum == MAXLNUM)
- ea.addr_count = 0;
+ ea.cmd = parsed.cmd;
+ if (parse_cmd_address(&ea, &errormsg) == FAIL) {
+ goto doend;
}
/*
@@ -1701,9 +1677,7 @@ static char_u * do_one_cmd(char_u **cmdlinep,
/*
* Skip ':' and any white space
*/
- ea.cmd = skipwhite(ea.cmd);
- while (*ea.cmd == ':')
- ea.cmd = skipwhite(ea.cmd + 1);
+ ea.cmd = skip_colon_white(ea.cmd, true);
/*
* If we got a line, but no command, then go to the line.
@@ -1772,8 +1746,8 @@ static char_u * do_one_cmd(char_u **cmdlinep,
if (!(flags & DOCMD_VERBOSE)) {
// If the modifier was parsed OK the error must be in the following
// command
- if (after_modifier != NULL) {
- append_command(after_modifier);
+ if (parsed.after_modifier != NULL) {
+ append_command(parsed.after_modifier);
} else {
append_command(*cmdlinep);
}
@@ -1808,10 +1782,14 @@ static char_u * do_one_cmd(char_u **cmdlinep,
if (!ea.skip) {
if (sandbox != 0 && !(ea.argt & SBOXOK)) {
- /* Command not allowed in sandbox. */
+ // Command not allowed in sandbox.
errormsg = (char_u *)_(e_sandbox);
goto doend;
}
+ if (restricted != 0 && (ea.argt & RESTRICT)) {
+ errormsg = (char_u *)_("E981: Command not allowed in restricted mode");
+ goto doend;
+ }
if (!MODIFIABLE(curbuf) && (ea.argt & MODIFY)
// allow :put in terminals
&& (!curbuf->terminal || ea.cmdidx != CMD_put)) {
@@ -2144,6 +2122,7 @@ static char_u * do_one_cmd(char_u **cmdlinep,
case CMD_browse:
case CMD_call:
case CMD_confirm:
+ case CMD_const:
case CMD_delfunction:
case CMD_djump:
case CMD_dlist:
@@ -2168,6 +2147,7 @@ static char_u * do_one_cmd(char_u **cmdlinep,
case CMD_leftabove:
case CMD_let:
case CMD_lockmarks:
+ case CMD_lockvar:
case CMD_lua:
case CMD_match:
case CMD_mzscheme:
@@ -2196,6 +2176,7 @@ static char_u * do_one_cmd(char_u **cmdlinep,
case CMD_tilde:
case CMD_topleft:
case CMD_unlet:
+ case CMD_unlockvar:
case CMD_verbose:
case CMD_vertical:
case CMD_wincmd:
@@ -2241,12 +2222,12 @@ static char_u * do_one_cmd(char_u **cmdlinep,
// The :try command saves the emsg_silent flag, reset it here when
// ":silent! try" was used, it should only apply to :try itself.
- if (ea.cmdidx == CMD_try && did_esilent > 0) {
- emsg_silent -= did_esilent;
+ if (ea.cmdidx == CMD_try && parsed.did_esilent > 0) {
+ emsg_silent -= parsed.did_esilent;
if (emsg_silent < 0) {
emsg_silent = 0;
}
- did_esilent = 0;
+ parsed.did_esilent = 0;
}
// 7. Execute the command.
@@ -2312,8 +2293,9 @@ doend:
? cmdnames[(int)ea.cmdidx].cmd_name
: (char_u *)NULL);
- if (verbose_save >= 0)
- p_verbose = verbose_save;
+ if (parsed.verbose_save >= 0) {
+ p_verbose = parsed.verbose_save;
+ }
if (cmdmod.save_ei != NULL) {
/* Restore 'eventignore' to the value before ":noautocmd". */
set_string_option_direct((char_u *)"ei", -1, cmdmod.save_ei,
@@ -2328,16 +2310,18 @@ doend:
cmdmod = save_cmdmod;
reg_executing = save_reg_executing;
- if (save_msg_silent != -1) {
- /* messages could be enabled for a serious error, need to check if the
- * counters don't become negative */
- if (!did_emsg || msg_silent > save_msg_silent)
- msg_silent = save_msg_silent;
- emsg_silent -= did_esilent;
- if (emsg_silent < 0)
+ if (parsed.save_msg_silent != -1) {
+ // messages could be enabled for a serious error, need to check if the
+ // counters don't become negative
+ if (!did_emsg || msg_silent > parsed.save_msg_silent) {
+ msg_silent = parsed.save_msg_silent;
+ }
+ emsg_silent -= parsed.did_esilent;
+ if (emsg_silent < 0) {
emsg_silent = 0;
- /* Restore msg_scroll, it's set by file I/O commands, even when no
- * message is actually displayed. */
+ }
+ // Restore msg_scroll, it's set by file I/O commands, even when no
+ // message is actually displayed.
msg_scroll = save_msg_scroll;
/* "silent reg" or "silent echo x" inside "redir" leaves msg_col
@@ -2346,8 +2330,9 @@ doend:
msg_col = 0;
}
- if (did_sandbox)
- --sandbox;
+ if (parsed.did_sandbox) {
+ sandbox--;
+ }
if (ea.nextcmd && *ea.nextcmd == NUL) /* not really a next command */
ea.nextcmd = NULL;
@@ -2357,6 +2342,163 @@ doend:
return ea.nextcmd;
}
+// Parse the address range, if any, in "eap".
+// Return FAIL and set "errormsg" or return OK.
+int parse_cmd_address(exarg_T *eap, char_u **errormsg)
+ FUNC_ATTR_NONNULL_ALL
+{
+ int address_count = 1;
+ linenr_T lnum;
+
+ // Repeat for all ',' or ';' separated addresses.
+ for (;;) {
+ eap->line1 = eap->line2;
+ switch (eap->addr_type) {
+ case ADDR_LINES:
+ // default is current line number
+ eap->line2 = curwin->w_cursor.lnum;
+ break;
+ case ADDR_WINDOWS:
+ eap->line2 = CURRENT_WIN_NR;
+ break;
+ case ADDR_ARGUMENTS:
+ eap->line2 = curwin->w_arg_idx + 1;
+ if (eap->line2 > ARGCOUNT) {
+ eap->line2 = ARGCOUNT;
+ }
+ break;
+ case ADDR_LOADED_BUFFERS:
+ case ADDR_BUFFERS:
+ eap->line2 = curbuf->b_fnum;
+ break;
+ case ADDR_TABS:
+ eap->line2 = CURRENT_TAB_NR;
+ break;
+ case ADDR_TABS_RELATIVE:
+ eap->line2 = 1;
+ break;
+ case ADDR_QUICKFIX:
+ eap->line2 = qf_get_cur_valid_idx(eap);
+ break;
+ }
+ eap->cmd = skipwhite(eap->cmd);
+ lnum = get_address(eap, &eap->cmd, eap->addr_type, eap->skip,
+ eap->addr_count == 0, address_count++);
+ if (eap->cmd == NULL) { // error detected
+ return FAIL;
+ }
+ if (lnum == MAXLNUM) {
+ if (*eap->cmd == '%') { // '%' - all lines
+ eap->cmd++;
+ switch (eap->addr_type) {
+ case ADDR_LINES:
+ eap->line1 = 1;
+ eap->line2 = curbuf->b_ml.ml_line_count;
+ break;
+ case ADDR_LOADED_BUFFERS: {
+ buf_T *buf = firstbuf;
+
+ while (buf->b_next != NULL && buf->b_ml.ml_mfp == NULL) {
+ buf = buf->b_next;
+ }
+ eap->line1 = buf->b_fnum;
+ buf = lastbuf;
+ while (buf->b_prev != NULL && buf->b_ml.ml_mfp == NULL) {
+ buf = buf->b_prev;
+ }
+ eap->line2 = buf->b_fnum;
+ break;
+ }
+ case ADDR_BUFFERS:
+ eap->line1 = firstbuf->b_fnum;
+ eap->line2 = lastbuf->b_fnum;
+ break;
+ case ADDR_WINDOWS:
+ case ADDR_TABS:
+ if (IS_USER_CMDIDX(eap->cmdidx)) {
+ eap->line1 = 1;
+ eap->line2 = eap->addr_type == ADDR_WINDOWS
+ ? LAST_WIN_NR : LAST_TAB_NR;
+ } else {
+ // there is no Vim command which uses '%' and
+ // ADDR_WINDOWS or ADDR_TABS
+ *errormsg = (char_u *)_(e_invrange);
+ return FAIL;
+ }
+ break;
+ case ADDR_TABS_RELATIVE:
+ *errormsg = (char_u *)_(e_invrange);
+ return FAIL;
+ case ADDR_ARGUMENTS:
+ if (ARGCOUNT == 0) {
+ eap->line1 = eap->line2 = 0;
+ } else {
+ eap->line1 = 1;
+ eap->line2 = ARGCOUNT;
+ }
+ break;
+ case ADDR_QUICKFIX:
+ eap->line1 = 1;
+ eap->line2 = qf_get_size(eap);
+ if (eap->line2 == 0) {
+ eap->line2 = 1;
+ }
+ break;
+ }
+ eap->addr_count++;
+ } else if (*eap->cmd == '*') {
+ // '*' - visual area
+ if (eap->addr_type != ADDR_LINES) {
+ *errormsg = (char_u *)_(e_invrange);
+ return FAIL;
+ }
+
+ eap->cmd++;
+ if (!eap->skip) {
+ pos_T *fp = getmark('<', false);
+ if (check_mark(fp) == FAIL) {
+ return FAIL;
+ }
+ eap->line1 = fp->lnum;
+ fp = getmark('>', false);
+ if (check_mark(fp) == FAIL) {
+ return FAIL;
+ }
+ eap->line2 = fp->lnum;
+ eap->addr_count++;
+ }
+ }
+ } else {
+ eap->line2 = lnum;
+ }
+ eap->addr_count++;
+
+ if (*eap->cmd == ';') {
+ if (!eap->skip) {
+ curwin->w_cursor.lnum = eap->line2;
+ // Don't leave the cursor on an illegal line or column, but do
+ // accept zero as address, so 0;/PATTERN/ works correctly.
+ if (eap->line2 > 0) {
+ check_cursor();
+ }
+ }
+ } else if (*eap->cmd != ',') {
+ break;
+ }
+ eap->cmd++;
+ }
+
+ // One address given: set start and end lines.
+ if (eap->addr_count == 1) {
+ eap->line1 = eap->line2;
+ // ... but only implicit: really no address given
+ if (lnum == MAXLNUM) {
+ eap->addr_count = 0;
+ }
+ }
+ return OK;
+}
+
/*
* Check for an Ex command with optional tail.
* If there is a match advance "pp" to the argument and return TRUE.
@@ -2667,9 +2809,11 @@ int modifier_len(char_u *cmd)
for (j = 0; p[j] != NUL; ++j)
if (p[j] != cmdmods[i].name[j])
break;
- if (!ASCII_ISALPHA(p[j]) && j >= cmdmods[i].minlen
- && (p == cmd || cmdmods[i].has_count))
+ if (j >= cmdmods[i].minlen
+ && !ASCII_ISALPHA(p[j])
+ && (p == cmd || cmdmods[i].has_count)) {
return j + (int)(p - cmd);
+ }
}
return 0;
}
@@ -3297,6 +3441,7 @@ const char * set_one_cmd_context(
case CMD_syntax:
set_context_in_syntax_cmd(xp, arg);
break;
+ case CMD_const:
case CMD_let:
case CMD_if:
case CMD_elseif:
@@ -3537,15 +3682,13 @@ const char * set_one_cmd_context(
return NULL;
}
-/*
- * skip a range specifier of the form: addr [,addr] [;addr] ..
- *
- * Backslashed delimiters after / or ? will be skipped, and commands will
- * not be expanded between /'s and ?'s or after "'".
- *
- * Also skip white space and ":" characters.
- * Returns the "cmd" pointer advanced to beyond the range.
- */
+// Skip a range specifier of the form: addr [,addr] [;addr] ..
+//
+// Backslashed delimiters after / or ? will be skipped, and commands will
+// not be expanded between /'s and ?'s or after "'".
+//
+// Also skip white space and ":" characters.
+// Returns the "cmd" pointer advanced to beyond the range.
char_u *skip_range(
const char_u *cmd,
int *ctx // pointer to xp_context or NULL
@@ -3576,9 +3719,8 @@ char_u *skip_range(
++cmd;
}
- /* Skip ":" and white space. */
- while (*cmd == ':')
- cmd = skipwhite(cmd + 1);
+ // Skip ":" and white space.
+ cmd = skip_colon_white(cmd, false);
return (char_u *)cmd;
}
@@ -3746,8 +3888,7 @@ static linenr_T get_address(exarg_T *eap,
curwin->w_cursor.col = 0;
}
searchcmdlen = 0;
- if (!do_search(NULL, c, cmd, 1L,
- SEARCH_HIS | SEARCH_MSG, NULL, NULL)) {
+ if (!do_search(NULL, c, cmd, 1L, SEARCH_HIS | SEARCH_MSG, NULL)) {
curwin->w_cursor = pos;
cmd = NULL;
goto error;
@@ -3784,8 +3925,7 @@ static linenr_T get_address(exarg_T *eap,
pos.coladd = 0;
if (searchit(curwin, curbuf, &pos, NULL,
*cmd == '?' ? BACKWARD : FORWARD,
- (char_u *)"", 1L, SEARCH_MSG,
- i, (linenr_T)0, NULL, NULL) != FAIL) {
+ (char_u *)"", 1L, SEARCH_MSG, i, NULL) != FAIL) {
lnum = pos.lnum;
} else {
cmd = NULL;
@@ -4239,7 +4379,7 @@ int expand_filename(exarg_T *eap, char_u **cmdlinep, char_u **errormsgp)
if (has_wildcards) {
expand_T xpc;
- int options = WILD_LIST_NOTFOUND|WILD_ADD_SLASH;
+ int options = WILD_LIST_NOTFOUND | WILD_NOERROR | WILD_ADD_SLASH;
ExpandInit(&xpc);
xpc.xp_context = EXPAND_FILES;
@@ -4404,6 +4544,21 @@ skip_cmd_arg (
return p;
}
+int get_bad_opt(const char_u *p, exarg_T *eap)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (STRICMP(p, "keep") == 0) {
+ eap->bad_char = BAD_KEEP;
+ } else if (STRICMP(p, "drop") == 0) {
+ eap->bad_char = BAD_DROP;
+ } else if (MB_BYTE2LEN(*p) == 1 && p[1] == NUL) {
+ eap->bad_char = *p;
+ } else {
+ return FAIL;
+ }
+ return OK;
+}
+
/*
* Get "++opt=arg" argument.
* Return FAIL or OK.
@@ -4462,8 +4617,10 @@ static int getargopt(exarg_T *eap)
*arg = NUL;
if (pp == &eap->force_ff) {
- if (check_ff_value(eap->cmd + eap->force_ff) == FAIL)
+ if (check_ff_value(eap->cmd + eap->force_ff) == FAIL) {
return FAIL;
+ }
+ eap->force_ff = eap->cmd[eap->force_ff];
} else if (pp == &eap->force_enc) {
/* Make 'fileencoding' lower case. */
for (p = eap->cmd + eap->force_enc; *p != NUL; ++p)
@@ -4471,15 +4628,9 @@ static int getargopt(exarg_T *eap)
} else {
/* Check ++bad= argument. Must be a single-byte character, "keep" or
* "drop". */
- p = eap->cmd + bad_char_idx;
- if (STRICMP(p, "keep") == 0)
- eap->bad_char = BAD_KEEP;
- else if (STRICMP(p, "drop") == 0)
- eap->bad_char = BAD_DROP;
- else if (MB_BYTE2LEN(*p) == 1 && p[1] == NUL)
- eap->bad_char = *p;
- else
+ if (get_bad_opt(eap->cmd + bad_char_idx, eap) == FAIL) {
return FAIL;
+ }
}
return OK;
@@ -4513,6 +4664,8 @@ static int get_tabpage_arg(exarg_T *eap)
if (relative == 0) {
if (STRCMP(p, "$") == 0) {
tab_number = LAST_TAB_NR;
+ } else if (STRCMP(p, "#") == 0) {
+ tab_number = tabpage_index(lastused_tabpage);
} else if (p == p_save || *p_save == '-' || *p != NUL
|| tab_number > LAST_TAB_NR) {
// No numbers as argument.
@@ -4994,13 +5147,14 @@ static char *get_command_complete(int arg)
static void uc_list(char_u *name, size_t name_len)
{
int i, j;
- int found = FALSE;
+ bool found = false;
ucmd_T *cmd;
- int len;
uint32_t a;
- garray_T *gap;
- gap = &curbuf->b_ucmds;
+ // In cmdwin, the alternative buffer should be used.
+ garray_T *gap = (cmdwin_type != 0 && get_cmdline_type() == NUL)
+ ? &prevwin->w_buffer->b_ucmds
+ : &curbuf->b_ucmds;
for (;; ) {
for (i = 0; i < gap->ga_len; ++i) {
cmd = USER_CMD_GA(gap, i);
@@ -5013,62 +5167,96 @@ static void uc_list(char_u *name, size_t name_len)
continue;
}
- /* Put out the title first time */
- if (!found)
- MSG_PUTS_TITLE(_("\n Name Args Address Complete Definition"));
- found = TRUE;
+ // Put out the title first time
+ if (!found) {
+ MSG_PUTS_TITLE(_("\n Name Args Address "
+ "Complete Definition"));
+ }
+ found = true;
msg_putchar('\n');
if (got_int)
break;
- /* Special cases */
- msg_putchar(a & BANG ? '!' : ' ');
- msg_putchar(a & REGSTR ? '"' : ' ');
- msg_putchar(gap != &ucmds ? 'b' : ' ');
- msg_putchar(' ');
+ // Special cases
+ int len = 4;
+ if (a & BANG) {
+ msg_putchar('!');
+ len--;
+ }
+ if (a & REGSTR) {
+ msg_putchar('"');
+ len--;
+ }
+ if (gap != &ucmds) {
+ msg_putchar('b');
+ len--;
+ }
+ if (a & TRLBAR) {
+ msg_putchar('|');
+ len--;
+ }
+ while (len-- > 0) {
+ msg_putchar(' ');
+ }
msg_outtrans_attr(cmd->uc_name, HL_ATTR(HLF_D));
len = (int)STRLEN(cmd->uc_name) + 4;
do {
msg_putchar(' ');
- ++len;
- } while (len < 16);
+ len++;
+ } while (len < 22);
+ // "over" is how much longer the name is than the column width for
+ // the name, we'll try to align what comes after.
+ const int over = len - 22;
len = 0;
- /* Arguments */
+ // Arguments
switch (a & (EXTRA|NOSPC|NEEDARG)) {
- case 0: IObuff[len++] = '0'; break;
- case (EXTRA): IObuff[len++] = '*'; break;
- case (EXTRA|NOSPC): IObuff[len++] = '?'; break;
- case (EXTRA|NEEDARG): IObuff[len++] = '+'; break;
- case (EXTRA|NOSPC|NEEDARG): IObuff[len++] = '1'; break;
+ case 0:
+ IObuff[len++] = '0';
+ break;
+ case (EXTRA):
+ IObuff[len++] = '*';
+ break;
+ case (EXTRA|NOSPC):
+ IObuff[len++] = '?';
+ break;
+ case (EXTRA|NEEDARG):
+ IObuff[len++] = '+';
+ break;
+ case (EXTRA|NOSPC|NEEDARG):
+ IObuff[len++] = '1';
+ break;
}
do {
IObuff[len++] = ' ';
- } while (len < 5);
+ } while (len < 5 - over);
- /* Range */
+ // Address / Range
if (a & (RANGE|COUNT)) {
if (a & COUNT) {
- /* -count=N */
- sprintf((char *)IObuff + len, "%" PRId64 "c", (int64_t)cmd->uc_def);
+ // -count=N
+ snprintf((char *)IObuff + len, IOSIZE, "%" PRId64 "c",
+ (int64_t)cmd->uc_def);
len += (int)STRLEN(IObuff + len);
- } else if (a & DFLALL)
+ } else if (a & DFLALL) {
IObuff[len++] = '%';
- else if (cmd->uc_def >= 0) {
- /* -range=N */
- sprintf((char *)IObuff + len, "%" PRId64 "", (int64_t)cmd->uc_def);
+ } else if (cmd->uc_def >= 0) {
+ // -range=N
+ snprintf((char *)IObuff + len, IOSIZE, "%" PRId64 "",
+ (int64_t)cmd->uc_def);
len += (int)STRLEN(IObuff + len);
- } else
+ } else {
IObuff[len++] = '.';
+ }
}
do {
IObuff[len++] = ' ';
- } while (len < 11);
+ } while (len < 9 - over);
// Address Type
for (j = 0; addr_type_complete[j].expand != -1; j++) {
@@ -5082,7 +5270,7 @@ static void uc_list(char_u *name, size_t name_len)
do {
IObuff[len++] = ' ';
- } while (len < 21);
+ } while (len < 13 - over);
// Completion
char *cmd_compl = get_command_complete(cmd->uc_compl);
@@ -5093,12 +5281,13 @@ static void uc_list(char_u *name, size_t name_len)
do {
IObuff[len++] = ' ';
- } while (len < 35);
+ } while (len < 24 - over);
IObuff[len] = '\0';
msg_outtrans(IObuff);
- msg_outtrans_special(cmd->uc_rep, false);
+ msg_outtrans_special(cmd->uc_rep, false,
+ name_len == 0 ? Columns - 46 : 0);
if (p_verbose > 0) {
last_set_msg(cmd->uc_script_ctx);
}
@@ -5286,9 +5475,8 @@ static void ex_command(exarg_T *eap)
end = p;
name_len = (int)(end - name);
- /* If there is nothing after the name, and no attributes were specified,
- * we are listing commands
- */
+ // If there is nothing after the name, and no attributes were specified,
+ // we are listing commands
p = skipwhite(end);
if (!has_attr && ends_excmd(*p)) {
uc_list(name, end - name);
@@ -5849,13 +6037,21 @@ char_u *get_user_cmd_addr_type(expand_T *xp, int idx)
/*
* Function given to ExpandGeneric() to obtain the list of user command names.
*/
-char_u *get_user_commands(expand_T *xp, int idx)
+char_u *get_user_commands(expand_T *xp FUNC_ATTR_UNUSED, int idx)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
- if (idx < curbuf->b_ucmds.ga_len)
- return USER_CMD_GA(&curbuf->b_ucmds, idx)->uc_name;
- idx -= curbuf->b_ucmds.ga_len;
- if (idx < ucmds.ga_len)
+ // In cmdwin, the alternative buffer should be used.
+ const buf_T *const buf = (cmdwin_type != 0 && get_cmdline_type() == NUL)
+ ? prevwin->w_buffer
+ : curbuf;
+
+ if (idx < buf->b_ucmds.ga_len) {
+ return USER_CMD_GA(&buf->b_ucmds, idx)->uc_name;
+ }
+ idx -= buf->b_ucmds.ga_len;
+ if (idx < ucmds.ga_len) {
return USER_CMD(idx)->uc_name;
+ }
return NULL;
}
@@ -6066,9 +6262,11 @@ static bool before_quit_autocmds(win_T *wp, bool quit_all, int forceit)
if (quit_all
|| (check_more(false, forceit) == OK && only_one_window())) {
apply_autocmds(EVENT_EXITPRE, NULL, NULL, false, curbuf);
- // Refuse to quit when locked or when the buffer in the last window is
- // being closed (can only happen in autocommands).
- if (curbuf_locked()
+ // Refuse to quit when locked or when the window was closed or the
+ // buffer in the last window is being closed (can only happen in
+ // autocommands).
+ if (!win_valid(wp)
+ || curbuf_locked()
|| (curbuf->b_nwindows == 1 && curbuf->b_locked > 0)) {
return true;
}
@@ -6708,17 +6906,18 @@ static void ex_preserve(exarg_T *eap)
/// ":recover".
static void ex_recover(exarg_T *eap)
{
- /* Set recoverymode right away to avoid the ATTENTION prompt. */
- recoverymode = TRUE;
+ // Set recoverymode right away to avoid the ATTENTION prompt.
+ recoverymode = true;
if (!check_changed(curbuf, (p_awa ? CCGD_AW : 0)
| CCGD_MULTWIN
| (eap->forceit ? CCGD_FORCEIT : 0)
| CCGD_EXCMD)
&& (*eap->arg == NUL
- || setfname(curbuf, eap->arg, NULL, TRUE) == OK))
- ml_recover();
- recoverymode = FALSE;
+ || setfname(curbuf, eap->arg, NULL, true) == OK)) {
+ ml_recover(true);
+ }
+ recoverymode = false;
}
/*
@@ -6886,6 +7085,10 @@ static void ex_tabs(exarg_T *eap)
msg_start();
msg_scroll = TRUE;
+ win_T *lastused_win = valid_tabpage(lastused_tabpage)
+ ? lastused_tabpage->tp_curwin
+ : NULL;
+
FOR_ALL_TABS(tp) {
if (got_int) {
break;
@@ -6903,7 +7106,7 @@ static void ex_tabs(exarg_T *eap)
}
msg_putchar('\n');
- msg_putchar(wp == curwin ? '>' : ' ');
+ msg_putchar(wp == curwin ? '>' : wp == lastused_win ? '#' : ' ');
msg_putchar(' ');
msg_putchar(bufIsChanged(wp->w_buffer) ? '+' : ' ');
msg_putchar(' ');
@@ -7166,7 +7369,7 @@ static void ex_syncbind(exarg_T *eap)
topline = curwin->w_topline;
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
if (wp->w_p_scb && wp->w_buffer) {
- y = wp->w_buffer->b_ml.ml_line_count - p_so;
+ y = wp->w_buffer->b_ml.ml_line_count - get_scrolloff_value();
if (topline > y) {
topline = y;
}
@@ -7928,167 +8131,6 @@ static void close_redir(void)
}
}
-#ifdef USE_CRNL
-# define MKSESSION_NL
-static int mksession_nl = FALSE; /* use NL only in put_eol() */
-#endif
-
-/*
- * ":mkexrc", ":mkvimrc", ":mkview" and ":mksession".
- */
-static void ex_mkrc(exarg_T *eap)
-{
- FILE *fd;
- int failed = false;
- int view_session = false;
- int using_vdir = false; // using 'viewdir'?
- char *viewFile = NULL;
- unsigned *flagp;
-
- if (eap->cmdidx == CMD_mksession || eap->cmdidx == CMD_mkview) {
- view_session = TRUE;
- }
-
- /* Use the short file name until ":lcd" is used. We also don't use the
- * short file name when 'acd' is set, that is checked later. */
- did_lcd = FALSE;
-
- char *fname;
- // ":mkview" or ":mkview 9": generate file name with 'viewdir'
- if (eap->cmdidx == CMD_mkview
- && (*eap->arg == NUL
- || (ascii_isdigit(*eap->arg) && eap->arg[1] == NUL))) {
- eap->forceit = true;
- fname = get_view_file(*eap->arg);
- if (fname == NULL) {
- return;
- }
- viewFile = fname;
- using_vdir = true;
- } else if (*eap->arg != NUL) {
- fname = (char *) eap->arg;
- } else if (eap->cmdidx == CMD_mkvimrc) {
- fname = VIMRC_FILE;
- } else if (eap->cmdidx == CMD_mksession) {
- fname = SESSION_FILE;
- } else {
- fname = EXRC_FILE;
- }
-
- /* When using 'viewdir' may have to create the directory. */
- if (using_vdir && !os_isdir(p_vdir)) {
- vim_mkdir_emsg((const char *)p_vdir, 0755);
- }
-
- fd = open_exfile((char_u *) fname, eap->forceit, WRITEBIN);
- if (fd != NULL) {
- if (eap->cmdidx == CMD_mkview)
- flagp = &vop_flags;
- else
- flagp = &ssop_flags;
-
-#ifdef MKSESSION_NL
- /* "unix" in 'sessionoptions': use NL line separator */
- if (view_session && (*flagp & SSOP_UNIX))
- mksession_nl = TRUE;
-#endif
-
- /* Write the version command for :mkvimrc */
- if (eap->cmdidx == CMD_mkvimrc)
- (void)put_line(fd, "version 6.0");
-
- if (eap->cmdidx == CMD_mksession) {
- if (put_line(fd, "let SessionLoad = 1") == FAIL)
- failed = TRUE;
- }
-
- if (!view_session
- || (eap->cmdidx == CMD_mksession
- && (*flagp & SSOP_OPTIONS)))
- failed |= (makemap(fd, NULL) == FAIL
- || makeset(fd, OPT_GLOBAL, FALSE) == FAIL);
-
- if (!failed && view_session) {
- if (put_line(fd,
- "let s:so_save = &so | let s:siso_save = &siso | set so=0 siso=0")
- == FAIL)
- failed = TRUE;
- if (eap->cmdidx == CMD_mksession) {
- char_u *dirnow; /* current directory */
-
- dirnow = xmalloc(MAXPATHL);
- /*
- * Change to session file's dir.
- */
- if (os_dirname(dirnow, MAXPATHL) == FAIL
- || os_chdir((char *)dirnow) != 0)
- *dirnow = NUL;
- if (*dirnow != NUL && (ssop_flags & SSOP_SESDIR)) {
- if (vim_chdirfile((char_u *) fname) == OK) {
- shorten_fnames(true);
- }
- } else if (*dirnow != NUL
- && (ssop_flags & SSOP_CURDIR) && globaldir != NULL) {
- if (os_chdir((char *)globaldir) == 0)
- shorten_fnames(TRUE);
- }
-
- failed |= (makeopens(fd, dirnow) == FAIL);
-
- /* restore original dir */
- if (*dirnow != NUL && ((ssop_flags & SSOP_SESDIR)
- || ((ssop_flags & SSOP_CURDIR) && globaldir !=
- NULL))) {
- if (os_chdir((char *)dirnow) != 0)
- EMSG(_(e_prev_dir));
- shorten_fnames(TRUE);
- /* restore original dir */
- if (*dirnow != NUL && ((ssop_flags & SSOP_SESDIR)
- || ((ssop_flags & SSOP_CURDIR) && globaldir !=
- NULL))) {
- if (os_chdir((char *)dirnow) != 0)
- EMSG(_(e_prev_dir));
- shorten_fnames(TRUE);
- }
- }
- xfree(dirnow);
- } else {
- failed |= (put_view(fd, curwin, !using_vdir, flagp,
- -1) == FAIL);
- }
- if (put_line(fd, "let &so = s:so_save | let &siso = s:siso_save")
- == FAIL)
- failed = TRUE;
- if (put_line(fd, "doautoall SessionLoadPost") == FAIL)
- failed = TRUE;
- if (eap->cmdidx == CMD_mksession) {
- if (put_line(fd, "unlet SessionLoad") == FAIL)
- failed = TRUE;
- }
- }
- if (put_line(fd, "\" vim: set ft=vim :") == FAIL)
- failed = TRUE;
-
- failed |= fclose(fd);
-
- if (failed) {
- EMSG(_(e_write));
- } else if (eap->cmdidx == CMD_mksession) {
- // successful session write - set v:this_session
- char *const tbuf = xmalloc(MAXPATHL);
- if (vim_FullName(fname, tbuf, MAXPATHL, false) == OK) {
- set_vim_var_string(VV_THIS_SESSION, tbuf, -1);
- }
- xfree(tbuf);
- }
-#ifdef MKSESSION_NL
- mksession_nl = FALSE;
-#endif
- }
-
- xfree(viewFile);
-}
-
/// Try creating a directory, give error message on failure
///
/// @param[in] name Directory to create.
@@ -8171,6 +8213,57 @@ void update_topline_cursor(void)
update_curswant();
}
+// Save the current State and go to Normal mode.
+// Return true if the typeahead could be saved.
+bool save_current_state(save_state_T *sst)
+ FUNC_ATTR_NONNULL_ALL
+{
+ sst->save_msg_scroll = msg_scroll;
+ sst->save_restart_edit = restart_edit;
+ sst->save_msg_didout = msg_didout;
+ sst->save_State = State;
+ sst->save_insertmode = p_im;
+ sst->save_finish_op = finish_op;
+ sst->save_opcount = opcount;
+ sst->save_reg_executing = reg_executing;
+
+ msg_scroll = false; // no msg scrolling in Normal mode
+ restart_edit = 0; // don't go to Insert mode
+ p_im = false; // don't use 'insertmode
+
+ // Save the current typeahead. This is required to allow using ":normal"
+ // from an event handler and makes sure we don't hang when the argument
+ // ends with half a command.
+ save_typeahead(&sst->tabuf);
+ return sst->tabuf.typebuf_valid;
+}
+
+void restore_current_state(save_state_T *sst)
+ FUNC_ATTR_NONNULL_ALL
+{
+ // Restore the previous typeahead.
+ restore_typeahead(&sst->tabuf);
+
+ msg_scroll = sst->save_msg_scroll;
+ if (force_restart_edit) {
+ force_restart_edit = false;
+ } else {
+ // Some function (terminal_enter()) was aware of ex_normal and decided to
+ // override the value of restart_edit anyway.
+ restart_edit = sst->save_restart_edit;
+ }
+ p_im = sst->save_insertmode;
+ finish_op = sst->save_finish_op;
+ opcount = sst->save_opcount;
+ reg_executing = sst->save_reg_executing;
+ msg_didout |= sst->save_msg_didout; // don't reset msg_didout now
+
+ // Restore the state (needed when called from a function executed for
+ // 'indentexpr'). Update the mouse and cursor, they may have changed.
+ State = sst->save_State;
+ ui_cursor_shape(); // may show different cursor shape
+}
+
/*
* ":normal[!] {commands}": Execute normal mode commands.
*/
@@ -8180,14 +8273,7 @@ static void ex_normal(exarg_T *eap)
EMSG("Can't re-enter normal mode from terminal mode");
return;
}
- int save_msg_scroll = msg_scroll;
- int save_restart_edit = restart_edit;
- int save_msg_didout = msg_didout;
- int save_State = State;
- tasave_T tabuf;
- int save_insertmode = p_im;
- int save_finish_op = finish_op;
- long save_opcount = opcount;
+ save_state_T save_state;
char_u *arg = NULL;
int l;
char_u *p;
@@ -8200,11 +8286,6 @@ static void ex_normal(exarg_T *eap)
EMSG(_("E192: Recursive use of :normal too deep"));
return;
}
- ++ex_normal_busy;
-
- msg_scroll = FALSE; /* no msg scrolling in Normal mode */
- restart_edit = 0; /* don't go to Insert mode */
- p_im = FALSE; /* don't use 'insertmode' */
/*
* vgetc() expects a CSI and K_SPECIAL to have been escaped. Don't do
@@ -8238,19 +8319,11 @@ static void ex_normal(exarg_T *eap)
}
}
- /*
- * Save the current typeahead. This is required to allow using ":normal"
- * from an event handler and makes sure we don't hang when the argument
- * ends with half a command.
- */
- save_typeahead(&tabuf);
- // TODO(philix): after save_typeahead() this is always TRUE
- if (tabuf.typebuf_valid) {
- /*
- * Repeat the :normal command for each line in the range. When no
- * range given, execute it just once, without positioning the cursor
- * first.
- */
+ ex_normal_busy++;
+ if (save_current_state(&save_state)) {
+ // Repeat the :normal command for each line in the range. When no
+ // range given, execute it just once, without positioning the cursor
+ // first.
do {
if (eap->addr_count != 0) {
curwin->w_cursor.lnum = eap->line1++;
@@ -8267,28 +8340,12 @@ static void ex_normal(exarg_T *eap)
/* Might not return to the main loop when in an event handler. */
update_topline_cursor();
- /* Restore the previous typeahead. */
- restore_typeahead(&tabuf);
+ restore_current_state(&save_state);
- --ex_normal_busy;
- msg_scroll = save_msg_scroll;
- if (force_restart_edit) {
- force_restart_edit = false;
- } else {
- // Some function (terminal_enter()) was aware of ex_normal and decided to
- // override the value of restart_edit anyway.
- restart_edit = save_restart_edit;
- }
- p_im = save_insertmode;
- finish_op = save_finish_op;
- opcount = save_opcount;
- msg_didout |= save_msg_didout; /* don't reset msg_didout now */
+ ex_normal_busy--;
- /* Restore the state (needed when called from a function executed for
- * 'indentexpr'). Update the mouse and cursor, they may have changed. */
- State = save_State;
setmouse();
- ui_cursor_shape(); /* may show different cursor shape */
+ ui_cursor_shape(); // may show different cursor shape
xfree(arg);
}
@@ -8959,864 +9016,6 @@ char_u *expand_sfile(char_u *arg)
return result;
}
-
-/*
- * Write openfile commands for the current buffers to an .exrc file.
- * Return FAIL on error, OK otherwise.
- */
-static int
-makeopens(
- FILE *fd,
- char_u *dirnow /* Current directory name */
-)
-{
- int only_save_windows = TRUE;
- int nr;
- int restore_size = true;
- win_T *wp;
- char_u *sname;
- win_T *edited_win = NULL;
- int tabnr;
- win_T *tab_firstwin;
- frame_T *tab_topframe;
- int cur_arg_idx = 0;
- int next_arg_idx = 0;
-
- if (ssop_flags & SSOP_BUFFERS)
- only_save_windows = FALSE; /* Save ALL buffers */
-
- // Begin by setting v:this_session, and then other sessionable variables.
- if (put_line(fd, "let v:this_session=expand(\"<sfile>:p\")") == FAIL) {
- return FAIL;
- }
- if (ssop_flags & SSOP_GLOBALS) {
- if (store_session_globals(fd) == FAIL) {
- return FAIL;
- }
- }
-
- /*
- * Close all windows but one.
- */
- if (put_line(fd, "silent only") == FAIL)
- return FAIL;
-
- /*
- * Now a :cd command to the session directory or the current directory
- */
- if (ssop_flags & SSOP_SESDIR) {
- if (put_line(fd, "exe \"cd \" . escape(expand(\"<sfile>:p:h\"), ' ')")
- == FAIL)
- return FAIL;
- } else if (ssop_flags & SSOP_CURDIR) {
- sname = home_replace_save(NULL, globaldir != NULL ? globaldir : dirnow);
- if (fputs("cd ", fd) < 0
- || ses_put_fname(fd, sname, &ssop_flags) == FAIL
- || put_eol(fd) == FAIL) {
- xfree(sname);
- return FAIL;
- }
- xfree(sname);
- }
-
- /*
- * If there is an empty, unnamed buffer we will wipe it out later.
- * Remember the buffer number.
- */
- if (put_line(fd,
- "if expand('%') == '' && !&modified && line('$') <= 1 && getline(1) == ''")
- ==
- FAIL)
- return FAIL;
- if (put_line(fd, " let s:wipebuf = bufnr('%')") == FAIL)
- return FAIL;
- if (put_line(fd, "endif") == FAIL)
- return FAIL;
-
- /*
- * Now save the current files, current buffer first.
- */
- if (put_line(fd, "set shortmess=aoO") == FAIL)
- return FAIL;
-
- /* Now put the other buffers into the buffer list */
- FOR_ALL_BUFFERS(buf) {
- if (!(only_save_windows && buf->b_nwindows == 0)
- && !(buf->b_help && !(ssop_flags & SSOP_HELP))
- && buf->b_fname != NULL
- && buf->b_p_bl) {
- if (fprintf(fd, "badd +%" PRId64 " ",
- buf->b_wininfo == NULL
- ? (int64_t)1L
- : (int64_t)buf->b_wininfo->wi_fpos.lnum) < 0
- || ses_fname(fd, buf, &ssop_flags, true) == FAIL) {
- return FAIL;
- }
- }
- }
-
- /* the global argument list */
- if (ses_arglist(fd, "argglobal", &global_alist.al_ga,
- !(ssop_flags & SSOP_CURDIR), &ssop_flags) == FAIL) {
- return FAIL;
- }
-
- if (ssop_flags & SSOP_RESIZE) {
- /* Note: after the restore we still check it worked!*/
- if (fprintf(fd, "set lines=%" PRId64 " columns=%" PRId64,
- (int64_t)Rows, (int64_t)Columns) < 0
- || put_eol(fd) == FAIL)
- return FAIL;
- }
-
- int restore_stal = FALSE;
- // When there are two or more tabpages and 'showtabline' is 1 the tabline
- // will be displayed when creating the next tab. That resizes the windows
- // in the first tab, which may cause problems. Set 'showtabline' to 2
- // temporarily to avoid that.
- if (p_stal == 1 && first_tabpage->tp_next != NULL) {
- if (put_line(fd, "set stal=2") == FAIL) {
- return FAIL;
- }
- restore_stal = TRUE;
- }
-
- /*
- * May repeat putting Windows for each tab, when "tabpages" is in
- * 'sessionoptions'.
- * Don't use goto_tabpage(), it may change directory and trigger
- * autocommands.
- */
- tab_firstwin = firstwin; /* first window in tab page "tabnr" */
- tab_topframe = topframe;
- for (tabnr = 1;; tabnr++) {
- tabpage_T *tp = find_tabpage(tabnr);
- if (tp == NULL) {
- break; // done all tab pages
- }
-
- int need_tabnew = false;
- int cnr = 1;
-
- if ((ssop_flags & SSOP_TABPAGES)) {
- if (tp == curtab) {
- tab_firstwin = firstwin;
- tab_topframe = topframe;
- } else {
- tab_firstwin = tp->tp_firstwin;
- tab_topframe = tp->tp_topframe;
- }
- if (tabnr > 1)
- need_tabnew = TRUE;
- }
-
- /*
- * Before creating the window layout, try loading one file. If this
- * is aborted we don't end up with a number of useless windows.
- * This may have side effects! (e.g., compressed or network file).
- */
- for (wp = tab_firstwin; wp != NULL; wp = wp->w_next) {
- if (ses_do_win(wp)
- && wp->w_buffer->b_ffname != NULL
- && !bt_help(wp->w_buffer)
- && !bt_nofile(wp->w_buffer)
- ) {
- if (fputs(need_tabnew ? "tabedit " : "edit ", fd) < 0
- || ses_fname(fd, wp->w_buffer, &ssop_flags, true) == FAIL) {
- return FAIL;
- }
- need_tabnew = false;
- if (!wp->w_arg_idx_invalid) {
- edited_win = wp;
- }
- break;
- }
- }
-
- /* If no file got edited create an empty tab page. */
- if (need_tabnew && put_line(fd, "tabnew") == FAIL)
- return FAIL;
-
- /*
- * Save current window layout.
- */
- if (put_line(fd, "set splitbelow splitright") == FAIL)
- return FAIL;
- if (ses_win_rec(fd, tab_topframe) == FAIL)
- return FAIL;
- if (!p_sb && put_line(fd, "set nosplitbelow") == FAIL)
- return FAIL;
- if (!p_spr && put_line(fd, "set nosplitright") == FAIL)
- return FAIL;
-
- /*
- * Check if window sizes can be restored (no windows omitted).
- * Remember the window number of the current window after restoring.
- */
- nr = 0;
- for (wp = tab_firstwin; wp != NULL; wp = wp->w_next) {
- if (ses_do_win(wp))
- ++nr;
- else
- restore_size = FALSE;
- if (curwin == wp)
- cnr = nr;
- }
-
- /* Go to the first window. */
- if (put_line(fd, "wincmd t") == FAIL)
- return FAIL;
-
- // If more than one window, see if sizes can be restored.
- // First set 'winheight' and 'winwidth' to 1 to avoid the windows being
- // resized when moving between windows.
- // Do this before restoring the view, so that the topline and the
- // cursor can be set. This is done again below.
- // winminheight and winminwidth need to be set to avoid an error if the
- // user has set winheight or winwidth.
- if (put_line(fd, "set winminheight=0") == FAIL
- || put_line(fd, "set winheight=1") == FAIL
- || put_line(fd, "set winminwidth=0") == FAIL
- || put_line(fd, "set winwidth=1") == FAIL) {
- return FAIL;
- }
- if (nr > 1 && ses_winsizes(fd, restore_size, tab_firstwin) == FAIL) {
- return FAIL;
- }
-
- /*
- * Restore the view of the window (options, file, cursor, etc.).
- */
- for (wp = tab_firstwin; wp != NULL; wp = wp->w_next) {
- if (!ses_do_win(wp))
- continue;
- if (put_view(fd, wp, wp != edited_win, &ssop_flags,
- cur_arg_idx) == FAIL)
- return FAIL;
- if (nr > 1 && put_line(fd, "wincmd w") == FAIL)
- return FAIL;
- next_arg_idx = wp->w_arg_idx;
- }
-
- /* The argument index in the first tab page is zero, need to set it in
- * each window. For further tab pages it's the window where we do
- * "tabedit". */
- cur_arg_idx = next_arg_idx;
-
- /*
- * Restore cursor to the current window if it's not the first one.
- */
- if (cnr > 1 && (fprintf(fd, "%dwincmd w", cnr) < 0
- || put_eol(fd) == FAIL))
- return FAIL;
-
- /*
- * Restore window sizes again after jumping around in windows, because
- * the current window has a minimum size while others may not.
- */
- if (nr > 1 && ses_winsizes(fd, restore_size, tab_firstwin) == FAIL)
- return FAIL;
-
- // Take care of tab-local working directories if applicable
- if (tp->tp_localdir) {
- if (fputs("if exists(':tcd') == 2 | tcd ", fd) < 0
- || ses_put_fname(fd, tp->tp_localdir, &ssop_flags) == FAIL
- || fputs(" | endif", fd) < 0
- || put_eol(fd) == FAIL) {
- return FAIL;
- }
- did_lcd = true;
- }
-
- /* Don't continue in another tab page when doing only the current one
- * or when at the last tab page. */
- if (!(ssop_flags & SSOP_TABPAGES))
- break;
- }
-
- if (ssop_flags & SSOP_TABPAGES) {
- if (fprintf(fd, "tabnext %d", tabpage_index(curtab)) < 0
- || put_eol(fd) == FAIL)
- return FAIL;
- }
- if (restore_stal && put_line(fd, "set stal=1") == FAIL) {
- return FAIL;
- }
-
- /*
- * Wipe out an empty unnamed buffer we started in.
- */
- if (put_line(fd, "if exists('s:wipebuf') "
- "&& getbufvar(s:wipebuf, '&buftype') isnot# 'terminal'")
- == FAIL)
- return FAIL;
- if (put_line(fd, " silent exe 'bwipe ' . s:wipebuf") == FAIL)
- return FAIL;
- if (put_line(fd, "endif") == FAIL)
- return FAIL;
- if (put_line(fd, "unlet! s:wipebuf") == FAIL)
- return FAIL;
-
- // Re-apply options.
- if (fprintf(fd, "set winheight=%" PRId64 " winwidth=%" PRId64
- " winminheight=%" PRId64 " winminwidth=%" PRId64
- " shortmess=%s",
- (int64_t)p_wh,
- (int64_t)p_wiw,
- (int64_t)p_wmh,
- (int64_t)p_wmw,
- p_shm) < 0
- || put_eol(fd) == FAIL) {
- return FAIL;
- }
-
- /*
- * Lastly, execute the x.vim file if it exists.
- */
- if (put_line(fd, "let s:sx = expand(\"<sfile>:p:r\").\"x.vim\"") == FAIL
- || put_line(fd, "if file_readable(s:sx)") == FAIL
- || put_line(fd, " exe \"source \" . fnameescape(s:sx)") == FAIL
- || put_line(fd, "endif") == FAIL)
- return FAIL;
-
- return OK;
-}
-
-static int ses_winsizes(FILE *fd, int restore_size, win_T *tab_firstwin)
-{
- int n = 0;
- win_T *wp;
-
- if (restore_size && (ssop_flags & SSOP_WINSIZE)) {
- for (wp = tab_firstwin; wp != NULL; wp = wp->w_next) {
- if (!ses_do_win(wp)) {
- continue;
- }
- n++;
-
- // restore height when not full height
- if (wp->w_height + wp->w_status_height < topframe->fr_height
- && (fprintf(fd,
- "exe '%dresize ' . ((&lines * %" PRId64
- " + %" PRId64 ") / %" PRId64 ")",
- n, (int64_t)wp->w_height,
- (int64_t)Rows / 2, (int64_t)Rows) < 0
- || put_eol(fd) == FAIL)) {
- return FAIL;
- }
-
- // restore width when not full width
- if (wp->w_width < Columns
- && (fprintf(fd,
- "exe 'vert %dresize ' . ((&columns * %" PRId64
- " + %" PRId64 ") / %" PRId64 ")",
- n, (int64_t)wp->w_width, (int64_t)Columns / 2,
- (int64_t)Columns) < 0
- || put_eol(fd) == FAIL)) {
- return FAIL;
- }
- }
- } else {
- // Just equalise window sizes
- if (put_line(fd, "wincmd =") == FAIL) {
- return FAIL;
- }
- }
- return OK;
-}
-
-/*
- * Write commands to "fd" to recursively create windows for frame "fr",
- * horizontally and vertically split.
- * After the commands the last window in the frame is the current window.
- * Returns FAIL when writing the commands to "fd" fails.
- */
-static int ses_win_rec(FILE *fd, frame_T *fr)
-{
- frame_T *frc;
- int count = 0;
-
- if (fr->fr_layout != FR_LEAF) {
- /* Find first frame that's not skipped and then create a window for
- * each following one (first frame is already there). */
- frc = ses_skipframe(fr->fr_child);
- if (frc != NULL)
- while ((frc = ses_skipframe(frc->fr_next)) != NULL) {
- /* Make window as big as possible so that we have lots of room
- * to split. */
- if (put_line(fd, "wincmd _ | wincmd |") == FAIL
- || put_line(fd, fr->fr_layout == FR_COL
- ? "split" : "vsplit") == FAIL)
- return FAIL;
- ++count;
- }
-
- /* Go back to the first window. */
- if (count > 0 && (fprintf(fd, fr->fr_layout == FR_COL
- ? "%dwincmd k" : "%dwincmd h", count) < 0
- || put_eol(fd) == FAIL))
- return FAIL;
-
- /* Recursively create frames/windows in each window of this column or
- * row. */
- frc = ses_skipframe(fr->fr_child);
- while (frc != NULL) {
- ses_win_rec(fd, frc);
- frc = ses_skipframe(frc->fr_next);
- /* Go to next window. */
- if (frc != NULL && put_line(fd, "wincmd w") == FAIL)
- return FAIL;
- }
- }
- return OK;
-}
-
-/*
- * Skip frames that don't contain windows we want to save in the Session.
- * Returns NULL when there none.
- */
-static frame_T *ses_skipframe(frame_T *fr)
-{
- frame_T *frc;
-
- FOR_ALL_FRAMES(frc, fr) {
- if (ses_do_frame(frc)) {
- break;
- }
- }
- return frc;
-}
-
-// Return true if frame "fr" has a window somewhere that we want to save in
-// the Session.
-static bool ses_do_frame(const frame_T *fr)
- FUNC_ATTR_NONNULL_ARG(1)
-{
- const frame_T *frc;
-
- if (fr->fr_layout == FR_LEAF) {
- return ses_do_win(fr->fr_win);
- }
- FOR_ALL_FRAMES(frc, fr->fr_child) {
- if (ses_do_frame(frc)) {
- return true;
- }
- }
- return false;
-}
-
-/// Return non-zero if window "wp" is to be stored in the Session.
-static int ses_do_win(win_T *wp)
-{
- if (wp->w_buffer->b_fname == NULL
- // When 'buftype' is "nofile" can't restore the window contents.
- || (!wp->w_buffer->terminal && bt_nofile(wp->w_buffer))) {
- return ssop_flags & SSOP_BLANK;
- }
- if (bt_help(wp->w_buffer)) {
- return ssop_flags & SSOP_HELP;
- }
- return true;
-}
-
-static int put_view_curpos(FILE *fd, const win_T *wp, char *spaces)
-{
- int r;
-
- if (wp->w_curswant == MAXCOL) {
- r = fprintf(fd, "%snormal! $", spaces);
- } else {
- r = fprintf(fd, "%snormal! 0%d|", spaces, wp->w_virtcol + 1);
- }
- return r < 0 || put_eol(fd) == FAIL ? FAIL : OK;
-}
-
-/*
- * Write commands to "fd" to restore the view of a window.
- * Caller must make sure 'scrolloff' is zero.
- */
-static int
-put_view(
- FILE *fd,
- win_T *wp,
- int add_edit, /* add ":edit" command to view */
- unsigned *flagp, /* vop_flags or ssop_flags */
- int current_arg_idx /* current argument index of the window, use
- * -1 if unknown */
-)
-{
- win_T *save_curwin;
- int f;
- int do_cursor;
- int did_next = FALSE;
-
- /* Always restore cursor position for ":mksession". For ":mkview" only
- * when 'viewoptions' contains "cursor". */
- do_cursor = (flagp == &ssop_flags || *flagp & SSOP_CURSOR);
-
- /*
- * Local argument list.
- */
- if (wp->w_alist == &global_alist) {
- if (put_line(fd, "argglobal") == FAIL)
- return FAIL;
- } else {
- if (ses_arglist(fd, "arglocal", &wp->w_alist->al_ga,
- flagp == &vop_flags
- || !(*flagp & SSOP_CURDIR)
- || wp->w_localdir != NULL, flagp) == FAIL)
- return FAIL;
- }
-
- /* Only when part of a session: restore the argument index. Some
- * arguments may have been deleted, check if the index is valid. */
- if (wp->w_arg_idx != current_arg_idx && wp->w_arg_idx < WARGCOUNT(wp)
- && flagp == &ssop_flags) {
- if (fprintf(fd, "%" PRId64 "argu", (int64_t)wp->w_arg_idx + 1) < 0
- || put_eol(fd) == FAIL) {
- return FAIL;
- }
- did_next = true;
- }
-
- /* Edit the file. Skip this when ":next" already did it. */
- if (add_edit && (!did_next || wp->w_arg_idx_invalid)) {
- /*
- * Load the file.
- */
- if (wp->w_buffer->b_ffname != NULL
- && (!bt_nofile(wp->w_buffer) || wp->w_buffer->terminal)
- ) {
- // Editing a file in this buffer: use ":edit file".
- // This may have side effects! (e.g., compressed or network file).
- //
- // Note, if a buffer for that file already exists, use :badd to
- // edit that buffer, to not lose folding information (:edit resets
- // folds in other buffers)
- if (fputs("if bufexists(\"", fd) < 0
- || ses_fname(fd, wp->w_buffer, flagp, false) == FAIL
- || fputs("\") | buffer ", fd) < 0
- || ses_fname(fd, wp->w_buffer, flagp, false) == FAIL
- || fputs(" | else | edit ", fd) < 0
- || ses_fname(fd, wp->w_buffer, flagp, false) == FAIL
- || fputs(" | endif", fd) < 0
- || put_eol(fd) == FAIL) {
- return FAIL;
- }
- } else {
- // No file in this buffer, just make it empty.
- if (put_line(fd, "enew") == FAIL) {
- return FAIL;
- }
- if (wp->w_buffer->b_ffname != NULL) {
- // The buffer does have a name, but it's not a file name.
- if (fputs("file ", fd) < 0
- || ses_fname(fd, wp->w_buffer, flagp, true) == FAIL) {
- return FAIL;
- }
- }
- do_cursor = false;
- }
- }
-
- /*
- * Local mappings and abbreviations.
- */
- if ((*flagp & (SSOP_OPTIONS | SSOP_LOCALOPTIONS))
- && makemap(fd, wp->w_buffer) == FAIL)
- return FAIL;
-
- /*
- * Local options. Need to go to the window temporarily.
- * Store only local values when using ":mkview" and when ":mksession" is
- * used and 'sessionoptions' doesn't include "nvim/options".
- * Some folding options are always stored when "folds" is included,
- * otherwise the folds would not be restored correctly.
- */
- save_curwin = curwin;
- curwin = wp;
- curbuf = curwin->w_buffer;
- if (*flagp & (SSOP_OPTIONS | SSOP_LOCALOPTIONS))
- f = makeset(fd, OPT_LOCAL,
- flagp == &vop_flags || !(*flagp & SSOP_OPTIONS));
- else if (*flagp & SSOP_FOLDS)
- f = makefoldset(fd);
- else
- f = OK;
- curwin = save_curwin;
- curbuf = curwin->w_buffer;
- if (f == FAIL)
- return FAIL;
-
- /*
- * Save Folds when 'buftype' is empty and for help files.
- */
- if ((*flagp & SSOP_FOLDS)
- && wp->w_buffer->b_ffname != NULL
- && (*wp->w_buffer->b_p_bt == NUL || bt_help(wp->w_buffer))
- ) {
- if (put_folds(fd, wp) == FAIL)
- return FAIL;
- }
-
- /*
- * Set the cursor after creating folds, since that moves the cursor.
- */
- if (do_cursor) {
-
- /* Restore the cursor line in the file and relatively in the
- * window. Don't use "G", it changes the jumplist. */
- if (fprintf(fd,
- "let s:l = %" PRId64 " - ((%" PRId64
- " * winheight(0) + %" PRId64 ") / %" PRId64 ")",
- (int64_t)wp->w_cursor.lnum,
- (int64_t)(wp->w_cursor.lnum - wp->w_topline),
- (int64_t)(wp->w_height_inner / 2),
- (int64_t)wp->w_height_inner) < 0
- || put_eol(fd) == FAIL
- || put_line(fd, "if s:l < 1 | let s:l = 1 | endif") == FAIL
- || put_line(fd, "exe s:l") == FAIL
- || put_line(fd, "normal! zt") == FAIL
- || fprintf(fd, "%" PRId64, (int64_t)wp->w_cursor.lnum) < 0
- || put_eol(fd) == FAIL)
- return FAIL;
- /* Restore the cursor column and left offset when not wrapping. */
- if (wp->w_cursor.col == 0) {
- if (put_line(fd, "normal! 0") == FAIL)
- return FAIL;
- } else {
- if (!wp->w_p_wrap && wp->w_leftcol > 0 && wp->w_width > 0) {
- if (fprintf(fd,
- "let s:c = %" PRId64 " - ((%" PRId64
- " * winwidth(0) + %" PRId64 ") / %" PRId64 ")",
- (int64_t)wp->w_virtcol + 1,
- (int64_t)(wp->w_virtcol - wp->w_leftcol),
- (int64_t)(wp->w_width / 2),
- (int64_t)wp->w_width) < 0
- || put_eol(fd) == FAIL
- || put_line(fd, "if s:c > 0") == FAIL
- || fprintf(fd, " exe 'normal! ' . s:c . '|zs' . %" PRId64 " . '|'",
- (int64_t)wp->w_virtcol + 1) < 0
- || put_eol(fd) == FAIL
- || put_line(fd, "else") == FAIL
- || put_view_curpos(fd, wp, " ") == FAIL
- || put_line(fd, "endif") == FAIL) {
- return FAIL;
- }
- } else if (put_view_curpos(fd, wp, "") == FAIL) {
- return FAIL;
- }
- }
- }
-
- //
- // Local directory, if the current flag is not view options or the "curdir"
- // option is included.
- //
- if (wp->w_localdir != NULL
- && (flagp != &vop_flags || (*flagp & SSOP_CURDIR))) {
- if (fputs("lcd ", fd) < 0
- || ses_put_fname(fd, wp->w_localdir, flagp) == FAIL
- || put_eol(fd) == FAIL) {
- return FAIL;
- }
- did_lcd = true;
- }
-
- return OK;
-}
-
-/*
- * Write an argument list to the session file.
- * Returns FAIL if writing fails.
- */
-static int
-ses_arglist(
- FILE *fd,
- char *cmd,
- garray_T *gap,
- int fullname, /* TRUE: use full path name */
- unsigned *flagp
-)
-{
- char_u *buf = NULL;
- char_u *s;
-
- if (fputs(cmd, fd) < 0 || put_eol(fd) == FAIL) {
- return FAIL;
- }
- if (put_line(fd, "%argdel") == FAIL) {
- return FAIL;
- }
- for (int i = 0; i < gap->ga_len; ++i) {
- /* NULL file names are skipped (only happens when out of memory). */
- s = alist_name(&((aentry_T *)gap->ga_data)[i]);
- if (s != NULL) {
- if (fullname) {
- buf = xmalloc(MAXPATHL);
- (void)vim_FullName((char *)s, (char *)buf, MAXPATHL, FALSE);
- s = buf;
- }
- if (fputs("$argadd ", fd) < 0 || ses_put_fname(fd, s, flagp) == FAIL
- || put_eol(fd) == FAIL) {
- xfree(buf);
- return FAIL;
- }
- xfree(buf);
- }
- }
- return OK;
-}
-
-/// Write a buffer name to the session file.
-/// Also ends the line, if "add_eol" is true.
-/// Returns FAIL if writing fails.
-static int ses_fname(FILE *fd, buf_T *buf, unsigned *flagp, bool add_eol)
-{
- char_u *name;
-
- /* Use the short file name if the current directory is known at the time
- * the session file will be sourced.
- * Don't do this for ":mkview", we don't know the current directory.
- * Don't do this after ":lcd", we don't keep track of what the current
- * directory is. */
- if (buf->b_sfname != NULL
- && flagp == &ssop_flags
- && (ssop_flags & (SSOP_CURDIR | SSOP_SESDIR))
- && !p_acd
- && !did_lcd)
- name = buf->b_sfname;
- else
- name = buf->b_ffname;
- if (ses_put_fname(fd, name, flagp) == FAIL
- || (add_eol && put_eol(fd) == FAIL)) {
- return FAIL;
- }
- return OK;
-}
-
-/*
- * Write a file name to the session file.
- * Takes care of the "slash" option in 'sessionoptions' and escapes special
- * characters.
- * Returns FAIL if writing fails.
- */
-static int ses_put_fname(FILE *fd, char_u *name, unsigned *flagp)
-{
- char_u *p;
-
- char_u *sname = home_replace_save(NULL, name);
-
- if (*flagp & SSOP_SLASH) {
- // change all backslashes to forward slashes
- for (p = sname; *p != NUL; MB_PTR_ADV(p)) {
- if (*p == '\\') {
- *p = '/';
- }
- }
- }
-
- // Escape special characters.
- p = (char_u *)vim_strsave_fnameescape((const char *)sname, false);
- xfree(sname);
-
- /* write the result */
- bool retval = fputs((char *)p, fd) < 0 ? FAIL : OK;
- xfree(p);
- return retval;
-}
-
-/*
- * ":loadview [nr]"
- */
-static void ex_loadview(exarg_T *eap)
-{
- char *fname = get_view_file(*eap->arg);
- if (fname != NULL) {
- if (do_source((char_u *)fname, FALSE, DOSO_NONE) == FAIL) {
- EMSG2(_(e_notopen), fname);
- }
- xfree(fname);
- }
-}
-
-/// Get the name of the view file for the current buffer.
-static char *get_view_file(int c)
-{
- if (curbuf->b_ffname == NULL) {
- EMSG(_(e_noname));
- return NULL;
- }
- char *sname = (char *)home_replace_save(NULL, curbuf->b_ffname);
-
- // We want a file name without separators, because we're not going to make
- // a directory.
- // "normal" path separator -> "=+"
- // "=" -> "=="
- // ":" path separator -> "=-"
- size_t len = 0;
- for (char *p = sname; *p; p++) {
- if (*p == '=' || vim_ispathsep(*p)) {
- ++len;
- }
- }
- char *retval = xmalloc(strlen(sname) + len + STRLEN(p_vdir) + 9);
- STRCPY(retval, p_vdir);
- add_pathsep(retval);
- char *s = retval + strlen(retval);
- for (char *p = sname; *p; p++) {
- if (*p == '=') {
- *s++ = '=';
- *s++ = '=';
- } else if (vim_ispathsep(*p)) {
- *s++ = '=';
-#if defined(BACKSLASH_IN_FILENAME)
- if (*p == ':')
- *s++ = '-';
- else
-#endif
- *s++ = '+';
- } else
- *s++ = *p;
- }
- *s++ = '=';
- *s++ = c;
- strcpy(s, ".vim");
-
- xfree(sname);
- return retval;
-}
-
-
-/*
- * Write end-of-line character(s) for ":mkexrc", ":mkvimrc" and ":mksession".
- * Return FAIL for a write error.
- */
-int put_eol(FILE *fd)
-{
-#if defined(USE_CRNL) && defined(MKSESSION_NL)
- if ((!mksession_nl && putc('\r', fd) < 0) || putc('\n', fd) < 0) {
-#elif defined(USE_CRNL)
- if (putc('\r', fd) < 0 || putc('\n', fd) < 0) {
-#else
- if (putc('\n', fd) < 0) {
-#endif
- return FAIL;
- }
- return OK;
-}
-
-/*
- * Write a line to "fd".
- * Return FAIL for a write error.
- */
-int put_line(FILE *fd, char *s)
-{
- if (fputs(s, fd) < 0 || put_eol(fd) == FAIL)
- return FAIL;
- return OK;
-}
-
/*
* ":rshada" and ":wshada".
*/
@@ -10010,10 +9209,11 @@ static void ex_setfiletype(exarg_T *eap)
static void ex_digraphs(exarg_T *eap)
{
- if (*eap->arg != NUL)
+ if (*eap->arg != NUL) {
putdigraph(eap->arg);
- else
- listdigraphs();
+ } else {
+ listdigraphs(eap->forceit);
+ }
}
static void ex_set(exarg_T *eap)
@@ -10108,8 +9308,9 @@ static void ex_match(exarg_T *eap)
static void ex_fold(exarg_T *eap)
{
- if (foldManualAllowed(TRUE))
- foldCreate(eap->line1, eap->line2);
+ if (foldManualAllowed(true)) {
+ foldCreate(curwin, eap->line1, eap->line2);
+ }
}
static void ex_foldopen(exarg_T *eap)
@@ -10131,6 +9332,28 @@ static void ex_folddo(exarg_T *eap)
ml_clearmarked(); // clear rest of the marks
}
+// Returns true if the supplied Ex cmdidx is for a location list command
+// instead of a quickfix command.
+bool is_loclist_cmd(int cmdidx)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ if (cmdidx < 0 || cmdidx >= CMD_SIZE) {
+ return false;
+ }
+ return cmdnames[cmdidx].cmd_name[0] == 'l';
+}
+
+bool get_pressedreturn(void)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ return ex_pressedreturn;
+}
+
+void set_pressedreturn(bool val)
+{
+ ex_pressedreturn = val;
+}
+
static void ex_terminal(exarg_T *eap)
{
char ex_cmd[1024];
@@ -10178,10 +9401,13 @@ bool cmd_can_preview(char_u *cmd)
return false;
}
+ // Ignore additional colons at the start...
+ cmd = skip_colon_white(cmd, true);
+
// Ignore any leading modifiers (:keeppatterns, :verbose, etc.)
for (int len = modifier_len(cmd); len != 0; len = modifier_len(cmd)) {
cmd += len;
- cmd = skipwhite(cmd);
+ cmd = skip_colon_white(cmd, true);
}
exarg_T ea;
@@ -10218,8 +9444,6 @@ bool cmd_can_preview(char_u *cmd)
Dictionary commands_array(buf_T *buf)
{
Dictionary rv = ARRAY_DICT_INIT;
- Object obj = NIL;
- (void)obj; // Avoid "dead assignment" warning.
char str[20];
garray_T *gap = (buf == NULL) ? &ucmds : &buf->b_ucmds;
@@ -10250,7 +9474,7 @@ Dictionary commands_array(buf_T *buf)
PUT(d, "complete_arg", cmd->uc_compl_arg == NULL
? NIL : STRING_OBJ(cstr_to_string((char *)cmd->uc_compl_arg)));
- obj = NIL;
+ Object obj = NIL;
if (cmd->uc_argt & COUNT) {
if (cmd->uc_def >= 0) {
snprintf(str, sizeof(str), "%" PRId64, (int64_t)cmd->uc_def);
diff --git a/src/nvim/ex_docmd.h b/src/nvim/ex_docmd.h
index cff350de08..f6bd2adcd5 100644
--- a/src/nvim/ex_docmd.h
+++ b/src/nvim/ex_docmd.h
@@ -20,6 +20,20 @@
#define EXMODE_NORMAL 1
#define EXMODE_VIM 2
+// Structure used to save the current state. Used when executing Normal mode
+// commands while in any other mode.
+typedef struct {
+ int save_msg_scroll;
+ int save_restart_edit;
+ int save_msg_didout;
+ int save_State;
+ int save_insertmode;
+ bool save_finish_op;
+ long save_opcount;
+ int save_reg_executing;
+ tasave_T tabuf;
+} save_state_T;
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ex_docmd.h.generated.h"
#endif
diff --git a/src/nvim/ex_eval.c b/src/nvim/ex_eval.c
index 28bc222827..81274fcf2a 100644
--- a/src/nvim/ex_eval.c
+++ b/src/nvim/ex_eval.c
@@ -16,7 +16,7 @@
#include "nvim/ex_eval.h"
#include "nvim/charset.h"
#include "nvim/eval.h"
-#include "nvim/eval/typval.h"
+#include "nvim/eval/userfunc.h"
#include "nvim/ex_cmds2.h"
#include "nvim/ex_docmd.h"
#include "nvim/message.h"
@@ -307,7 +307,7 @@ void free_global_msglist(void)
* error exception. If cstack is NULL, postpone the throw until do_cmdline()
* has returned (see do_one_cmd()).
*/
-void do_errthrow(struct condstack *cstack, char_u *cmdname)
+void do_errthrow(cstack_T *cstack, char_u *cmdname)
{
/*
* Ensure that all commands in nested function calls and sourced files
@@ -339,7 +339,7 @@ void do_errthrow(struct condstack *cstack, char_u *cmdname)
* exception if appropriate. Return TRUE if the current exception is discarded,
* FALSE otherwise.
*/
-int do_intthrow(struct condstack *cstack)
+int do_intthrow(cstack_T *cstack)
{
// If no interrupt occurred or no try conditional is active and no exception
// is being thrown, do nothing (for compatibility of non-EH scripts).
@@ -508,7 +508,7 @@ static int throw_exception(void *value, except_type_T type, char_u *cmdname)
nomem:
xfree(excp);
- suppress_errthrow = TRUE;
+ suppress_errthrow = true;
EMSG(_(e_outofmem));
fail:
current_exception = NULL;
@@ -795,7 +795,7 @@ void ex_if(exarg_T *eap)
{
int skip;
int result;
- struct condstack *cstack = eap->cstack;
+ cstack_T *const cstack = eap->cstack;
if (cstack->cs_idx == CSTACK_LEN - 1)
eap->errmsg = (char_u *)N_("E579: :if nesting too deep");
@@ -852,7 +852,7 @@ void ex_else(exarg_T *eap)
{
int skip;
int result;
- struct condstack *cstack = eap->cstack;
+ cstack_T *const cstack = eap->cstack;
skip = CHECK_SKIP;
@@ -926,7 +926,7 @@ void ex_while(exarg_T *eap)
bool error;
int skip;
int result;
- struct condstack *cstack = eap->cstack;
+ cstack_T *const cstack = eap->cstack;
if (cstack->cs_idx == CSTACK_LEN - 1)
eap->errmsg = (char_u *)N_("E585: :while/:for nesting too deep");
@@ -1005,7 +1005,7 @@ void ex_while(exarg_T *eap)
void ex_continue(exarg_T *eap)
{
int idx;
- struct condstack *cstack = eap->cstack;
+ cstack_T *const cstack = eap->cstack;
if (cstack->cs_looplevel <= 0 || cstack->cs_idx < 0)
eap->errmsg = (char_u *)N_("E586: :continue without :while or :for");
@@ -1039,7 +1039,7 @@ void ex_continue(exarg_T *eap)
void ex_break(exarg_T *eap)
{
int idx;
- struct condstack *cstack = eap->cstack;
+ cstack_T *const cstack = eap->cstack;
if (cstack->cs_looplevel <= 0 || cstack->cs_idx < 0)
eap->errmsg = (char_u *)N_("E587: :break without :while or :for");
@@ -1061,7 +1061,7 @@ void ex_break(exarg_T *eap)
*/
void ex_endwhile(exarg_T *eap)
{
- struct condstack *cstack = eap->cstack;
+ cstack_T *const cstack = eap->cstack;
int idx;
char_u *err;
int csf;
@@ -1164,7 +1164,7 @@ void ex_throw(exarg_T *eap)
* for ":throw" (user exception) and error and interrupt exceptions. Also
* used for rethrowing an uncaught exception.
*/
-void do_throw(struct condstack *cstack)
+void do_throw(cstack_T *cstack)
{
int idx;
int inactivate_try = FALSE;
@@ -1225,7 +1225,7 @@ void do_throw(struct condstack *cstack)
void ex_try(exarg_T *eap)
{
int skip;
- struct condstack *cstack = eap->cstack;
+ cstack_T *const cstack = eap->cstack;
if (cstack->cs_idx == CSTACK_LEN - 1)
eap->errmsg = (char_u *)N_("E601: :try nesting too deep");
@@ -1260,7 +1260,7 @@ void ex_try(exarg_T *eap)
* to save the value.
*/
if (emsg_silent) {
- eslist_T *elem = xmalloc(sizeof(struct eslist_elem));
+ eslist_T *elem = xmalloc(sizeof(*elem));
elem->saved_emsg_silent = emsg_silent;
elem->next = cstack->cs_emsg_silent_list;
cstack->cs_emsg_silent_list = elem;
@@ -1286,7 +1286,7 @@ void ex_catch(exarg_T *eap)
char_u *save_cpo;
regmatch_T regmatch;
int prev_got_int;
- struct condstack *cstack = eap->cstack;
+ cstack_T *const cstack = eap->cstack;
char_u *pat;
if (cstack->cs_trylevel <= 0 || cstack->cs_idx < 0) {
@@ -1432,7 +1432,7 @@ void ex_finally(exarg_T *eap)
int idx;
int skip = FALSE;
int pending = CSTP_NONE;
- struct condstack *cstack = eap->cstack;
+ cstack_T *const cstack = eap->cstack;
if (cstack->cs_trylevel <= 0 || cstack->cs_idx < 0)
eap->errmsg = (char_u *)N_("E606: :finally without :try");
@@ -1555,7 +1555,7 @@ void ex_endtry(exarg_T *eap)
int rethrow = FALSE;
int pending = CSTP_NONE;
void *rettv = NULL;
- struct condstack *cstack = eap->cstack;
+ cstack_T *const cstack = eap->cstack;
if (cstack->cs_trylevel <= 0 || cstack->cs_idx < 0) {
eap->errmsg = (char_u *)N_("E602: :endtry without :try");
@@ -1882,7 +1882,7 @@ void leave_cleanup(cleanup_T *csp)
* entered, is restored (used by ex_endtry()). This is normally done only
* when such a try conditional is left.
*/
-int cleanup_conditionals(struct condstack *cstack, int searched_cond, int inclusive)
+int cleanup_conditionals(cstack_T *cstack, int searched_cond, int inclusive)
{
int idx;
int stop = FALSE;
@@ -1990,7 +1990,7 @@ int cleanup_conditionals(struct condstack *cstack, int searched_cond, int inclus
/*
* Return an appropriate error message for a missing endwhile/endfor/endif.
*/
-static char_u *get_end_emsg(struct condstack *cstack)
+static char_u *get_end_emsg(cstack_T *cstack)
{
if (cstack->cs_flags[cstack->cs_idx] & CSF_WHILE)
return e_endwhile;
@@ -2007,7 +2007,8 @@ static char_u *get_end_emsg(struct condstack *cstack)
* type.
* Also free "for info" structures where needed.
*/
-void rewind_conditionals(struct condstack *cstack, int idx, int cond_type, int *cond_level)
+void rewind_conditionals(cstack_T *cstack, int idx, int cond_type,
+ int *cond_level)
{
while (cstack->cs_idx > idx) {
if (cstack->cs_flags[cstack->cs_idx] & cond_type)
diff --git a/src/nvim/ex_eval.h b/src/nvim/ex_eval.h
index 2237b6aca3..d8388c9156 100644
--- a/src/nvim/ex_eval.h
+++ b/src/nvim/ex_eval.h
@@ -4,42 +4,6 @@
#include "nvim/pos.h" // for linenr_T
#include "nvim/ex_cmds_defs.h" // for exarg_T
-/*
- * 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.
- */
-#define CSTACK_LEN 50
-
-struct condstack {
- 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
-};
-# 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. */
# define CSF_TRUE 0x0001 /* condition was TRUE */
@@ -70,14 +34,6 @@ struct condstack {
# define CSTP_FINISH 32 /* ":finish" is pending */
/*
- * Flags for the cs_lflags item in struct condstack.
- */
-# define CSL_HAD_LOOP 1 /* just found ":while" or ":for" */
-# define CSL_HAD_ENDLOOP 2 /* just found ":endwhile" or ":endfor" */
-# define CSL_HAD_CONT 4 /* just found ":continue" */
-# define 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
diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c
index 3f7bc45c2e..93684e8606 100644
--- a/src/nvim/ex_getln.c
+++ b/src/nvim/ex_getln.c
@@ -23,6 +23,7 @@
#include "nvim/digraph.h"
#include "nvim/edit.h"
#include "nvim/eval.h"
+#include "nvim/eval/userfunc.h"
#include "nvim/ex_cmds.h"
#include "nvim/ex_cmds2.h"
#include "nvim/ex_docmd.h"
@@ -136,6 +137,15 @@ struct cmdline_info {
/// Last value of prompt_id, incremented when doing new prompt
static unsigned last_prompt_id = 0;
+typedef struct {
+ colnr_T vs_curswant;
+ colnr_T vs_leftcol;
+ linenr_T vs_topline;
+ int vs_topfill;
+ linenr_T vs_botline;
+ int vs_empty_rows;
+} viewstate_T;
+
typedef struct command_line_state {
VimState state;
int firstc;
@@ -151,16 +161,8 @@ typedef struct command_line_state {
int histype; // history type to be used
pos_T search_start; // where 'incsearch' starts searching
pos_T save_cursor;
- colnr_T old_curswant;
- colnr_T init_curswant;
- colnr_T old_leftcol;
- colnr_T init_leftcol;
- linenr_T old_topline;
- linenr_T init_topline;
- int old_topfill;
- int init_topfill;
- linenr_T old_botline;
- linenr_T init_botline;
+ viewstate_T init_viewstate;
+ viewstate_T old_viewstate;
pos_T match_start;
pos_T match_end;
int did_incsearch;
@@ -228,6 +230,28 @@ static int compl_selected;
static int cmd_hkmap = 0; // Hebrew mapping during command line
+static void save_viewstate(viewstate_T *vs)
+ FUNC_ATTR_NONNULL_ALL
+{
+ vs->vs_curswant = curwin->w_curswant;
+ vs->vs_leftcol = curwin->w_leftcol;
+ vs->vs_topline = curwin->w_topline;
+ vs->vs_topfill = curwin->w_topfill;
+ vs->vs_botline = curwin->w_botline;
+ vs->vs_empty_rows = curwin->w_empty_rows;
+}
+
+static void restore_viewstate(viewstate_T *vs)
+ FUNC_ATTR_NONNULL_ALL
+{
+ curwin->w_curswant = vs->vs_curswant;
+ curwin->w_leftcol = vs->vs_leftcol;
+ curwin->w_topline = vs->vs_topline;
+ curwin->w_topfill = vs->vs_topfill;
+ curwin->w_botline = vs->vs_botline;
+ curwin->w_empty_rows = vs->vs_empty_rows;
+}
+
/// Internal entry point for cmdline mode.
///
/// caller must use save_cmdline and restore_cmdline. Best is to use
@@ -238,21 +262,18 @@ static uint8_t *command_line_enter(int firstc, long count, int indent)
static int cmdline_level = 0;
cmdline_level++;
- CommandLineState state, *s = &state;
- memset(s, 0, sizeof(CommandLineState));
- s->firstc = firstc;
- s->count = count;
- s->indent = indent;
- s->save_msg_scroll = msg_scroll;
- s->save_State = State;
+ CommandLineState state = {
+ .firstc = firstc,
+ .count = count,
+ .indent = indent,
+ .save_msg_scroll = msg_scroll,
+ .save_State = State,
+ .ignore_drag_release = true,
+ .match_start = curwin->w_cursor,
+ };
+ CommandLineState *s = &state;
s->save_p_icm = vim_strsave(p_icm);
- s->ignore_drag_release = true;
- s->match_start = curwin->w_cursor;
- s->init_curswant = curwin->w_curswant;
- s->init_leftcol = curwin->w_leftcol;
- s->init_topline = curwin->w_topline;
- s->init_topfill = curwin->w_topfill;
- s->init_botline = curwin->w_botline;
+ save_viewstate(&state.init_viewstate);
if (s->firstc == -1) {
s->firstc = NUL;
@@ -270,11 +291,7 @@ static uint8_t *command_line_enter(int firstc, long count, int indent)
clearpos(&s->match_end);
s->save_cursor = curwin->w_cursor; // may be restored later
s->search_start = curwin->w_cursor;
- s->old_curswant = curwin->w_curswant;
- s->old_leftcol = curwin->w_leftcol;
- s->old_topline = curwin->w_topline;
- s->old_topfill = curwin->w_topfill;
- s->old_botline = curwin->w_botline;
+ save_viewstate(&state.old_viewstate);
assert(indent >= 0);
@@ -415,8 +432,8 @@ static uint8_t *command_line_enter(int firstc, long count, int indent)
tv_dict_add_nr(dict, S_LEN("cmdlevel"), ccline.level);
tv_dict_set_keys_readonly(dict);
// not readonly:
- tv_dict_add_special(dict, S_LEN("abort"),
- s->gotesc ? kSpecialVarTrue : kSpecialVarFalse);
+ tv_dict_add_bool(dict, S_LEN("abort"),
+ s->gotesc ? kBoolVarTrue : kBoolVarFalse);
try_enter(&tstate);
apply_autocmds(EVENT_CMDLINELEAVE, (char_u *)firstcbuf, (char_u *)firstcbuf,
false, curbuf);
@@ -433,6 +450,11 @@ static uint8_t *command_line_enter(int firstc, long count, int indent)
ExpandCleanup(&s->xpc);
ccline.xpc = NULL;
+ if (s->gotesc) {
+ // There might be a preview window open for inccommand. Close it.
+ close_preview_windows();
+ }
+
if (s->did_incsearch) {
if (s->gotesc) {
curwin->w_cursor = s->save_cursor;
@@ -444,11 +466,7 @@ static uint8_t *command_line_enter(int firstc, long count, int indent)
}
curwin->w_cursor = s->search_start; // -V519
}
- curwin->w_curswant = s->old_curswant;
- curwin->w_leftcol = s->old_leftcol;
- curwin->w_topline = s->old_topline;
- curwin->w_topfill = s->old_topfill;
- curwin->w_botline = s->old_botline;
+ restore_viewstate(&s->old_viewstate);
highlight_match = false;
validate_cursor(); // needed for TAB
redraw_all_later(SOME_VALID);
@@ -531,7 +549,7 @@ static int command_line_check(VimState *state)
static int command_line_execute(VimState *state, int key)
{
- if (key == K_IGNORE) {
+ if (key == K_IGNORE || key == K_NOP) {
return -1; // get another key
}
@@ -609,6 +627,16 @@ static int command_line_execute(VimState *state, int key)
s->c = Ctrl_N;
}
}
+ if (compl_match_array || s->did_wild_list) {
+ if (s->c == Ctrl_E) {
+ s->res = nextwild(&s->xpc, WILD_CANCEL, WILD_NO_BEEP,
+ s->firstc != '@');
+ } else if (s->c == Ctrl_Y) {
+ s->res = nextwild(&s->xpc, WILD_APPLY, WILD_NO_BEEP,
+ s->firstc != '@');
+ s->c = Ctrl_E;
+ }
+ }
// Hitting CR after "emenu Name.": complete submenu
if (s->xpc.xp_context == EXPAND_MENUNAMES && p_wmnu
@@ -878,7 +906,9 @@ static int command_line_execute(VimState *state, int key)
}
if (s->c == cedit_key || s->c == K_CMDWIN) {
- if (ex_normal_busy == 0 && got_int == false) {
+ // TODO(vim): why is ex_normal_busy checked here?
+ if ((s->c == K_CMDWIN || ex_normal_busy == 0)
+ && got_int == false) {
// Open a window to edit the command line (and history).
s->c = open_cmdwin();
s->some_key_typed = true;
@@ -923,22 +953,28 @@ static int command_line_execute(VimState *state, int key)
// - wildcard expansion is only done when the 'wildchar' key is really
// typed, not when it comes from a macro
if ((s->c == p_wc && !s->gotesc && KeyTyped) || s->c == p_wcm) {
+ int options = WILD_NO_BEEP;
+ if (wim_flags[s->wim_index] & WIM_BUFLASTUSED) {
+ options |= WILD_BUFLASTUSED;
+ }
if (s->xpc.xp_numfiles > 0) { // typed p_wc at least twice
// if 'wildmode' contains "list" may still need to list
if (s->xpc.xp_numfiles > 1
&& !s->did_wild_list
- && (wim_flags[s->wim_index] & WIM_LIST)) {
- (void)showmatches(&s->xpc, false);
+ && ((wim_flags[s->wim_index] & WIM_LIST)
+ || (p_wmnu && (wim_flags[s->wim_index] & WIM_FULL) != 0))) {
+ (void)showmatches(&s->xpc, p_wmnu
+ && ((wim_flags[s->wim_index] & WIM_LIST) == 0));
redrawcmd();
s->did_wild_list = true;
}
if (wim_flags[s->wim_index] & WIM_LONGEST) {
- s->res = nextwild(&s->xpc, WILD_LONGEST, WILD_NO_BEEP,
- s->firstc != '@');
+ s->res = nextwild(&s->xpc, WILD_LONGEST, options,
+ s->firstc != '@');
} else if (wim_flags[s->wim_index] & WIM_FULL) {
- s->res = nextwild(&s->xpc, WILD_NEXT, WILD_NO_BEEP,
- s->firstc != '@');
+ s->res = nextwild(&s->xpc, WILD_NEXT, options,
+ s->firstc != '@');
} else {
s->res = OK; // don't insert 'wildchar' now
}
@@ -949,11 +985,11 @@ static int command_line_execute(VimState *state, int key)
// if 'wildmode' first contains "longest", get longest
// common part
if (wim_flags[0] & WIM_LONGEST) {
- s->res = nextwild(&s->xpc, WILD_LONGEST, WILD_NO_BEEP,
- s->firstc != '@');
+ s->res = nextwild(&s->xpc, WILD_LONGEST, options,
+ s->firstc != '@');
} else {
- s->res = nextwild(&s->xpc, WILD_EXPAND_KEEP, WILD_NO_BEEP,
- s->firstc != '@');
+ s->res = nextwild(&s->xpc, WILD_EXPAND_KEEP, options,
+ s->firstc != '@');
}
// if interrupted while completing, behave like it failed
@@ -990,11 +1026,11 @@ static int command_line_execute(VimState *state, int key)
s->did_wild_list = true;
if (wim_flags[s->wim_index] & WIM_LONGEST) {
- nextwild(&s->xpc, WILD_LONGEST, WILD_NO_BEEP,
- s->firstc != '@');
+ nextwild(&s->xpc, WILD_LONGEST, options,
+ s->firstc != '@');
} else if (wim_flags[s->wim_index] & WIM_FULL) {
- nextwild(&s->xpc, WILD_NEXT, WILD_NO_BEEP,
- s->firstc != '@');
+ nextwild(&s->xpc, WILD_NEXT, options,
+ s->firstc != '@');
}
} else {
vim_beep(BO_WILD);
@@ -1075,7 +1111,7 @@ static void command_line_next_incsearch(CommandLineState *s, bool next_match)
int found = searchit(curwin, curbuf, &t, NULL,
next_match ? FORWARD : BACKWARD,
pat, s->count, search_flags,
- RE_SEARCH, 0, NULL, NULL);
+ RE_SEARCH, NULL);
emsg_off--;
ui_busy_stop();
if (found) {
@@ -1111,11 +1147,7 @@ static void command_line_next_incsearch(CommandLineState *s, bool next_match)
update_topline();
validate_cursor();
highlight_match = true;
- s->old_curswant = curwin->w_curswant;
- s->old_leftcol = curwin->w_leftcol;
- s->old_topline = curwin->w_topline;
- s->old_topfill = curwin->w_topfill;
- s->old_botline = curwin->w_botline;
+ save_viewstate(&s->old_viewstate);
update_screen(NOT_VALID);
redrawcmdline();
} else {
@@ -1236,11 +1268,7 @@ static int command_line_handle_key(CommandLineState *s)
s->search_start = s->save_cursor;
// save view settings, so that the screen won't be restored at the
// wrong position
- s->old_curswant = s->init_curswant;
- s->old_leftcol = s->init_leftcol;
- s->old_topline = s->init_topline;
- s->old_topfill = s->init_topfill;
- s->old_botline = s->init_botline;
+ s->old_viewstate = s->init_viewstate;
}
redrawcmd();
} else if (ccline.cmdlen == 0 && s->c != Ctrl_W
@@ -1818,6 +1846,7 @@ static int command_line_changed(CommandLineState *s)
if (p_is && !cmd_silent && (s->firstc == '/' || s->firstc == '?')) {
pos_T end_pos;
proftime_T tm;
+ searchit_arg_T sia;
// if there is a character waiting, search and redraw later
if (char_avail()) {
@@ -1844,8 +1873,10 @@ static int command_line_changed(CommandLineState *s)
if (!p_hls) {
search_flags += SEARCH_KEEP;
}
+ memset(&sia, 0, sizeof(sia));
+ sia.sa_tm = &tm;
i = do_search(NULL, s->firstc, ccline.cmdbuff, s->count,
- search_flags, &tm, NULL);
+ search_flags, &sia);
emsg_off--;
// if interrupted while searching, behave like it failed
if (got_int) {
@@ -1867,10 +1898,7 @@ static int command_line_changed(CommandLineState *s)
// first restore the old curwin values, so the screen is
// positioned in the same way as the actual search command
- curwin->w_leftcol = s->old_leftcol;
- curwin->w_topline = s->old_topline;
- curwin->w_topfill = s->old_topfill;
- curwin->w_botline = s->old_botline;
+ restore_viewstate(&s->old_viewstate);
changed_cline_bef_curs();
update_topline();
@@ -1924,21 +1952,21 @@ static int command_line_changed(CommandLineState *s)
// - Immediately undo the effects.
State |= CMDPREVIEW;
emsg_silent++; // Block error reporting as the command may be incomplete
+ msg_silent++; // Block messages, namely ones that prompt
do_cmdline(ccline.cmdbuff, NULL, NULL, DOCMD_KEEPLINE|DOCMD_NOWAIT);
+ msg_silent--; // Unblock messages
emsg_silent--; // Unblock error reporting
// Restore the window "view".
curwin->w_cursor = s->save_cursor;
- curwin->w_curswant = s->old_curswant;
- curwin->w_leftcol = s->old_leftcol;
- curwin->w_topline = s->old_topline;
- curwin->w_topfill = s->old_topfill;
- curwin->w_botline = s->old_botline;
+ restore_viewstate(&s->old_viewstate);
update_topline();
redrawcmdline();
+
} else if (State & CMDPREVIEW) {
State = (State & ~CMDPREVIEW);
+ close_preview_windows();
update_screen(SOME_VALID); // Clear 'inccommand' preview.
}
@@ -1991,7 +2019,8 @@ char_u *
getcmdline (
int firstc,
long count, // only used for incremental search
- int indent // indent for inside conditionals
+ int indent, // indent for inside conditionals
+ bool do_concat FUNC_ATTR_UNUSED
)
{
// Be prepared for situations where cmdline can be invoked recursively.
@@ -2167,17 +2196,18 @@ static void correct_screencol(int idx, int cells, int *col)
* Get an Ex command line for the ":" command.
*/
char_u *
-getexline (
- int c, /* normally ':', NUL for ":append" */
+getexline(
+ int c, // normally ':', NUL for ":append"
void *cookie,
- int indent /* indent for inside conditionals */
+ int indent, // indent for inside conditionals
+ bool do_concat
)
{
/* When executing a register, remove ':' that's in front of each line. */
if (exec_from_reg && vpeekc() == ':')
(void)vgetc();
- return getcmdline(c, 1L, indent);
+ return getcmdline(c, 1L, indent, do_concat);
}
/*
@@ -2187,11 +2217,12 @@ getexline (
* Returns a string in allocated memory or NULL.
*/
char_u *
-getexmodeline (
- int promptc, /* normally ':', NUL for ":append" and '?' for
- :s prompt */
+getexmodeline(
+ int promptc, // normally ':', NUL for ":append" and '?'
+ // for :s prompt
void *cookie,
- int indent /* indent for inside conditionals */
+ int indent, // indent for inside conditionals
+ bool do_concat
)
{
garray_T line_ga;
@@ -2320,7 +2351,7 @@ redraw:
} while (++vcol % 8);
p++;
} else {
- len = MB_PTR2LEN(p);
+ len = utfc_ptr2len(p);
msg_outtrans_len(p, len);
vcol += ptr2cells(p);
p += len;
@@ -2428,9 +2459,10 @@ redraw:
/* make following messages go to the next line */
msg_didout = FALSE;
msg_col = 0;
- if (msg_row < Rows - 1)
- ++msg_row;
- emsg_on_display = FALSE; /* don't want os_delay() */
+ if (msg_row < Rows - 1) {
+ msg_row++;
+ }
+ emsg_on_display = false; // don't want os_delay()
if (got_int)
ga_clear(&line_ga);
@@ -2692,7 +2724,7 @@ static bool color_cmdline(CmdlineInfo *colored_ccline)
goto color_cmdline_error;
}
if (tv.v_type != VAR_LIST) {
- PRINT_ERRMSG(_("E5400: Callback should return list"));
+ PRINT_ERRMSG("%s", _("E5400: Callback should return list"));
goto color_cmdline_error;
}
if (tv.vval.v_list == NULL) {
@@ -3031,7 +3063,7 @@ void cmdline_screen_cleared(void)
}
}
-/// called by ui_flush, do what redraws neccessary to keep cmdline updated.
+/// called by ui_flush, do what redraws necessary to keep cmdline updated.
void cmdline_ui_flush(void)
{
if (!ui_has(kUICmdline)) {
@@ -3557,7 +3589,7 @@ static int ccheck_abbr(int c)
// Do not consider '<,'> be part of the mapping, skip leading whitespace.
// Actually accepts any mark.
- while (ascii_iswhite(ccline.cmdbuff[spos]) && spos < ccline.cmdlen) {
+ while (spos < ccline.cmdlen && ascii_iswhite(ccline.cmdbuff[spos])) {
spos++;
}
if (ccline.cmdlen - spos > 5
@@ -3781,6 +3813,12 @@ ExpandOne (
return NULL;
}
+ if (mode == WILD_CANCEL) {
+ ss = vim_strsave(orig_save);
+ } else if (mode == WILD_APPLY) {
+ ss = vim_strsave(findex == -1 ? orig_save : xp->xp_files[findex]);
+ }
+
/* free old names */
if (xp->xp_numfiles != -1 && mode != WILD_ALL && mode != WILD_LONGEST) {
FreeWild(xp->xp_numfiles, xp->xp_files);
@@ -3792,7 +3830,7 @@ ExpandOne (
if (mode == WILD_FREE) /* only release file name */
return NULL;
- if (xp->xp_numfiles == -1) {
+ if (xp->xp_numfiles == -1 && mode != WILD_APPLY && mode != WILD_CANCEL) {
xfree(orig_save);
orig_save = orig;
orig_saved = TRUE;
@@ -4684,6 +4722,9 @@ ExpandFromContext (
flags |= EW_KEEPALL;
if (options & WILD_SILENT)
flags |= EW_SILENT;
+ if (options & WILD_NOERROR) {
+ flags |= EW_NOERROR;
+ }
if (options & WILD_ALLLINKS) {
flags |= EW_ALLLINKS;
}
@@ -4697,8 +4738,7 @@ ExpandFromContext (
int free_pat = FALSE;
int i;
- /* for ":set path=" and ":set tags=" halve backslashes for escaped
- * space */
+ // for ":set path=" and ":set tags=" halve backslashes for escaped space
if (xp->xp_backslash != XP_BS_NONE) {
free_pat = TRUE;
pat = vim_strsave(pat);
@@ -4966,8 +5006,7 @@ static void expand_shellcmd(char_u *filepat, int *num_file, char_u ***file,
int ret;
bool did_curdir = false;
- /* for ":set path=" and ":set tags=" halve backslashes for escaped
- * space */
+ // for ":set path=" and ":set tags=" halve backslashes for escaped space
pat = vim_strsave(filepat);
for (i = 0; pat[i]; ++i)
if (pat[i] == '\\' && pat[i + 1] == ' ')
@@ -5000,19 +5039,24 @@ static void expand_shellcmd(char_u *filepat, int *num_file, char_u ***file,
hashtab_T found_ht;
hash_init(&found_ht);
for (s = path; ; s = e) {
+ e = vim_strchr(s, ENV_SEPCHAR);
+ if (e == NULL) {
+ e = s + STRLEN(s);
+ }
+
if (*s == NUL) {
if (did_curdir) {
break;
}
// Find directories in the current directory, path is empty.
did_curdir = true;
- } else if (*s == '.') {
+ flags |= EW_DIR;
+ } else if (STRNCMP(s, ".", e - s) == 0) {
did_curdir = true;
- }
-
- e = vim_strchr(s, ENV_SEPCHAR);
- if (e == NULL) {
- e = s + STRLEN(s);
+ flags |= EW_DIR;
+ } else {
+ // Do not match directories inside a $PATH item.
+ flags &= ~EW_DIR;
}
l = (size_t)(e - s);
@@ -5324,12 +5368,12 @@ void globpath(char_u *path, char_u *file, garray_T *ga, int expand_options)
// Concatenate new results to previous ones.
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] = vim_strsave(p[i]);
+ ((char_u **)ga->ga_data)[ga->ga_len] = p[i];
ga->ga_len++;
}
-
- FreeWild(num_p, p);
+ xfree(p);
}
}
}
@@ -6065,12 +6109,9 @@ static int open_cmdwin(void)
set_bufref(&old_curbuf, curbuf);
- /* Save current window sizes. */
+ // Save current window sizes.
win_size_save(&winsizes);
- /* Don't execute autocommands while creating the window. */
- block_autocmds();
-
// When using completion in Insert mode with <C-R>=<C-F> one can open the
// command line window, but we don't want the popup menu then.
pum_undisplay(true);
@@ -6079,10 +6120,9 @@ static int open_cmdwin(void)
cmdmod.tab = 0;
cmdmod.noswapfile = 1;
- /* Create a window for the command-line buffer. */
+ // Create a window for the command-line buffer.
if (win_split((int)p_cwh, WSP_BOT) == FAIL) {
beep_flush();
- unblock_autocmds();
return K_IGNORE;
}
cmdwin_type = get_cmdline_type();
@@ -6097,13 +6137,11 @@ static int open_cmdwin(void)
curbuf->b_p_ma = true;
curwin->w_p_fen = false;
- // Do execute autocommands for setting the filetype (load syntax).
- unblock_autocmds();
- // But don't allow switching to another buffer.
+ // Don't allow switching to another buffer.
curbuf_lock++;
- /* Showing the prompt may have set need_wait_return, reset it. */
- need_wait_return = FALSE;
+ // Showing the prompt may have set need_wait_return, reset it.
+ need_wait_return = false;
const int histtype = hist_char2type(cmdwin_type);
if (histtype == HIST_CMD || histtype == HIST_DEBUG) {
@@ -6115,11 +6153,11 @@ static int open_cmdwin(void)
}
curbuf_lock--;
- /* Reset 'textwidth' after setting 'filetype' (the Vim filetype plugin
- * sets 'textwidth' to 78). */
+ // Reset 'textwidth' after setting 'filetype' (the Vim filetype plugin
+ // sets 'textwidth' to 78).
curbuf->b_p_tw = 0;
- /* Fill the buffer with the history. */
+ // Fill the buffer with the history.
init_history();
if (hislen > 0 && histtype != HIST_INVALID) {
i = hisidx[histtype];
@@ -6160,9 +6198,10 @@ static int open_cmdwin(void)
// Trigger CmdwinEnter autocommands.
typestr[0] = (char_u)cmdwin_type;
typestr[1] = NUL;
- apply_autocmds(EVENT_CMDWINENTER, typestr, typestr, FALSE, curbuf);
- if (restart_edit != 0) /* autocmd with ":startinsert" */
+ apply_autocmds(EVENT_CMDWINENTER, typestr, typestr, false, curbuf);
+ if (restart_edit != 0) { // autocmd with ":startinsert"
stuffcharReadbuff(K_NOP);
+ }
i = RedrawingDisabled;
RedrawingDisabled = 0;
@@ -6179,10 +6218,10 @@ static int open_cmdwin(void)
const bool save_KeyTyped = KeyTyped;
- /* Trigger CmdwinLeave autocommands. */
- apply_autocmds(EVENT_CMDWINLEAVE, typestr, typestr, FALSE, curbuf);
+ // Trigger CmdwinLeave autocommands.
+ apply_autocmds(EVENT_CMDWINLEAVE, typestr, typestr, false, curbuf);
- /* Restore KeyTyped in case it is modified by autocommands */
+ // Restore KeyTyped in case it is modified by autocommands
KeyTyped = save_KeyTyped;
// Restore the command line info.
@@ -6241,9 +6280,7 @@ static int open_cmdwin(void)
}
}
- /* Don't execute autocommands while deleting the window. */
- block_autocmds();
- // Avoid command-line window first character being concealed
+ // Avoid command-line window first character being concealed.
curwin->w_p_cole = 0;
wp = curwin;
set_bufref(&bufref, curbuf);
@@ -6256,10 +6293,8 @@ static int open_cmdwin(void)
close_buffer(NULL, bufref.br_buf, DOBUF_WIPE, false);
}
- /* Restore window sizes. */
+ // Restore window sizes.
win_size_restore(&winsizes);
-
- unblock_autocmds();
}
ga_clear(&winsizes);
@@ -6308,7 +6343,7 @@ char *script_get(exarg_T *const eap, size_t *const lenp)
for (;;) {
char *const theline = (char *)eap->getline(
eap->cstack->cs_looplevel > 0 ? -1 :
- NUL, eap->cookie, 0);
+ NUL, eap->cookie, 0, true);
if (theline == NULL || strcmp(end_pattern, theline) == 0) {
xfree(theline);
diff --git a/src/nvim/ex_getln.h b/src/nvim/ex_getln.h
index 051564fbe1..dc4395e081 100644
--- a/src/nvim/ex_getln.h
+++ b/src/nvim/ex_getln.h
@@ -16,6 +16,8 @@
#define WILD_ALL 6
#define WILD_LONGEST 7
#define WILD_ALL_KEEP 8
+#define WILD_CANCEL 9
+#define WILD_APPLY 10
#define WILD_LIST_NOTFOUND 0x01
#define WILD_HOME_REPLACE 0x02
@@ -27,6 +29,9 @@
#define WILD_ESCAPE 0x80
#define WILD_ICASE 0x100
#define WILD_ALLLINKS 0x200
+#define WILD_IGNORE_COMPLETESLASH 0x400
+#define WILD_NOERROR 0x800 // sets EW_NOERROR
+#define WILD_BUFLASTUSED 0x1000
/// Present history tables
typedef enum {
diff --git a/src/nvim/ex_session.c b/src/nvim/ex_session.c
new file mode 100644
index 0000000000..1b797c6168
--- /dev/null
+++ b/src/nvim/ex_session.c
@@ -0,0 +1,1047 @@
+// This is an open source non-commercial project. Dear PVS-Studio, please check
+// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+
+// Functions for creating a session file, i.e. implementing:
+// :mkexrc
+// :mkvimrc
+// :mkview
+// :mksession
+
+#include <assert.h>
+#include <string.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <inttypes.h>
+
+#include "nvim/vim.h"
+#include "nvim/globals.h"
+#include "nvim/ascii.h"
+#include "nvim/buffer.h"
+#include "nvim/cursor.h"
+#include "nvim/edit.h"
+#include "nvim/eval.h"
+#include "nvim/ex_cmds2.h"
+#include "nvim/ex_docmd.h"
+#include "nvim/ex_getln.h"
+#include "nvim/ex_session.h"
+#include "nvim/file_search.h"
+#include "nvim/fileio.h"
+#include "nvim/fold.h"
+#include "nvim/getchar.h"
+#include "nvim/keymap.h"
+#include "nvim/misc1.h"
+#include "nvim/move.h"
+#include "nvim/option.h"
+#include "nvim/os/input.h"
+#include "nvim/os/os.h"
+#include "nvim/os/time.h"
+#include "nvim/path.h"
+#include "nvim/window.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "ex_session.c.generated.h"
+#endif
+
+/// Whether ":lcd" or ":tcd" was produced for a session.
+static int did_lcd;
+
+#define PUTLINE_FAIL(s) \
+ do { if (FAIL == put_line(fd, (s))) { return FAIL; } } while (0)
+
+static int put_view_curpos(FILE *fd, const win_T *wp, char *spaces)
+{
+ int r;
+
+ if (wp->w_curswant == MAXCOL) {
+ r = fprintf(fd, "%snormal! $\n", spaces);
+ } else {
+ r = fprintf(fd, "%snormal! 0%d|\n", spaces, wp->w_virtcol + 1);
+ }
+ return r >= 0;
+}
+
+static int ses_winsizes(FILE *fd, int restore_size, win_T *tab_firstwin)
+{
+ int n = 0;
+ win_T *wp;
+
+ if (restore_size && (ssop_flags & SSOP_WINSIZE)) {
+ for (wp = tab_firstwin; wp != NULL; wp = wp->w_next) {
+ if (!ses_do_win(wp)) {
+ continue;
+ }
+ n++;
+
+ // restore height when not full height
+ if (wp->w_height + wp->w_status_height < topframe->fr_height
+ && (fprintf(fd,
+ "exe '%dresize ' . ((&lines * %" PRId64
+ " + %" PRId64 ") / %" PRId64 ")\n",
+ n, (int64_t)wp->w_height,
+ (int64_t)Rows / 2, (int64_t)Rows) < 0)) {
+ return FAIL;
+ }
+
+ // restore width when not full width
+ if (wp->w_width < Columns
+ && (fprintf(fd,
+ "exe 'vert %dresize ' . ((&columns * %" PRId64
+ " + %" PRId64 ") / %" PRId64 ")\n",
+ n, (int64_t)wp->w_width, (int64_t)Columns / 2,
+ (int64_t)Columns) < 0)) {
+ return FAIL;
+ }
+ }
+ } else {
+ // Just equalize window sizes.
+ PUTLINE_FAIL("wincmd =");
+ }
+ return OK;
+}
+
+// Write commands to "fd" to recursively create windows for frame "fr",
+// horizontally and vertically split.
+// After the commands the last window in the frame is the current window.
+// Returns FAIL when writing the commands to "fd" fails.
+static int ses_win_rec(FILE *fd, frame_T *fr)
+{
+ frame_T *frc;
+ int count = 0;
+
+ if (fr->fr_layout != FR_LEAF) {
+ // Find first frame that's not skipped and then create a window for
+ // each following one (first frame is already there).
+ frc = ses_skipframe(fr->fr_child);
+ if (frc != NULL) {
+ while ((frc = ses_skipframe(frc->fr_next)) != NULL) {
+ // Make window as big as possible so that we have lots of room
+ // to split.
+ if (fprintf(fd, "%s%s",
+ "wincmd _ | wincmd |\n",
+ (fr->fr_layout == FR_COL ? "split\n" : "vsplit\n")) < 0) {
+ return FAIL;
+ }
+ count++;
+ }
+ }
+
+ // Go back to the first window.
+ if (count > 0 && (fprintf(fd, fr->fr_layout == FR_COL
+ ? "%dwincmd k\n" : "%dwincmd h\n", count) < 0)) {
+ return FAIL;
+ }
+
+ // Recursively create frames/windows in each window of this column or row.
+ frc = ses_skipframe(fr->fr_child);
+ while (frc != NULL) {
+ ses_win_rec(fd, frc);
+ frc = ses_skipframe(frc->fr_next);
+ // Go to next window.
+ if (frc != NULL && put_line(fd, "wincmd w") == FAIL) {
+ return FAIL;
+ }
+ }
+ }
+ return OK;
+}
+
+// Skip frames that don't contain windows we want to save in the Session.
+// Returns NULL when there none.
+static frame_T *ses_skipframe(frame_T *fr)
+{
+ frame_T *frc;
+
+ FOR_ALL_FRAMES(frc, fr) {
+ if (ses_do_frame(frc)) {
+ break;
+ }
+ }
+ return frc;
+}
+
+// Return true if frame "fr" has a window somewhere that we want to save in
+// the Session.
+static bool ses_do_frame(const frame_T *fr)
+ FUNC_ATTR_NONNULL_ARG(1)
+{
+ const frame_T *frc;
+
+ if (fr->fr_layout == FR_LEAF) {
+ return ses_do_win(fr->fr_win);
+ }
+ FOR_ALL_FRAMES(frc, fr->fr_child) {
+ if (ses_do_frame(frc)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/// Return non-zero if window "wp" is to be stored in the Session.
+static int ses_do_win(win_T *wp)
+{
+ if (wp->w_buffer->b_fname == NULL
+ // When 'buftype' is "nofile" can't restore the window contents.
+ || (!wp->w_buffer->terminal && bt_nofile(wp->w_buffer))) {
+ return ssop_flags & SSOP_BLANK;
+ }
+ if (bt_help(wp->w_buffer)) {
+ return ssop_flags & SSOP_HELP;
+ }
+ return true;
+}
+
+/// Writes an :argument list to the session file.
+///
+/// @param fd
+/// @param cmd
+/// @param gap
+/// @param fullname true: use full path name
+/// @param flagp
+///
+/// @returns FAIL if writing fails.
+static int ses_arglist(FILE *fd, char *cmd, garray_T *gap, int fullname,
+ unsigned *flagp)
+{
+ char_u *buf = NULL;
+ char_u *s;
+
+ if (fprintf(fd, "%s\n%s\n", cmd, "%argdel") < 0) {
+ return FAIL;
+ }
+ for (int i = 0; i < gap->ga_len; i++) {
+ // NULL file names are skipped (only happens when out of memory).
+ s = alist_name(&((aentry_T *)gap->ga_data)[i]);
+ if (s != NULL) {
+ if (fullname) {
+ buf = xmalloc(MAXPATHL);
+ (void)vim_FullName((char *)s, (char *)buf, MAXPATHL, false);
+ s = buf;
+ }
+ char *fname_esc = ses_escape_fname((char *)s, flagp);
+ if (fprintf(fd, "$argadd %s\n", fname_esc) < 0) {
+ xfree(fname_esc);
+ xfree(buf);
+ return FAIL;
+ }
+ xfree(fname_esc);
+ xfree(buf);
+ }
+ }
+ return OK;
+}
+
+/// Gets the buffer name for `buf`.
+static char *ses_get_fname(buf_T *buf, unsigned *flagp)
+{
+ // Use the short file name if the current directory is known at the time
+ // the session file will be sourced.
+ // Don't do this for ":mkview", we don't know the current directory.
+ // Don't do this after ":lcd", we don't keep track of what the current
+ // directory is.
+ if (buf->b_sfname != NULL
+ && flagp == &ssop_flags
+ && (ssop_flags & (SSOP_CURDIR | SSOP_SESDIR))
+ && !p_acd
+ && !did_lcd) {
+ return (char *)buf->b_sfname;
+ }
+ return (char *)buf->b_ffname;
+}
+
+/// Write a buffer name to the session file.
+/// Also ends the line, if "add_eol" is true.
+/// Returns FAIL if writing fails.
+static int ses_fname(FILE *fd, buf_T *buf, unsigned *flagp, bool add_eol)
+{
+ char *name = ses_get_fname(buf, flagp);
+ if (ses_put_fname(fd, (char_u *)name, flagp) == FAIL
+ || (add_eol && fprintf(fd, "\n") < 0)) {
+ return FAIL;
+ }
+ return OK;
+}
+
+// Escapes a filename for session writing.
+// Takes care of "slash" flag in 'sessionoptions' and escapes special
+// characters.
+//
+// Returns allocated string or NULL.
+static char *ses_escape_fname(char *name, unsigned *flagp)
+{
+ char *p;
+ char *sname = (char *)home_replace_save(NULL, (char_u *)name);
+
+ // Always SSOP_SLASH: change all backslashes to forward slashes.
+ for (p = sname; *p != NUL; MB_PTR_ADV(p)) {
+ if (*p == '\\') {
+ *p = '/';
+ }
+ }
+
+ // Escape special characters.
+ p = vim_strsave_fnameescape(sname, false);
+ xfree(sname);
+ return p;
+}
+
+// Write a file name to the session file.
+// Takes care of the "slash" option in 'sessionoptions' and escapes special
+// characters.
+// Returns FAIL if writing fails.
+static int ses_put_fname(FILE *fd, char_u *name, unsigned *flagp)
+{
+ char *p = ses_escape_fname((char *)name, flagp);
+ bool retval = fputs(p, fd) < 0 ? FAIL : OK;
+ xfree(p);
+ return retval;
+}
+
+// Write commands to "fd" to restore the view of a window.
+// Caller must make sure 'scrolloff' is zero.
+static int put_view(
+ FILE *fd,
+ win_T *wp,
+ int add_edit, // add ":edit" command to view
+ unsigned *flagp, // vop_flags or ssop_flags
+ int current_arg_idx // current argument index of the window, use
+) // -1 if unknown
+{
+ win_T *save_curwin;
+ int f;
+ int do_cursor;
+ int did_next = false;
+
+ // Always restore cursor position for ":mksession". For ":mkview" only
+ // when 'viewoptions' contains "cursor".
+ do_cursor = (flagp == &ssop_flags || *flagp & SSOP_CURSOR);
+
+ //
+ // Local argument list.
+ //
+ if (wp->w_alist == &global_alist) {
+ PUTLINE_FAIL("argglobal");
+ } else {
+ if (ses_arglist(fd, "arglocal", &wp->w_alist->al_ga,
+ flagp == &vop_flags
+ || !(*flagp & SSOP_CURDIR)
+ || wp->w_localdir != NULL, flagp) == FAIL) {
+ return FAIL;
+ }
+ }
+
+ // Only when part of a session: restore the argument index. Some
+ // arguments may have been deleted, check if the index is valid.
+ if (wp->w_arg_idx != current_arg_idx && wp->w_arg_idx < WARGCOUNT(wp)
+ && flagp == &ssop_flags) {
+ if (fprintf(fd, "%" PRId64 "argu\n", (int64_t)wp->w_arg_idx + 1) < 0) {
+ return FAIL;
+ }
+ did_next = true;
+ }
+
+ // Edit the file. Skip this when ":next" already did it.
+ if (add_edit && (!did_next || wp->w_arg_idx_invalid)) {
+ char *fname_esc =
+ ses_escape_fname(ses_get_fname(wp->w_buffer, flagp), flagp);
+ //
+ // Load the file.
+ //
+ if (wp->w_buffer->b_ffname != NULL
+ && (!bt_nofile(wp->w_buffer) || wp->w_buffer->terminal)
+ ) {
+ // Editing a file in this buffer: use ":edit file".
+ // This may have side effects! (e.g., compressed or network file).
+ //
+ // Note, if a buffer for that file already exists, use :badd to
+ // edit that buffer, to not lose folding information (:edit resets
+ // folds in other buffers)
+ if (fprintf(fd,
+ "if bufexists(\"%s\") | buffer %s | else | edit %s | endif\n"
+ // Fixup :terminal buffer name. #7836
+ "if &buftype ==# 'terminal'\n"
+ " silent file %s\n"
+ "endif\n",
+ fname_esc,
+ fname_esc,
+ fname_esc,
+ fname_esc) < 0) {
+ xfree(fname_esc);
+ return FAIL;
+ }
+ } else {
+ // No file in this buffer, just make it empty.
+ PUTLINE_FAIL("enew");
+ if (wp->w_buffer->b_ffname != NULL) {
+ // The buffer does have a name, but it's not a file name.
+ if (fprintf(fd, "file %s\n", fname_esc) < 0) {
+ xfree(fname_esc);
+ return FAIL;
+ }
+ }
+ do_cursor = false;
+ }
+ xfree(fname_esc);
+ }
+
+ //
+ // Local mappings and abbreviations.
+ //
+ if ((*flagp & (SSOP_OPTIONS | SSOP_LOCALOPTIONS))
+ && makemap(fd, wp->w_buffer) == FAIL) {
+ return FAIL;
+ }
+
+ //
+ // Local options. Need to go to the window temporarily.
+ // Store only local values when using ":mkview" and when ":mksession" is
+ // used and 'sessionoptions' doesn't include "nvim/options".
+ // Some folding options are always stored when "folds" is included,
+ // otherwise the folds would not be restored correctly.
+ //
+ save_curwin = curwin;
+ curwin = wp;
+ curbuf = curwin->w_buffer;
+ if (*flagp & (SSOP_OPTIONS | SSOP_LOCALOPTIONS)) {
+ f = makeset(fd, OPT_LOCAL,
+ flagp == &vop_flags || !(*flagp & SSOP_OPTIONS));
+ } else if (*flagp & SSOP_FOLDS) {
+ f = makefoldset(fd);
+ } else {
+ f = OK;
+ }
+ curwin = save_curwin;
+ curbuf = curwin->w_buffer;
+ if (f == FAIL) {
+ return FAIL;
+ }
+
+ //
+ // Save Folds when 'buftype' is empty and for help files.
+ //
+ if ((*flagp & SSOP_FOLDS)
+ && wp->w_buffer->b_ffname != NULL
+ && (bt_normal(wp->w_buffer) || bt_help(wp->w_buffer))
+ ) {
+ if (put_folds(fd, wp) == FAIL) {
+ return FAIL;
+ }
+ }
+
+ //
+ // Set the cursor after creating folds, since that moves the cursor.
+ //
+ if (do_cursor) {
+ // Restore the cursor line in the file and relatively in the
+ // window. Don't use "G", it changes the jumplist.
+ if (fprintf(fd,
+ "let s:l = %" PRId64 " - ((%" PRId64
+ " * winheight(0) + %" PRId64 ") / %" PRId64 ")\n"
+ "if s:l < 1 | let s:l = 1 | endif\n"
+ "exe s:l\n"
+ "normal! zt\n"
+ "%" PRId64 "\n",
+ (int64_t)wp->w_cursor.lnum,
+ (int64_t)(wp->w_cursor.lnum - wp->w_topline),
+ (int64_t)(wp->w_height_inner / 2),
+ (int64_t)wp->w_height_inner,
+ (int64_t)wp->w_cursor.lnum) < 0) {
+ return FAIL;
+ }
+ // Restore the cursor column and left offset when not wrapping.
+ if (wp->w_cursor.col == 0) {
+ PUTLINE_FAIL("normal! 0");
+ } else {
+ if (!wp->w_p_wrap && wp->w_leftcol > 0 && wp->w_width > 0) {
+ if (fprintf(fd,
+ "let s:c = %" PRId64 " - ((%" PRId64
+ " * winwidth(0) + %" PRId64 ") / %" PRId64 ")\n"
+ "if s:c > 0\n"
+ " exe 'normal! ' . s:c . '|zs' . %" PRId64 " . '|'\n"
+ "else\n",
+ (int64_t)wp->w_virtcol + 1,
+ (int64_t)(wp->w_virtcol - wp->w_leftcol),
+ (int64_t)(wp->w_width / 2),
+ (int64_t)wp->w_width,
+ (int64_t)wp->w_virtcol + 1) < 0
+ || put_view_curpos(fd, wp, " ") == FAIL
+ || put_line(fd, "endif") == FAIL) {
+ return FAIL;
+ }
+ } else if (put_view_curpos(fd, wp, "") == FAIL) {
+ return FAIL;
+ }
+ }
+ }
+
+ //
+ // Local directory, if the current flag is not view options or the "curdir"
+ // option is included.
+ //
+ if (wp->w_localdir != NULL
+ && (flagp != &vop_flags || (*flagp & SSOP_CURDIR))) {
+ if (fputs("lcd ", fd) < 0
+ || ses_put_fname(fd, wp->w_localdir, flagp) == FAIL
+ || fprintf(fd, "\n") < 0) {
+ return FAIL;
+ }
+ did_lcd = true;
+ }
+
+ return OK;
+}
+
+/// Writes commands for restoring the current buffers, for :mksession.
+///
+/// Legacy 'sessionoptions' flags SSOP_UNIX, SSOP_SLASH are always enabled.
+///
+/// @param dirnow Current directory name
+/// @param fd File descriptor to write to
+///
+/// @return FAIL on error, OK otherwise.
+static int makeopens(FILE *fd, char_u *dirnow)
+{
+ int only_save_windows = true;
+ int nr;
+ int restore_size = true;
+ win_T *wp;
+ char_u *sname;
+ win_T *edited_win = NULL;
+ int tabnr;
+ win_T *tab_firstwin;
+ frame_T *tab_topframe;
+ int cur_arg_idx = 0;
+ int next_arg_idx = 0;
+
+ if (ssop_flags & SSOP_BUFFERS) {
+ only_save_windows = false; // Save ALL buffers
+ }
+
+ // Begin by setting v:this_session, and then other sessionable variables.
+ PUTLINE_FAIL("let v:this_session=expand(\"<sfile>:p\")");
+ if (ssop_flags & SSOP_GLOBALS) {
+ if (store_session_globals(fd) == FAIL) {
+ return FAIL;
+ }
+ }
+
+ // Close all windows but one.
+ PUTLINE_FAIL("silent only");
+
+ //
+ // Now a :cd command to the session directory or the current directory
+ //
+ if (ssop_flags & SSOP_SESDIR) {
+ PUTLINE_FAIL("exe \"cd \" . escape(expand(\"<sfile>:p:h\"), ' ')");
+ } else if (ssop_flags & SSOP_CURDIR) {
+ sname = home_replace_save(NULL, globaldir != NULL ? globaldir : dirnow);
+ char *fname_esc = ses_escape_fname((char *)sname, &ssop_flags);
+ if (fprintf(fd, "cd %s\n", fname_esc) < 0) {
+ xfree(fname_esc);
+ xfree(sname);
+ return FAIL;
+ }
+ xfree(fname_esc);
+ xfree(sname);
+ }
+
+ if (fprintf(fd,
+ "%s",
+ // If there is an empty, unnamed buffer we will wipe it out later.
+ // Remember the buffer number.
+ "if expand('%') == '' && !&modified && line('$') <= 1"
+ " && getline(1) == ''\n"
+ " let s:wipebuf = bufnr('%')\n"
+ "endif\n"
+ // Now save the current files, current buffer first.
+ "set shortmess=aoO\n") < 0) {
+ return FAIL;
+ }
+
+ // Now put the other buffers into the buffer list.
+ FOR_ALL_BUFFERS(buf) {
+ if (!(only_save_windows && buf->b_nwindows == 0)
+ && !(buf->b_help && !(ssop_flags & SSOP_HELP))
+ && buf->b_fname != NULL
+ && buf->b_p_bl) {
+ if (fprintf(fd, "badd +%" PRId64 " ",
+ buf->b_wininfo == NULL
+ ? (int64_t)1L
+ : (int64_t)buf->b_wininfo->wi_fpos.lnum) < 0
+ || ses_fname(fd, buf, &ssop_flags, true) == FAIL) {
+ return FAIL;
+ }
+ }
+ }
+
+ // the global argument list
+ if (ses_arglist(fd, "argglobal", &global_alist.al_ga,
+ !(ssop_flags & SSOP_CURDIR), &ssop_flags) == FAIL) {
+ return FAIL;
+ }
+
+ if (ssop_flags & SSOP_RESIZE) {
+ // Note: after the restore we still check it worked!
+ if (fprintf(fd, "set lines=%" PRId64 " columns=%" PRId64 "\n",
+ (int64_t)Rows, (int64_t)Columns) < 0) {
+ return FAIL;
+ }
+ }
+
+ int restore_stal = false;
+ // When there are two or more tabpages and 'showtabline' is 1 the tabline
+ // will be displayed when creating the next tab. That resizes the windows
+ // in the first tab, which may cause problems. Set 'showtabline' to 2
+ // temporarily to avoid that.
+ if (p_stal == 1 && first_tabpage->tp_next != NULL) {
+ PUTLINE_FAIL("set stal=2");
+ restore_stal = true;
+ }
+
+ //
+ // For each tab:
+ // - Put windows for each tab, when "tabpages" is in 'sessionoptions'.
+ // - Don't use goto_tabpage(), it may change CWD and trigger autocommands.
+ //
+ tab_firstwin = firstwin; // First window in tab page "tabnr".
+ tab_topframe = topframe;
+ for (tabnr = 1;; tabnr++) {
+ tabpage_T *tp = find_tabpage(tabnr);
+ if (tp == NULL) {
+ break; // done all tab pages
+ }
+
+ int need_tabnew = false;
+ int cnr = 1;
+
+ if ((ssop_flags & SSOP_TABPAGES)) {
+ if (tp == curtab) {
+ tab_firstwin = firstwin;
+ tab_topframe = topframe;
+ } else {
+ tab_firstwin = tp->tp_firstwin;
+ tab_topframe = tp->tp_topframe;
+ }
+ if (tabnr > 1) {
+ need_tabnew = true;
+ }
+ }
+
+ //
+ // Before creating the window layout, try loading one file. If this
+ // is aborted we don't end up with a number of useless windows.
+ // This may have side effects! (e.g., compressed or network file).
+ //
+ for (wp = tab_firstwin; wp != NULL; wp = wp->w_next) {
+ if (ses_do_win(wp)
+ && wp->w_buffer->b_ffname != NULL
+ && !bt_help(wp->w_buffer)
+ && !bt_nofile(wp->w_buffer)
+ ) {
+ if (fputs(need_tabnew ? "tabedit " : "edit ", fd) < 0
+ || ses_fname(fd, wp->w_buffer, &ssop_flags, true) == FAIL) {
+ return FAIL;
+ }
+ need_tabnew = false;
+ if (!wp->w_arg_idx_invalid) {
+ edited_win = wp;
+ }
+ break;
+ }
+ }
+
+ // If no file got edited create an empty tab page.
+ if (need_tabnew && put_line(fd, "tabnew") == FAIL) {
+ return FAIL;
+ }
+
+ //
+ // Save current window layout.
+ //
+ PUTLINE_FAIL("set splitbelow splitright");
+ if (ses_win_rec(fd, tab_topframe) == FAIL) {
+ return FAIL;
+ }
+ if (!p_sb && put_line(fd, "set nosplitbelow") == FAIL) {
+ return FAIL;
+ }
+ if (!p_spr && put_line(fd, "set nosplitright") == FAIL) {
+ return FAIL;
+ }
+
+ //
+ // Check if window sizes can be restored (no windows omitted).
+ // Remember the window number of the current window after restoring.
+ //
+ nr = 0;
+ for (wp = tab_firstwin; wp != NULL; wp = wp->w_next) {
+ if (ses_do_win(wp)) {
+ nr++;
+ } else {
+ restore_size = false;
+ }
+ if (curwin == wp) {
+ cnr = nr;
+ }
+ }
+
+ // Go to the first window.
+ PUTLINE_FAIL("wincmd t");
+
+ // If more than one window, see if sizes can be restored.
+ // First set 'winheight' and 'winwidth' to 1 to avoid the windows being
+ // resized when moving between windows.
+ // Do this before restoring the view, so that the topline and the
+ // cursor can be set. This is done again below.
+ // winminheight and winminwidth need to be set to avoid an error if the
+ // user has set winheight or winwidth.
+ if (fprintf(fd,
+ "set winminheight=0\n"
+ "set winheight=1\n"
+ "set winminwidth=0\n"
+ "set winwidth=1\n") < 0) {
+ return FAIL;
+ }
+ if (nr > 1 && ses_winsizes(fd, restore_size, tab_firstwin) == FAIL) {
+ return FAIL;
+ }
+
+ //
+ // Restore the view of the window (options, file, cursor, etc.).
+ //
+ for (wp = tab_firstwin; wp != NULL; wp = wp->w_next) {
+ if (!ses_do_win(wp)) {
+ continue;
+ }
+ if (put_view(fd, wp, wp != edited_win, &ssop_flags, cur_arg_idx)
+ == FAIL) {
+ return FAIL;
+ }
+ if (nr > 1 && put_line(fd, "wincmd w") == FAIL) {
+ return FAIL;
+ }
+ next_arg_idx = wp->w_arg_idx;
+ }
+
+ // The argument index in the first tab page is zero, need to set it in
+ // each window. For further tab pages it's the window where we do
+ // "tabedit".
+ cur_arg_idx = next_arg_idx;
+
+ //
+ // Restore cursor to the current window if it's not the first one.
+ //
+ if (cnr > 1 && (fprintf(fd, "%dwincmd w\n", cnr) < 0)) {
+ return FAIL;
+ }
+
+ //
+ // Restore window sizes again after jumping around in windows, because
+ // the current window has a minimum size while others may not.
+ //
+ if (nr > 1 && ses_winsizes(fd, restore_size, tab_firstwin) == FAIL) {
+ return FAIL;
+ }
+
+ // Take care of tab-local working directories if applicable
+ if (tp->tp_localdir) {
+ if (fputs("if exists(':tcd') == 2 | tcd ", fd) < 0
+ || ses_put_fname(fd, tp->tp_localdir, &ssop_flags) == FAIL
+ || fputs(" | endif\n", fd) < 0) {
+ return FAIL;
+ }
+ did_lcd = true;
+ }
+
+ // Don't continue in another tab page when doing only the current one
+ // or when at the last tab page.
+ if (!(ssop_flags & SSOP_TABPAGES)) {
+ break;
+ }
+ }
+
+ if (ssop_flags & SSOP_TABPAGES) {
+ if (fprintf(fd, "tabnext %d\n", tabpage_index(curtab)) < 0) {
+ return FAIL;
+ }
+ }
+ if (restore_stal && put_line(fd, "set stal=1") == FAIL) {
+ return FAIL;
+ }
+
+ //
+ // Wipe out an empty unnamed buffer we started in.
+ //
+ if (fprintf(fd, "%s",
+ "if exists('s:wipebuf') "
+ "&& getbufvar(s:wipebuf, '&buftype') isnot# 'terminal'\n"
+ " silent exe 'bwipe ' . s:wipebuf\n"
+ "endif\n"
+ "unlet! s:wipebuf\n") < 0) {
+ return FAIL;
+ }
+
+ // Re-apply options.
+ if (fprintf(fd,
+ "set winheight=%" PRId64 " winwidth=%" PRId64
+ " winminheight=%" PRId64 " winminwidth=%" PRId64
+ " shortmess=%s\n",
+ (int64_t)p_wh,
+ (int64_t)p_wiw,
+ (int64_t)p_wmh,
+ (int64_t)p_wmw,
+ p_shm) < 0) {
+ return FAIL;
+ }
+
+ //
+ // Lastly, execute the x.vim file if it exists.
+ //
+ if (fprintf(fd, "%s",
+ "let s:sx = expand(\"<sfile>:p:r\").\"x.vim\"\n"
+ "if filereadable(s:sx)\n"
+ " exe \"source \" . fnameescape(s:sx)\n"
+ "endif\n") < 0) {
+ return FAIL;
+ }
+
+ return OK;
+}
+
+/// ":loadview [nr]"
+void ex_loadview(exarg_T *eap)
+{
+ char *fname = get_view_file(*eap->arg);
+ if (fname != NULL) {
+ if (do_source((char_u *)fname, false, DOSO_NONE) == FAIL) {
+ EMSG2(_(e_notopen), fname);
+ }
+ xfree(fname);
+ }
+}
+
+/// ":mkexrc", ":mkvimrc", ":mkview", ":mksession".
+///
+/// Legacy 'sessionoptions' flags SSOP_UNIX, SSOP_SLASH are always enabled.
+/// - SSOP_UNIX: line-endings are always LF
+/// - SSOP_SLASH: filenames are always written with "/" slash
+void ex_mkrc(exarg_T *eap)
+{
+ FILE *fd;
+ int failed = false;
+ int view_session = false; // :mkview, :mksession
+ int using_vdir = false; // using 'viewdir'?
+ char *viewFile = NULL;
+ unsigned *flagp;
+
+ if (eap->cmdidx == CMD_mksession || eap->cmdidx == CMD_mkview) {
+ view_session = true;
+ }
+
+ // Use the short file name until ":lcd" is used. We also don't use the
+ // short file name when 'acd' is set, that is checked later.
+ did_lcd = false;
+
+ char *fname;
+ // ":mkview" or ":mkview 9": generate file name with 'viewdir'
+ if (eap->cmdidx == CMD_mkview
+ && (*eap->arg == NUL
+ || (ascii_isdigit(*eap->arg) && eap->arg[1] == NUL))) {
+ eap->forceit = true;
+ fname = get_view_file(*eap->arg);
+ if (fname == NULL) {
+ return;
+ }
+ viewFile = fname;
+ using_vdir = true;
+ } else if (*eap->arg != NUL) {
+ fname = (char *)eap->arg;
+ } else if (eap->cmdidx == CMD_mkvimrc) {
+ fname = VIMRC_FILE;
+ } else if (eap->cmdidx == CMD_mksession) {
+ fname = SESSION_FILE;
+ } else {
+ fname = EXRC_FILE;
+ }
+
+ // When using 'viewdir' may have to create the directory.
+ if (using_vdir && !os_isdir(p_vdir)) {
+ vim_mkdir_emsg((const char *)p_vdir, 0755);
+ }
+
+ fd = open_exfile((char_u *)fname, eap->forceit, WRITEBIN);
+ if (fd != NULL) {
+ if (eap->cmdidx == CMD_mkview) {
+ flagp = &vop_flags;
+ } else {
+ flagp = &ssop_flags;
+ }
+
+ // Write the version command for :mkvimrc
+ if (eap->cmdidx == CMD_mkvimrc) {
+ (void)put_line(fd, "version 6.0");
+ }
+
+ if (eap->cmdidx == CMD_mksession) {
+ if (put_line(fd, "let SessionLoad = 1") == FAIL) {
+ failed = true;
+ }
+ }
+
+ if (!view_session || (eap->cmdidx == CMD_mksession
+ && (*flagp & SSOP_OPTIONS))) {
+ failed |= (makemap(fd, NULL) == FAIL
+ || makeset(fd, OPT_GLOBAL, false) == FAIL);
+ }
+
+ if (!failed && view_session) {
+ if (put_line(fd,
+ "let s:so_save = &so | let s:siso_save = &siso"
+ " | set so=0 siso=0") == FAIL) {
+ failed = true;
+ }
+ if (eap->cmdidx == CMD_mksession) {
+ char_u *dirnow; // current directory
+
+ dirnow = xmalloc(MAXPATHL);
+ //
+ // Change to session file's dir.
+ //
+ if (os_dirname(dirnow, MAXPATHL) == FAIL
+ || os_chdir((char *)dirnow) != 0) {
+ *dirnow = NUL;
+ }
+ if (*dirnow != NUL && (ssop_flags & SSOP_SESDIR)) {
+ if (vim_chdirfile((char_u *)fname) == OK) {
+ shorten_fnames(true);
+ }
+ } else if (*dirnow != NUL
+ && (ssop_flags & SSOP_CURDIR) && globaldir != NULL) {
+ if (os_chdir((char *)globaldir) == 0) {
+ shorten_fnames(true);
+ }
+ }
+
+ failed |= (makeopens(fd, dirnow) == FAIL);
+
+ // restore original dir
+ if (*dirnow != NUL && ((ssop_flags & SSOP_SESDIR)
+ || ((ssop_flags & SSOP_CURDIR) && globaldir !=
+ NULL))) {
+ if (os_chdir((char *)dirnow) != 0) {
+ EMSG(_(e_prev_dir));
+ }
+ shorten_fnames(true);
+ // restore original dir
+ if (*dirnow != NUL && ((ssop_flags & SSOP_SESDIR)
+ || ((ssop_flags & SSOP_CURDIR) && globaldir !=
+ NULL))) {
+ if (os_chdir((char *)dirnow) != 0) {
+ EMSG(_(e_prev_dir));
+ }
+ shorten_fnames(true);
+ }
+ }
+ xfree(dirnow);
+ } else {
+ failed |= (put_view(fd, curwin, !using_vdir, flagp, -1) == FAIL);
+ }
+ if (fprintf(fd,
+ "%s",
+ "let &so = s:so_save | let &siso = s:siso_save\n"
+ "doautoall SessionLoadPost\n")
+ < 0) {
+ failed = true;
+ }
+ if (eap->cmdidx == CMD_mksession) {
+ if (fprintf(fd, "unlet SessionLoad\n") < 0) {
+ failed = true;
+ }
+ }
+ }
+ if (put_line(fd, "\" vim: set ft=vim :") == FAIL) {
+ failed = true;
+ }
+
+ failed |= fclose(fd);
+
+ if (failed) {
+ EMSG(_(e_write));
+ } else if (eap->cmdidx == CMD_mksession) {
+ // successful session write - set v:this_session
+ char *const tbuf = xmalloc(MAXPATHL);
+ if (vim_FullName(fname, tbuf, MAXPATHL, false) == OK) {
+ set_vim_var_string(VV_THIS_SESSION, tbuf, -1);
+ }
+ xfree(tbuf);
+ }
+ }
+
+ xfree(viewFile);
+}
+
+/// Get the name of the view file for the current buffer.
+static char *get_view_file(int c)
+{
+ if (curbuf->b_ffname == NULL) {
+ EMSG(_(e_noname));
+ return NULL;
+ }
+ char *sname = (char *)home_replace_save(NULL, curbuf->b_ffname);
+
+ // We want a file name without separators, because we're not going to make
+ // a directory.
+ // "normal" path separator -> "=+"
+ // "=" -> "=="
+ // ":" path separator -> "=-"
+ size_t len = 0;
+ for (char *p = sname; *p; p++) {
+ if (*p == '=' || vim_ispathsep(*p)) {
+ len++;
+ }
+ }
+ char *retval = xmalloc(strlen(sname) + len + STRLEN(p_vdir) + 9);
+ STRCPY(retval, p_vdir);
+ add_pathsep(retval);
+ char *s = retval + strlen(retval);
+ for (char *p = sname; *p; p++) {
+ if (*p == '=') {
+ *s++ = '=';
+ *s++ = '=';
+ } else if (vim_ispathsep(*p)) {
+ *s++ = '=';
+#if defined(BACKSLASH_IN_FILENAME)
+ *s++ = (*p == ':') ? '-' : '+';
+#else
+ *s++ = '+';
+#endif
+ } else {
+ *s++ = *p;
+ }
+ }
+ *s++ = '=';
+ assert(c >= CHAR_MIN && c <= CHAR_MAX);
+ *s++ = (char)c;
+ xstrlcpy(s, ".vim", 5);
+
+ xfree(sname);
+ return retval;
+}
+
+// TODO(justinmk): remove this, not needed after 5ba3cecb68cd.
+int put_eol(FILE *fd)
+{
+ if (putc('\n', fd) < 0) {
+ return FAIL;
+ }
+ return OK;
+}
+
+// TODO(justinmk): remove this, not needed after 5ba3cecb68cd.
+int put_line(FILE *fd, char *s)
+{
+ if (fprintf(fd, "%s\n", s) < 0) {
+ return FAIL;
+ }
+ return OK;
+}
diff --git a/src/nvim/ex_session.h b/src/nvim/ex_session.h
new file mode 100644
index 0000000000..8d3ea5b91a
--- /dev/null
+++ b/src/nvim/ex_session.h
@@ -0,0 +1,13 @@
+#ifndef NVIM_EX_SESSION_H
+#define NVIM_EX_SESSION_H
+
+#include <stdio.h>
+
+#include "nvim/ex_cmds_defs.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "ex_session.h.generated.h"
+#endif
+
+#endif // NVIM_EX_SESSION_H
+
diff --git a/src/nvim/extmark.c b/src/nvim/extmark.c
new file mode 100644
index 0000000000..1457a1172d
--- /dev/null
+++ b/src/nvim/extmark.c
@@ -0,0 +1,928 @@
+// 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. Each mark exists in a btree of
+// lines containing btrees of columns.
+//
+// The btree provides efficient range lookups.
+// A map of pointers to the marks is used for fast lookup by mark id.
+//
+// Marks are moved by calls to extmark_splice. Additionally mark_adjust
+// might adjust extmarks to line inserts/deletes.
+//
+// Undo/Redo of marks is implemented by storing the call arguments to
+// extmark_splice. The list of arguments is applied in extmark_apply_undo.
+// The only case where we have to copy extmarks is for the area being effected
+// by a delete.
+//
+// Marks live in namespaces that allow plugins/users to segregate marks
+// from other users.
+//
+// For possible ideas for efficency improvements see:
+// http://blog.atom.io/2015/06/16/optimizing-an-important-atom-primitive.html
+// TODO(bfredl): These ideas could be used for an enhanced btree, which
+// wouldn't need separate line and column layers.
+// Other implementations exist in gtk and tk toolkits.
+//
+// Deleting marks only happens when explicitly calling extmark_del, deleteing
+// over a range of marks will only move the marks. Deleting on a mark will
+// leave it in same position unless it is on the EOL of a line.
+
+#include <assert.h>
+#include "nvim/api/vim.h"
+#include "nvim/vim.h"
+#include "nvim/charset.h"
+#include "nvim/extmark.h"
+#include "nvim/buffer_updates.h"
+#include "nvim/memline.h"
+#include "nvim/pos.h"
+#include "nvim/globals.h"
+#include "nvim/map.h"
+#include "nvim/lib/kbtree.h"
+#include "nvim/undo.h"
+#include "nvim/buffer.h"
+#include "nvim/syntax.h"
+#include "nvim/highlight.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "extmark.c.generated.h"
+#endif
+
+static ExtmarkNs *buf_ns_ref(buf_T *buf, uint64_t ns_id, bool put) {
+ if (!buf->b_extmark_ns) {
+ if (!put) {
+ return NULL;
+ }
+ buf->b_extmark_ns = map_new(uint64_t, ExtmarkNs)();
+ buf->b_extmark_index = map_new(uint64_t, ExtmarkItem)();
+ }
+
+ ExtmarkNs *ns = map_ref(uint64_t, ExtmarkNs)(buf->b_extmark_ns, ns_id, put);
+ if (put && ns->map == NULL) {
+ ns->map = map_new(uint64_t, uint64_t)();
+ ns->free_id = 1;
+ }
+ return ns;
+}
+
+
+/// Create or update an extmark
+///
+/// must not be used during iteration!
+/// @returns the mark id
+uint64_t extmark_set(buf_T *buf, uint64_t ns_id, uint64_t id,
+ int row, colnr_T col, ExtmarkOp op)
+{
+ ExtmarkNs *ns = buf_ns_ref(buf, ns_id, true);
+ mtpos_t old_pos;
+ uint64_t mark = 0;
+
+ if (id == 0) {
+ id = ns->free_id++;
+ } else {
+ uint64_t old_mark = map_get(uint64_t, uint64_t)(ns->map, id);
+ if (old_mark) {
+ if (old_mark & MARKTREE_PAIRED_FLAG) {
+ extmark_del(buf, ns_id, id);
+ } else {
+ // TODO(bfredl): we need to do more if "revising" a decoration mark.
+ MarkTreeIter itr[1] = { 0 };
+ old_pos = marktree_lookup(buf->b_marktree, old_mark, itr);
+ assert(itr->node);
+ if (old_pos.row == row && old_pos.col == col) {
+ map_del(uint64_t, ExtmarkItem)(buf->b_extmark_index, old_mark);
+ mark = marktree_revise(buf->b_marktree, itr);
+ goto revised;
+ }
+ marktree_del_itr(buf->b_marktree, itr, false);
+ }
+ } else {
+ ns->free_id = MAX(ns->free_id, id+1);
+ }
+ }
+
+ mark = marktree_put(buf->b_marktree, row, col, true);
+revised:
+ map_put(uint64_t, ExtmarkItem)(buf->b_extmark_index, mark,
+ (ExtmarkItem){ ns_id, id, 0,
+ KV_INITIAL_VALUE });
+ map_put(uint64_t, uint64_t)(ns->map, id, mark);
+
+ 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.
+ u_extmark_set(buf, mark, row, col);
+ }
+ return id;
+}
+
+static bool extmark_setraw(buf_T *buf, uint64_t mark, int row, colnr_T col)
+{
+ MarkTreeIter itr[1] = { 0 };
+ mtpos_t pos = marktree_lookup(buf->b_marktree, mark, itr);
+ if (pos.row == -1) {
+ return false;
+ }
+
+ if (pos.row == row && pos.col == col) {
+ return true;
+ }
+
+ marktree_move(buf->b_marktree, itr, row, col);
+ return true;
+}
+
+// Remove an extmark
+// Returns 0 on missing id
+bool extmark_del(buf_T *buf, uint64_t ns_id, uint64_t id)
+{
+ ExtmarkNs *ns = buf_ns_ref(buf, ns_id, false);
+ if (!ns) {
+ return false;
+ }
+
+ uint64_t mark = map_get(uint64_t, uint64_t)(ns->map, id);
+ if (!mark) {
+ return false;
+ }
+
+ MarkTreeIter itr[1] = { 0 };
+ mtpos_t pos = marktree_lookup(buf->b_marktree, mark, itr);
+ assert(pos.row >= 0);
+ marktree_del_itr(buf->b_marktree, itr, false);
+ ExtmarkItem item = map_get(uint64_t, ExtmarkItem)(buf->b_extmark_index, mark);
+
+ if (mark & MARKTREE_PAIRED_FLAG) {
+ mtpos_t pos2 = marktree_lookup(buf->b_marktree,
+ mark|MARKTREE_END_FLAG, itr);
+ assert(pos2.row >= 0);
+ marktree_del_itr(buf->b_marktree, itr, false);
+ if (item.hl_id && pos2.row >= pos.row) {
+ redraw_buf_range_later(buf, pos.row+1, pos2.row+1);
+ }
+ }
+
+ if (kv_size(item.virt_text)) {
+ redraw_buf_line_later(buf, pos.row+1);
+ }
+ clear_virttext(&item.virt_text);
+
+ map_del(uint64_t, uint64_t)(ns->map, id);
+ map_del(uint64_t, ExtmarkItem)(buf->b_extmark_index, mark);
+
+ // TODO(bfredl): delete it from current undo header, opportunistically?
+
+ return true;
+}
+
+// Free extmarks in a ns between lines
+// if ns = 0, it means clear all namespaces
+bool extmark_clear(buf_T *buf, uint64_t ns_id,
+ int l_row, colnr_T l_col,
+ int u_row, colnr_T u_col)
+{
+ if (!buf->b_extmark_ns) {
+ return false;
+ }
+
+ bool marks_cleared = false;
+
+ bool all_ns = (ns_id == 0);
+ ExtmarkNs *ns = NULL;
+ if (!all_ns) {
+ ns = buf_ns_ref(buf, ns_id, false);
+ if (!ns) {
+ // nothing to do
+ return false;
+ }
+
+ // TODO(bfredl): if map_size(ns->map) << buf->b_marktree.n_nodes
+ // it could be faster to iterate over the map instead
+ }
+
+ // the value is either zero or the lnum (row+1) if highlight was present.
+ static Map(uint64_t, uint64_t) *delete_set = NULL;
+ if (delete_set == NULL) {
+ delete_set = map_new(uint64_t, uint64_t)();
+ }
+
+ MarkTreeIter itr[1] = { 0 };
+ marktree_itr_get(buf->b_marktree, l_row, l_col, itr);
+ while (true) {
+ mtmark_t mark = marktree_itr_current(itr);
+ if (mark.row < 0
+ || mark.row > u_row
+ || (mark.row == u_row && mark.col > u_col)) {
+ break;
+ }
+ uint64_t *del_status = map_ref(uint64_t, uint64_t)(delete_set, mark.id,
+ false);
+ if (del_status) {
+ marktree_del_itr(buf->b_marktree, itr, false);
+ map_del(uint64_t, uint64_t)(delete_set, mark.id);
+ if (*del_status > 0) {
+ redraw_buf_range_later(buf, (linenr_T)(*del_status), mark.row+1);
+ }
+ continue;
+ }
+
+ uint64_t start_id = mark.id & ~MARKTREE_END_FLAG;
+ ExtmarkItem item = map_get(uint64_t, ExtmarkItem)(buf->b_extmark_index,
+ start_id);
+
+ assert(item.ns_id > 0 && item.mark_id > 0);
+ if (item.mark_id > 0 && (item.ns_id == ns_id || all_ns)) {
+ if (kv_size(item.virt_text)) {
+ redraw_buf_line_later(buf, mark.row+1);
+ }
+ clear_virttext(&item.virt_text);
+ marks_cleared = true;
+ if (mark.id & MARKTREE_PAIRED_FLAG) {
+ uint64_t other = mark.id ^ MARKTREE_END_FLAG;
+ uint64_t status = item.hl_id ? ((uint64_t)mark.row+1) : 0;
+ map_put(uint64_t, uint64_t)(delete_set, other, status);
+ }
+ ExtmarkNs *my_ns = all_ns ? buf_ns_ref(buf, item.ns_id, false) : ns;
+ map_del(uint64_t, uint64_t)(my_ns->map, item.mark_id);
+ map_del(uint64_t, ExtmarkItem)(buf->b_extmark_index, mark.id);
+ marktree_del_itr(buf->b_marktree, itr, false);
+ } else {
+ marktree_itr_next(buf->b_marktree, itr);
+ }
+ }
+ uint64_t id, status;
+ map_foreach(delete_set, id, status, {
+ mtpos_t pos = marktree_lookup(buf->b_marktree, id, itr);
+ assert(itr->node);
+ marktree_del_itr(buf->b_marktree, itr, false);
+ if (status > 0) {
+ redraw_buf_range_later(buf, (linenr_T)status, pos.row+1);
+ }
+ });
+ map_clear(uint64_t, uint64_t)(delete_set);
+ return marks_cleared;
+}
+
+// Returns the position of marks between a range,
+// marks found at the start or end index will be included,
+// 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
+ExtmarkArray extmark_get(buf_T *buf, uint64_t ns_id,
+ int l_row, colnr_T l_col,
+ int u_row, colnr_T u_col,
+ int64_t amount, bool reverse)
+{
+ ExtmarkArray array = KV_INITIAL_VALUE;
+ MarkTreeIter itr[1] = { 0 };
+ // Find all the marks
+ marktree_itr_get_ext(buf->b_marktree, (mtpos_t){ l_row, l_col },
+ itr, reverse, false, NULL);
+ int order = reverse ? -1 : 1;
+ while ((int64_t)kv_size(array) < amount) {
+ mtmark_t mark = marktree_itr_current(itr);
+ if (mark.row < 0
+ || (mark.row - u_row) * order > 0
+ || (mark.row == u_row && (mark.col - u_col) * order > 0)) {
+ break;
+ }
+ ExtmarkItem item = map_get(uint64_t, ExtmarkItem)(buf->b_extmark_index,
+ mark.id);
+ if (item.ns_id == ns_id) {
+ kv_push(array, ((ExtmarkInfo) { .ns_id = item.ns_id,
+ .mark_id = item.mark_id,
+ .row = mark.row, .col = mark.col }));
+ }
+ if (reverse) {
+ marktree_itr_prev(buf->b_marktree, itr);
+ } else {
+ marktree_itr_next(buf->b_marktree, itr);
+ }
+ }
+ return array;
+}
+
+// Lookup an extmark by id
+ExtmarkInfo extmark_from_id(buf_T *buf, uint64_t ns_id, uint64_t id)
+{
+ ExtmarkNs *ns = buf_ns_ref(buf, ns_id, false);
+ ExtmarkInfo ret = { 0, 0, -1, -1 };
+ if (!ns) {
+ return ret;
+ }
+
+ uint64_t mark = map_get(uint64_t, uint64_t)(ns->map, id);
+ if (!mark) {
+ return ret;
+ }
+
+ mtpos_t pos = marktree_lookup(buf->b_marktree, mark, NULL);
+ assert(pos.row >= 0);
+
+ ret.ns_id = ns_id;
+ ret.mark_id = id;
+ ret.row = pos.row;
+ ret.col = pos.col;
+
+ return ret;
+}
+
+
+// free extmarks from the buffer
+void extmark_free_all(buf_T *buf)
+{
+ if (!buf->b_extmark_ns) {
+ return;
+ }
+
+ uint64_t id;
+ ExtmarkNs ns;
+ ExtmarkItem item;
+
+ marktree_clear(buf->b_marktree);
+
+ map_foreach(buf->b_extmark_ns, id, ns, {
+ (void)id;
+ map_free(uint64_t, uint64_t)(ns.map);
+ });
+ map_free(uint64_t, ExtmarkNs)(buf->b_extmark_ns);
+ buf->b_extmark_ns = NULL;
+
+ map_foreach(buf->b_extmark_index, id, item, {
+ (void)id;
+ clear_virttext(&item.virt_text);
+ });
+ map_free(uint64_t, ExtmarkItem)(buf->b_extmark_index);
+ buf->b_extmark_index = NULL;
+}
+
+
+// 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
+///
+/// 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)
+{
+ u_header_T *uhp = u_force_get_undo_header(buf);
+ if (!uhp) {
+ return;
+ }
+
+ ExtmarkUndoObject undo;
+
+ MarkTreeIter itr[1] = { 0 };
+ marktree_itr_get(buf->b_marktree, l_row, l_col, itr);
+ while (true) {
+ mtmark_t mark = marktree_itr_current(itr);
+ if (mark.row < 0
+ || mark.row > u_row
+ || (mark.row == u_row && mark.col > u_col)) {
+ break;
+ }
+ ExtmarkSavePos pos;
+ pos.mark = mark.id;
+ pos.old_row = mark.row;
+ pos.old_col = mark.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);
+ }
+}
+
+/// undo or redo an extmark operation
+void extmark_apply_undo(ExtmarkUndoObject undo_info, bool undo)
+{
+ // splice: any text operation changing position (except :move)
+ if (undo_info.type == kExtmarkSplice) {
+ // Undo
+ ExtmarkSplice splice = undo_info.data.splice;
+ if (undo) {
+ extmark_splice(curbuf,
+ splice.start_row, splice.start_col,
+ splice.newextent_row, splice.newextent_col,
+ splice.oldextent_row, splice.oldextent_col,
+ kExtmarkNoUndo);
+
+ } else {
+ extmark_splice(curbuf,
+ splice.start_row, splice.start_col,
+ splice.oldextent_row, splice.oldextent_col,
+ splice.newextent_row, splice.newextent_col,
+ kExtmarkNoUndo);
+ }
+ // kExtmarkSavePos
+ } else if (undo_info.type == kExtmarkSavePos) {
+ ExtmarkSavePos pos = undo_info.data.savepos;
+ if (undo) {
+ if (pos.old_row >= 0) {
+ extmark_setraw(curbuf, pos.mark, pos.old_row, pos.old_col);
+ }
+ // Redo
+ } else {
+ if (pos.row >= 0) {
+ extmark_setraw(curbuf, pos.mark, pos.row, pos.col);
+ }
+ }
+ } else if (undo_info.type == kExtmarkMove) {
+ ExtmarkMove move = undo_info.data.move;
+ if (undo) {
+ extmark_move_region(curbuf,
+ move.new_row, move.new_col,
+ move.extent_row, move.extent_col,
+ move.start_row, move.start_col,
+ kExtmarkNoUndo);
+ } else {
+ extmark_move_region(curbuf,
+ move.start_row, move.start_col,
+ move.extent_row, move.extent_col,
+ move.new_row, move.new_col,
+ kExtmarkNoUndo);
+ }
+ }
+}
+
+
+// Adjust extmark row for inserted/deleted rows (columns stay fixed).
+void extmark_adjust(buf_T *buf,
+ linenr_T line1,
+ linenr_T line2,
+ long amount,
+ long amount_after,
+ ExtmarkOp undo)
+{
+ if (!curbuf_splice_pending) {
+ int old_extent, new_extent;
+ if (amount == MAXLNUM) {
+ old_extent = (int)(line2 - line1+1);
+ new_extent = (int)(amount_after + old_extent);
+ } else {
+ // A region is either deleted (amount == MAXLNUM) or
+ // added (line2 == MAXLNUM). The only other case is :move
+ // which is handled by a separate entry point extmark_move_region.
+ assert(line2 == MAXLNUM);
+ old_extent = 0;
+ new_extent = (int)amount;
+ }
+ extmark_splice(buf,
+ (int)line1-1, 0,
+ old_extent, 0,
+ new_extent, 0, undo);
+ }
+}
+
+void extmark_splice(buf_T *buf,
+ int start_row, colnr_T start_col,
+ int oldextent_row, colnr_T oldextent_col,
+ int newextent_row, colnr_T newextent_col,
+ ExtmarkOp undo)
+{
+ buf_updates_send_splice(buf, start_row, start_col,
+ oldextent_row, oldextent_col,
+ newextent_row, newextent_col);
+
+ if (undo == kExtmarkUndo && (oldextent_row > 0 || oldextent_col > 0)) {
+ // Copy 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 + oldextent_row;
+ int end_col = (oldextent_row ? 0 : start_col) + oldextent_col;
+ u_extmark_copy(buf, start_row, start_col, end_row, end_col);
+ }
+
+
+ marktree_splice(buf->b_marktree, start_row, start_col,
+ oldextent_row, oldextent_col,
+ newextent_row, newextent_col);
+
+ if (undo == kExtmarkUndo) {
+ u_header_T *uhp = u_force_get_undo_header(buf);
+ if (!uhp) {
+ return;
+ }
+
+ bool merged = false;
+ // TODO(bfredl): this is quite rudimentary. We merge small (within line)
+ // inserts with each other and small deletes with each other. Add full
+ // merge algorithm later.
+ if (oldextent_row == 0 && newextent_row == 0 && kv_size(uhp->uh_extmark)) {
+ ExtmarkUndoObject *item = &kv_A(uhp->uh_extmark,
+ kv_size(uhp->uh_extmark)-1);
+ if (item->type == kExtmarkSplice) {
+ ExtmarkSplice *splice = &item->data.splice;
+ if (splice->start_row == start_row && splice->oldextent_row == 0
+ && splice->newextent_row == 0) {
+ if (oldextent_col == 0 && start_col >= splice->start_col
+ && start_col <= splice->start_col+splice->newextent_col) {
+ splice->newextent_col += newextent_col;
+ merged = true;
+ } else if (newextent_col == 0
+ && start_col == splice->start_col+splice->newextent_col) {
+ splice->oldextent_col += oldextent_col;
+ merged = true;
+ } else if (newextent_col == 0
+ && start_col + oldextent_col == splice->start_col) {
+ splice->start_col = start_col;
+ splice->oldextent_col += oldextent_col;
+ merged = true;
+ }
+ }
+ }
+ }
+
+ if (!merged) {
+ ExtmarkSplice splice;
+ splice.start_row = start_row;
+ splice.start_col = start_col;
+ splice.oldextent_row = oldextent_row;
+ splice.oldextent_col = oldextent_col;
+ splice.newextent_row = newextent_row;
+ splice.newextent_col = newextent_col;
+
+ kv_push(uhp->uh_extmark,
+ ((ExtmarkUndoObject){ .type = kExtmarkSplice,
+ .data.splice = splice }));
+ }
+ }
+}
+
+void extmark_splice_cols(buf_T *buf,
+ int start_row, colnr_T start_col,
+ colnr_T old_col, colnr_T new_col,
+ ExtmarkOp undo)
+{
+ extmark_splice(buf, start_row, start_col,
+ 0, old_col,
+ 0, new_col, undo);
+}
+
+void extmark_move_region(buf_T *buf,
+ int start_row, colnr_T start_col,
+ int extent_row, colnr_T extent_col,
+ int new_row, colnr_T new_col,
+ ExtmarkOp undo)
+{
+ // TODO(bfredl): this is not synced to the buffer state inside the callback.
+ // But unless we make the undo implementation smarter, this is not ensured
+ // anyway.
+ buf_updates_send_splice(buf, start_row, start_col,
+ extent_row, extent_col,
+ 0, 0);
+
+ marktree_move_region(buf->b_marktree, start_row, start_col,
+ extent_row, extent_col,
+ new_row, new_col);
+
+ buf_updates_send_splice(buf, new_row, new_col,
+ 0, 0,
+ extent_row, extent_col);
+
+
+ if (undo == kExtmarkUndo) {
+ u_header_T *uhp = u_force_get_undo_header(buf);
+ if (!uhp) {
+ return;
+ }
+
+ ExtmarkMove move;
+ move.start_row = start_row;
+ move.start_col = start_col;
+ move.extent_row = extent_row;
+ move.extent_col = extent_col;
+ move.new_row = new_row;
+ move.new_col = new_col;
+
+ kv_push(uhp->uh_extmark,
+ ((ExtmarkUndoObject){ .type = kExtmarkMove,
+ .data.move = move }));
+ }
+}
+
+uint64_t src2ns(Integer *src_id)
+{
+ if (*src_id == 0) {
+ *src_id = (Integer)nvim_create_namespace((String)STRING_INIT);
+ }
+ if (*src_id < 0) {
+ return UINT64_MAX;
+ } else {
+ return (uint64_t)(*src_id);
+ }
+}
+
+/// Adds a decoration to a buffer.
+///
+/// Unlike matchaddpos() highlights, these follow changes to the the buffer
+/// texts. Decorations are represented internally and in the API as extmarks.
+///
+/// @param buf The buffer to add decorations to
+/// @param ns_id A valid namespace id.
+/// @param hl_id Id of the highlight group to use (or zero)
+/// @param start_row The line to highlight
+/// @param start_col First column to highlight
+/// @param end_row The line to highlight
+/// @param end_col The last column to highlight
+/// @param virt_text Virtual text (currently placed at the EOL of start_row)
+/// @return The extmark id inside the namespace
+uint64_t extmark_add_decoration(buf_T *buf, uint64_t ns_id, int hl_id,
+ int start_row, colnr_T start_col,
+ int end_row, colnr_T end_col,
+ VirtText virt_text)
+{
+ ExtmarkNs *ns = buf_ns_ref(buf, ns_id, true);
+ ExtmarkItem item;
+ item.ns_id = ns_id;
+ item.mark_id = ns->free_id++;
+ item.hl_id = hl_id;
+ item.virt_text = virt_text;
+
+ uint64_t mark;
+
+ if (end_row > -1) {
+ mark = marktree_put_pair(buf->b_marktree,
+ start_row, start_col, true,
+ end_row, end_col, false);
+ } else {
+ mark = marktree_put(buf->b_marktree, start_row, start_col, true);
+ }
+
+ map_put(uint64_t, ExtmarkItem)(buf->b_extmark_index, mark, item);
+ map_put(uint64_t, uint64_t)(ns->map, item.mark_id, mark);
+
+ redraw_buf_range_later(buf, start_row+1,
+ (end_row >= 0 ? end_row : start_row) + 1);
+ return item.mark_id;
+}
+
+/// Add highlighting to a buffer, bounded by two cursor positions,
+/// with an offset.
+///
+/// @param buf Buffer to add highlights to
+/// @param src_id src_id to use or 0 to use a new src_id group,
+/// or -1 for ungrouped highlight.
+/// @param hl_id Highlight group id
+/// @param pos_start Cursor position to start the hightlighting at
+/// @param pos_end Cursor position to end the highlighting at
+/// @param offset Move the whole highlighting this many columns to the right
+void bufhl_add_hl_pos_offset(buf_T *buf,
+ int src_id,
+ int hl_id,
+ lpos_T pos_start,
+ lpos_T pos_end,
+ colnr_T offset)
+{
+ colnr_T hl_start = 0;
+ colnr_T hl_end = 0;
+
+ // TODO(bfredl): if decoration had blocky mode, we could avoid this loop
+ for (linenr_T lnum = pos_start.lnum; lnum <= pos_end.lnum; lnum ++) {
+ int end_off = 0;
+ if (pos_start.lnum < lnum && lnum < pos_end.lnum) {
+ // TODO(bfredl): This is quite ad-hoc, but the space between |num| and
+ // text being highlighted is the indication of \n being part of the
+ // substituted text. But it would be more consistent to highlight
+ // a space _after_ the previous line instead (like highlight EOL list
+ // char)
+ hl_start = MAX(offset-1, 0);
+ end_off = 1;
+ hl_end = 0;
+ } else if (lnum == pos_start.lnum && lnum < pos_end.lnum) {
+ hl_start = pos_start.col + offset;
+ end_off = 1;
+ hl_end = 0;
+ } else if (pos_start.lnum < lnum && lnum == pos_end.lnum) {
+ hl_start = MAX(offset-1, 0);
+ hl_end = pos_end.col + offset;
+ } else if (pos_start.lnum == lnum && pos_end.lnum == lnum) {
+ hl_start = pos_start.col + offset;
+ hl_end = pos_end.col + offset;
+ }
+ (void)extmark_add_decoration(buf, (uint64_t)src_id, hl_id,
+ (int)lnum-1, hl_start,
+ (int)lnum-1+end_off, hl_end,
+ VIRTTEXT_EMPTY);
+ }
+}
+
+void clear_virttext(VirtText *text)
+{
+ for (size_t i = 0; i < kv_size(*text); i++) {
+ xfree(kv_A(*text, i).text);
+ }
+ kv_destroy(*text);
+ *text = (VirtText)KV_INITIAL_VALUE;
+}
+
+VirtText *extmark_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) {
+ mtmark_t mark = marktree_itr_current(itr);
+ if (mark.row < 0 || mark.row > row) {
+ break;
+ }
+ ExtmarkItem *item = map_ref(uint64_t, ExtmarkItem)(buf->b_extmark_index,
+ mark.id, false);
+ if (item && (ns_id == 0 || ns_id == item->ns_id)
+ && kv_size(item->virt_text)) {
+ return &item->virt_text;
+ }
+ marktree_itr_next(buf->b_marktree, itr);
+ }
+ return NULL;
+}
+
+bool decorations_redraw_reset(buf_T *buf, DecorationRedrawState *state)
+{
+ state->row = -1;
+ kv_size(state->active) = 0;
+ return buf->b_extmark_index || buf->b_luahl;
+}
+
+
+bool decorations_redraw_start(buf_T *buf, int top_row,
+ DecorationRedrawState *state)
+{
+ state->top_row = top_row;
+ marktree_itr_get(buf->b_marktree, top_row, 0, state->itr);
+ if (!state->itr->node) {
+ return false;
+ }
+ marktree_itr_rewind(buf->b_marktree, state->itr);
+ while (true) {
+ mtmark_t mark = marktree_itr_current(state->itr);
+ if (mark.row < 0) { // || mark.row > end_row
+ break;
+ }
+ // TODO(bfredl): dedicated flag for being a decoration?
+ if ((mark.row < top_row && mark.id&MARKTREE_END_FLAG)) {
+ goto next_mark;
+ }
+ mtpos_t altpos = marktree_lookup(buf->b_marktree,
+ mark.id^MARKTREE_END_FLAG, NULL);
+
+ uint64_t start_id = mark.id & ~MARKTREE_END_FLAG;
+ ExtmarkItem *item = map_ref(uint64_t, ExtmarkItem)(buf->b_extmark_index,
+ start_id, false);
+ if ((!(mark.id&MARKTREE_END_FLAG) && altpos.row < top_row
+ && item && !kv_size(item->virt_text))
+ || ((mark.id&MARKTREE_END_FLAG) && altpos.row >= top_row)) {
+ goto next_mark;
+ }
+
+ if (item && (item->hl_id > 0 || kv_size(item->virt_text))) {
+ int attr_id = item->hl_id > 0 ? syn_id2attr(item->hl_id) : 0;
+ VirtText *vt = kv_size(item->virt_text) ? &item->virt_text : NULL;
+ HlRange range;
+ if (mark.id&MARKTREE_END_FLAG) {
+ range = (HlRange){ altpos.row, altpos.col, mark.row, mark.col,
+ attr_id, vt };
+ } else {
+ range = (HlRange){ mark.row, mark.col, altpos.row,
+ altpos.col, attr_id, vt };
+ }
+ kv_push(state->active, range);
+ }
+next_mark:
+ if (marktree_itr_node_done(state->itr)) {
+ break;
+ }
+ marktree_itr_next(buf->b_marktree, state->itr);
+ }
+
+ return true; // TODO(bfredl): check if available in the region
+}
+
+bool decorations_redraw_line(buf_T *buf, int row, DecorationRedrawState *state)
+{
+ if (state->row == -1) {
+ decorations_redraw_start(buf, row, state);
+ }
+ state->row = row;
+ state->col_until = -1;
+ return true; // TODO(bfredl): be more precise
+}
+
+int decorations_redraw_col(buf_T *buf, int col, DecorationRedrawState *state)
+{
+ if (col <= state->col_until) {
+ return state->current;
+ }
+ state->col_until = MAXCOL;
+ while (true) {
+ mtmark_t mark = marktree_itr_current(state->itr);
+ if (mark.row < 0 || mark.row > state->row) {
+ break;
+ } else if (mark.row == state->row && mark.col > col) {
+ state->col_until = mark.col-1;
+ break;
+ }
+
+ if ((mark.id&MARKTREE_END_FLAG)) {
+ // TODO(bfredl): check decorations flag
+ goto next_mark;
+ }
+ mtpos_t endpos = marktree_lookup(buf->b_marktree,
+ mark.id|MARKTREE_END_FLAG, NULL);
+
+ ExtmarkItem *item = map_ref(uint64_t, ExtmarkItem)(buf->b_extmark_index,
+ mark.id, false);
+
+ if (endpos.row < mark.row
+ || (endpos.row == mark.row && endpos.col <= mark.col)) {
+ if (item && !kv_size(item->virt_text)) {
+ goto next_mark;
+ }
+ }
+
+ if (item && (item->hl_id > 0 || kv_size(item->virt_text))) {
+ int attr_id = item->hl_id > 0 ? syn_id2attr(item->hl_id) : 0;
+ VirtText *vt = kv_size(item->virt_text) ? &item->virt_text : NULL;
+ kv_push(state->active, ((HlRange){ mark.row, mark.col,
+ endpos.row, endpos.col,
+ attr_id, vt }));
+ }
+
+next_mark:
+ marktree_itr_next(buf->b_marktree, state->itr);
+ }
+
+ int attr = 0;
+ size_t j = 0;
+ for (size_t i = 0; i < kv_size(state->active); i++) {
+ HlRange item = kv_A(state->active, i);
+ 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 && item.virt_text)) {
+ keep = false;
+ }
+ } else {
+ if (item.start_row < state->row
+ || (item.start_row == state->row && item.start_col <= col)) {
+ active = true;
+ if (item.end_row == state->row) {
+ state->col_until = MIN(state->col_until, item.end_col-1);
+ }
+ } else {
+ if (item.start_row == state->row) {
+ state->col_until = MIN(state->col_until, item.start_col-1);
+ }
+ }
+ }
+ if (active && item.attr_id > 0) {
+ attr = hl_combine_attr(attr, item.attr_id);
+ }
+ if (keep) {
+ kv_A(state->active, j++) = kv_A(state->active, i);
+ }
+ }
+ kv_size(state->active) = j;
+ state->current = attr;
+ return attr;
+}
+
+VirtText *decorations_redraw_virt_text(buf_T *buf, DecorationRedrawState *state)
+{
+ decorations_redraw_col(buf, MAXCOL, state);
+ for (size_t i = 0; i < kv_size(state->active); i++) {
+ HlRange item = kv_A(state->active, i);
+ if (item.start_row == state->row && item.virt_text) {
+ return item.virt_text;
+ }
+ }
+ return NULL;
+}
diff --git a/src/nvim/extmark.h b/src/nvim/extmark.h
new file mode 100644
index 0000000000..b5eb0db3b6
--- /dev/null
+++ b/src/nvim/extmark.h
@@ -0,0 +1,93 @@
+#ifndef NVIM_EXTMARK_H
+#define NVIM_EXTMARK_H
+
+#include "nvim/buffer_defs.h"
+#include "nvim/extmark_defs.h"
+#include "nvim/marktree.h"
+
+EXTERN int extmark_splice_pending INIT(= 0);
+
+typedef struct
+{
+ uint64_t ns_id;
+ uint64_t mark_id;
+ int row;
+ colnr_T col;
+} ExtmarkInfo;
+
+typedef kvec_t(ExtmarkInfo) ExtmarkArray;
+
+
+// delete the columns between mincol and endcol
+typedef struct {
+ int start_row;
+ colnr_T start_col;
+ int oldextent_row;
+ colnr_T oldextent_col;
+ int newextent_row;
+ colnr_T newextent_col;
+} ExtmarkSplice;
+
+// adjust marks after :move operation
+typedef struct {
+ int start_row;
+ int start_col;
+ int extent_row;
+ int extent_col;
+ int new_row;
+ int new_col;
+} ExtmarkMove;
+
+// extmark was updated
+typedef struct {
+ uint64_t mark; // raw mark id of the marktree
+ int old_row;
+ colnr_T old_col;
+ int row;
+ colnr_T col;
+} ExtmarkSavePos;
+
+typedef enum {
+ kExtmarkSplice,
+ kExtmarkMove,
+ kExtmarkUpdate,
+ kExtmarkSavePos,
+ kExtmarkClear,
+} UndoObjectType;
+
+// TODO(bfredl): reduce the number of undo action types
+struct undo_object {
+ UndoObjectType type;
+ union {
+ ExtmarkSplice splice;
+ ExtmarkMove move;
+ ExtmarkSavePos savepos;
+ } data;
+};
+
+
+typedef struct {
+ int start_row;
+ int start_col;
+ int end_row;
+ int end_col;
+ int attr_id;
+ VirtText *virt_text;
+} HlRange;
+
+typedef struct {
+ MarkTreeIter itr[1];
+ kvec_t(HlRange) active;
+ int top_row;
+ int row;
+ int col_until;
+ int current;
+ VirtText *virt_text;
+} DecorationRedrawState;
+
+
+#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
new file mode 100644
index 0000000000..c927048981
--- /dev/null
+++ b/src/nvim/extmark_defs.h
@@ -0,0 +1,37 @@
+#ifndef NVIM_EXTMARK_DEFS_H
+#define NVIM_EXTMARK_DEFS_H
+
+#include "nvim/pos.h" // for colnr_T
+#include "nvim/lib/kvec.h"
+
+typedef struct {
+ char *text;
+ int hl_id;
+} VirtTextChunk;
+
+typedef kvec_t(VirtTextChunk) VirtText;
+#define VIRTTEXT_EMPTY ((VirtText)KV_INITIAL_VALUE)
+
+typedef struct
+{
+ uint64_t ns_id;
+ uint64_t mark_id;
+ int hl_id; // highlight group
+ // TODO(bfredl): virt_text is pretty larger than the rest,
+ // pointer indirection?
+ VirtText virt_text;
+} ExtmarkItem;
+
+typedef struct undo_object ExtmarkUndoObject;
+typedef kvec_t(ExtmarkUndoObject) extmark_undo_vec_t;
+
+// Undo/redo extmarks
+
+typedef enum {
+ kExtmarkNOOP, // Extmarks shouldn't be moved
+ kExtmarkUndo, // Operation should be reversable/undoable
+ kExtmarkNoUndo, // Operation should not be reversable
+ kExtmarkUndoNoRedo, // Operation should be undoable, but not redoable
+} ExtmarkOp;
+
+#endif // NVIM_EXTMARK_DEFS_H
diff --git a/src/nvim/file_search.c b/src/nvim/file_search.c
index ad6a481bc5..47272df2f0 100644
--- a/src/nvim/file_search.c
+++ b/src/nvim/file_search.c
@@ -1102,8 +1102,8 @@ static bool ff_wc_equal(char_u *s1, char_u *s2)
prev2 = prev1;
prev1 = c1;
- i += MB_PTR2LEN(s1 + i);
- j += MB_PTR2LEN(s2 + j);
+ i += utfc_ptr2len(s1 + i);
+ j += utfc_ptr2len(s2 + j);
}
return s1[i] == s2[j];
}
diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c
index 4cf42b41e9..f922591d0b 100644
--- a/src/nvim/fileio.c
+++ b/src/nvim/fileio.c
@@ -20,7 +20,7 @@
#include "nvim/cursor.h"
#include "nvim/diff.h"
#include "nvim/edit.h"
-#include "nvim/eval.h"
+#include "nvim/eval/userfunc.h"
#include "nvim/ex_cmds.h"
#include "nvim/ex_docmd.h"
#include "nvim/ex_eval.h"
@@ -62,6 +62,11 @@
#define BUFSIZE 8192 /* size of normal write buffer */
#define SMBUFSIZE 256 /* size of emergency write buffer */
+// For compatibility with libuv < 1.20.0 (tested on 1.18.0)
+#ifndef UV_FS_COPYFILE_FICLONE
+#define UV_FS_COPYFILE_FICLONE 0
+#endif
+
//
// The autocommands are stored in a list for each event.
// Autocommands for the same pattern, that are consecutive, are joined
@@ -295,6 +300,7 @@ readfile(
int skip_read = false;
context_sha256_T sha_ctx;
int read_undo_file = false;
+ int split = 0; // number of split lines
linenr_T linecnt;
int error = FALSE; /* errors encountered */
int ff_error = EOL_UNKNOWN; /* file format with errors */
@@ -407,11 +413,27 @@ readfile(
if (newfile) {
if (apply_autocmds_exarg(EVENT_BUFREADCMD, NULL, sfname,
- FALSE, curbuf, eap))
- return aborting() ? FAIL : OK;
+ false, curbuf, eap)) {
+ int status = OK;
+
+ if (aborting()) {
+ status = FAIL;
+ }
+
+ // The BufReadCmd code usually uses ":read" to get the text and
+ // perhaps ":file" to change the buffer name. But we should
+ // consider this to work like ":edit", thus reset the
+ // BF_NOTEDITED flag. Then ":write" will work to overwrite the
+ // same file.
+ if (status == OK) {
+ curbuf->b_flags &= ~BF_NOTEDITED;
+ }
+ return status;
+ }
} else if (apply_autocmds_exarg(EVENT_FILEREADCMD, sfname, sfname,
- FALSE, NULL, eap))
+ false, NULL, eap)) {
return aborting() ? FAIL : OK;
+ }
curbuf->b_op_start = pos;
}
@@ -548,20 +570,21 @@ readfile(
return FAIL;
}
}
- if (dir_of_file_exists(fname))
- filemess(curbuf, sfname, (char_u *)_("[New File]"), 0);
- else
- filemess(curbuf, sfname,
- (char_u *)_("[New DIRECTORY]"), 0);
- /* Even though this is a new file, it might have been
- * edited before and deleted. Get the old marks. */
+ if (dir_of_file_exists(fname)) {
+ filemess(curbuf, sfname, (char_u *)new_file_message(), 0);
+ } else {
+ filemess(curbuf, sfname, (char_u *)_("[New DIRECTORY]"), 0);
+ }
+ // Even though this is a new file, it might have been
+ // edited before and deleted. Get the old marks.
check_marks_read();
- /* Set forced 'fileencoding'. */
- if (eap != NULL)
+ // Set forced 'fileencoding'.
+ if (eap != NULL) {
set_forced_fenc(eap);
+ }
apply_autocmds_exarg(EVENT_BUFNEWFILE, sfname, sfname,
- FALSE, curbuf, eap);
- /* remember the current fileformat */
+ false, curbuf, eap);
+ // remember the current fileformat
save_file_ff(curbuf);
if (aborting()) /* autocmds may abort script processing */
@@ -991,8 +1014,21 @@ retry:
*/
{
if (!skip_read) {
- size = 0x10000L; /* use buffer >= 64K */
+ // 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;
+ }
+ }
+ // Protect against the argument of lalloc() going negative.
+ if (size < 0 || size + linerest + 1 < 0 || linerest >= MAXCOL) {
+ split++;
+ *ptr = NL; // split line by inserting a NL
+ size = 1;
+ } else if (!skip_read) {
for (; size >= 10; size /= 2) {
new_buffer = verbose_try_malloc((size_t)size + (size_t)linerest + 1);
if (new_buffer) {
@@ -1840,6 +1876,10 @@ failed:
STRCAT(IObuff, _("[CR missing]"));
c = TRUE;
}
+ if (split) {
+ STRCAT(IObuff, _("[long lines split]"));
+ c = true;
+ }
if (notconverted) {
STRCAT(IObuff, _("[NOT converted]"));
c = TRUE;
@@ -2032,14 +2072,16 @@ readfile_linenr(
* Fill "*eap" to force the 'fileencoding', 'fileformat' and 'binary to be
* equal to the buffer "buf". Used for calling readfile().
*/
-void prep_exarg(exarg_T *eap, buf_T *buf)
+void prep_exarg(exarg_T *eap, const buf_T *buf)
+ FUNC_ATTR_NONNULL_ALL
{
- eap->cmd = xmalloc(STRLEN(buf->b_p_ff) + STRLEN(buf->b_p_fenc) + 15);
+ const size_t cmd_len = 15 + STRLEN(buf->b_p_fenc);
+ eap->cmd = xmalloc(cmd_len);
- sprintf((char *)eap->cmd, "e ++ff=%s ++enc=%s", buf->b_p_ff, buf->b_p_fenc);
- eap->force_enc = 14 + (int)STRLEN(buf->b_p_ff);
+ snprintf((char *)eap->cmd, cmd_len, "e ++enc=%s", buf->b_p_fenc);
+ eap->force_enc = 8;
eap->bad_char = buf->b_bad_char;
- eap->force_ff = 7;
+ eap->force_ff = *buf->b_p_ff;
eap->force_bin = buf->b_p_bin ? FORCE_BIN : FORCE_NOBIN;
eap->read_edit = FALSE;
@@ -2180,6 +2222,11 @@ 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"
*
@@ -2650,6 +2697,7 @@ buf_write(
*/
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;
@@ -2737,6 +2785,7 @@ buf_write(
int some_error = false;
char_u *dirp;
char_u *rootname;
+ char_u *p;
/*
* Try to make the backup in each directory in the 'bdir' option.
@@ -2756,6 +2805,17 @@ buf_write(
* Isolate one directory name, using an entry in 'bdir'.
*/
(void)copy_option_part(&dirp, IObuff, IOSIZE, ",");
+ p = IObuff + STRLEN(IObuff);
+ if (after_pathsep((char *)IObuff, (char *)p) && p[-1] == p[-2]) {
+ // Ends with '//', Use Full path
+ if ((p = (char_u *)make_percent_swname((char *)IObuff, (char *)fname))
+ != NULL) {
+ backup = (char_u *)modname((char *)p, (char *)backup_ext,
+ no_prepend_dot);
+ xfree(p);
+ }
+ }
+
rootname = get_file_in_dir(fname, IObuff);
if (rootname == NULL) {
some_error = TRUE; /* out of memory */
@@ -2764,10 +2824,14 @@ buf_write(
FileInfo file_info_new;
{
- /*
- * Make backup file name.
- */
- backup = (char_u *)modname((char *)rootname, (char *)backup_ext, FALSE);
+ //
+ // Make the backup file name.
+ //
+ if (backup == NULL) {
+ backup = (char_u *)modname((char *)rootname, (char *)backup_ext,
+ no_prepend_dot);
+ }
+
if (backup == NULL) {
xfree(rootname);
some_error = TRUE; /* out of memory */
@@ -2893,12 +2957,26 @@ nobackup:
* Isolate one directory name and make the backup file name.
*/
(void)copy_option_part(&dirp, IObuff, IOSIZE, ",");
- rootname = get_file_in_dir(fname, IObuff);
- if (rootname == NULL)
- backup = NULL;
- else {
- backup = (char_u *)modname((char *)rootname, (char *)backup_ext, FALSE);
- xfree(rootname);
+ p = IObuff + STRLEN(IObuff);
+ if (after_pathsep((char *)IObuff, (char *)p) && p[-1] == p[-2]) {
+ // path ends with '//', use full path
+ if ((p = (char_u *)make_percent_swname((char *)IObuff, (char *)fname))
+ != NULL) {
+ backup = (char_u *)modname((char *)p, (char *)backup_ext,
+ no_prepend_dot);
+ xfree(p);
+ }
+ }
+
+ if (backup == NULL) {
+ rootname = get_file_in_dir(fname, IObuff);
+ if (rootname == NULL) {
+ backup = NULL;
+ } else {
+ backup = (char_u *)modname((char *)rootname, (char *)backup_ext,
+ no_prepend_dot);
+ xfree(rootname);
+ }
}
if (backup != NULL) {
@@ -3459,8 +3537,8 @@ restore_backup:
STRCAT(IObuff, _("[Device]"));
c = TRUE;
} else if (newfile) {
- STRCAT(IObuff, shortmess(SHM_NEW) ? _("[New]") : _("[New File]"));
- c = TRUE;
+ STRCAT(IObuff, new_file_message());
+ c = true;
}
if (no_eol) {
msg_add_eol();
@@ -3700,8 +3778,9 @@ static int set_rw_fname(char_u *fname, char_u *sfname)
return FAIL;
}
- if (setfname(curbuf, fname, sfname, FALSE) == OK)
+ if (setfname(curbuf, fname, sfname, false) == OK) {
curbuf->b_flags |= BF_NOTEDITED;
+ }
/* ....and a new named one is created */
apply_autocmds(EVENT_BUFNEW, NULL, NULL, FALSE, curbuf);
@@ -4834,7 +4913,7 @@ buf_check_timestamp(
if (buf->terminal
|| buf->b_ffname == NULL
|| buf->b_ml.ml_mfp == NULL
- || *buf->b_p_bt != NUL
+ || !bt_normal(buf)
|| buf->b_saving
|| busy
)
@@ -5328,7 +5407,7 @@ static bool vim_settempdir(char *tempdir)
char_u *vim_tempname(void)
{
// Temp filename counter.
- static uint32_t temp_count;
+ static uint64_t temp_count;
char_u *tempdir = vim_gettempdir();
if (!tempdir) {
@@ -5339,7 +5418,7 @@ char_u *vim_tempname(void)
// and nobody else creates a file in it.
char_u template[TEMP_FILE_PATH_MAXLEN];
snprintf((char *)template, TEMP_FILE_PATH_MAXLEN,
- "%s%" PRIu32, tempdir, temp_count++);
+ "%s%" PRIu64, tempdir, temp_count++);
return vim_strsave(template);
}
@@ -6555,7 +6634,7 @@ static int autocmd_nested = FALSE;
/// Execute autocommands for "event" and file name "fname".
///
-/// @param event event that occured
+/// @param event event that occurred
/// @param fname filename, NULL or empty means use actual file name
/// @param fname_io filename to use for <afile> on cmdline
/// @param force When true, ignore autocmd_busy
@@ -6572,7 +6651,7 @@ bool apply_autocmds(event_T event, char_u *fname, char_u *fname_io, bool force,
/// Like apply_autocmds(), but with extra "eap" argument. This takes care of
/// setting v:filearg.
///
-/// @param event event that occured
+/// @param event event that occurred
/// @param fname NULL or empty means use actual file name
/// @param fname_io fname to use for <afile> on cmdline
/// @param force When true, ignore autocmd_busy
@@ -6592,7 +6671,7 @@ static bool apply_autocmds_exarg(event_T event, char_u *fname, char_u *fname_io,
/// conditional, no autocommands are executed. If otherwise the autocommands
/// cause the script to be aborted, retval is set to FAIL.
///
-/// @param event event that occured
+/// @param event event that occurred
/// @param fname NULL or empty means use actual file name
/// @param fname_io fname to use for <afile> on cmdline
/// @param force When true, ignore autocmd_busy
@@ -6652,7 +6731,7 @@ bool has_event(event_T event) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
/// Execute autocommands for "event" and file name "fname".
///
-/// @param event event that occured
+/// @param event event that occurred
/// @param fname filename, NULL or empty means use actual file name
/// @param fname_io filename to use for <afile> on cmdline,
/// NULL means use `fname`.
@@ -6681,7 +6760,6 @@ static bool apply_autocmds_group(event_T event, char_u *fname, char_u *fname_io,
static int nesting = 0;
AutoPatCmd patcmd;
AutoPat *ap;
- void *save_funccalp;
char_u *save_cmdarg;
long save_cmdbang;
static int filechangeshell_busy = FALSE;
@@ -6830,7 +6908,8 @@ static bool apply_autocmds_group(event_T event, char_u *fname, char_u *fname_io,
|| event == EVENT_SPELLFILEMISSING
|| event == EVENT_SYNTAX
|| event == EVENT_SIGNAL
- || event == EVENT_TABCLOSED) {
+ || event == EVENT_TABCLOSED
+ || event == EVENT_WINCLOSED) {
fname = vim_strsave(fname);
} else {
fname = (char_u *)FullName_save((char *)fname, false);
@@ -6870,8 +6949,9 @@ static bool apply_autocmds_group(event_T event, char_u *fname, char_u *fname_io,
if (do_profiling == PROF_YES)
prof_child_enter(&wait_time); /* doesn't count for the caller itself */
- /* Don't use local function variables, if called from a function */
- save_funccalp = save_funccal();
+ // Don't use local function variables, if called from a function.
+ funccal_entry_T funccal_entry;
+ save_funccal(&funccal_entry);
/*
* When starting to execute autocommands, save the search patterns.
@@ -6960,9 +7040,10 @@ static bool apply_autocmds_group(event_T event, char_u *fname, char_u *fname_io,
autocmd_bufnr = save_autocmd_bufnr;
autocmd_match = save_autocmd_match;
current_sctx = save_current_sctx;
- restore_funccal(save_funccalp);
- if (do_profiling == PROF_YES)
+ restore_funccal();
+ if (do_profiling == PROF_YES) {
prof_child_exit(&wait_time);
+ }
KeyTyped = save_KeyTyped;
xfree(fname);
xfree(sfname);
@@ -7100,12 +7181,10 @@ auto_next_pat(
}
}
-/*
- * Get next autocommand command.
- * Called by do_cmdline() to get the next line for ":if".
- * Returns allocated string, or NULL for end of autocommands.
- */
-char_u *getnextac(int c, void *cookie, int indent)
+/// Get next autocommand command.
+/// Called by do_cmdline() to get the next line for ":if".
+/// @return allocated string, or NULL for end of autocommands.
+char_u *getnextac(int c, void *cookie, int indent, bool do_concat)
{
AutoPatCmd *acp = (AutoPatCmd *)cookie;
char_u *retval;
@@ -7166,8 +7245,8 @@ char_u *getnextac(int c, void *cookie, int indent)
/// To account for buffer-local autocommands, function needs to know
/// in which buffer the file will be opened.
///
-/// @param event event that occured.
-/// @param sfname filename the event occured in.
+/// @param event event that occurred.
+/// @param sfname filename the event occurred in.
/// @param buf buffer the file is open in
bool has_autocmd(event_T event, char_u *sfname, buf_T *buf)
FUNC_ATTR_WARN_UNUSED_RESULT
diff --git a/src/nvim/fold.c b/src/nvim/fold.c
index 5ce953e626..61a85171e8 100644
--- a/src/nvim/fold.c
+++ b/src/nvim/fold.c
@@ -19,9 +19,11 @@
#include "nvim/diff.h"
#include "nvim/eval.h"
#include "nvim/ex_docmd.h"
+#include "nvim/ex_session.h"
#include "nvim/func_attr.h"
#include "nvim/indent.h"
#include "nvim/buffer_updates.h"
+#include "nvim/extmark.h"
#include "nvim/mark.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
@@ -537,12 +539,10 @@ int foldManualAllowed(int create)
return FALSE;
}
-/* foldCreate() {{{2 */
-/*
- * Create a fold from line "start" to line "end" (inclusive) in the current
- * window.
- */
-void foldCreate(linenr_T start, linenr_T end)
+// foldCreate() {{{2
+/// Create a fold from line "start" to line "end" (inclusive) in the current
+/// window.
+void foldCreate(win_T *wp, linenr_T start, linenr_T end)
{
fold_T *fp;
garray_T *gap;
@@ -563,16 +563,16 @@ void foldCreate(linenr_T start, linenr_T end)
end_rel = end;
}
- /* When 'foldmethod' is "marker" add markers, which creates the folds. */
- if (foldmethodIsMarker(curwin)) {
- foldCreateMarkers(start, end);
+ // When 'foldmethod' is "marker" add markers, which creates the folds.
+ if (foldmethodIsMarker(wp)) {
+ foldCreateMarkers(wp, start, end);
return;
}
- checkupdate(curwin);
+ checkupdate(wp);
- /* Find the place to insert the new fold. */
- gap = &curwin->w_folds;
+ // Find the place to insert the new fold
+ gap = &wp->w_folds;
for (;; ) {
if (!foldFind(gap, start_rel, &fp))
break;
@@ -582,12 +582,14 @@ void foldCreate(linenr_T start, linenr_T end)
start_rel -= fp->fd_top;
end_rel -= fp->fd_top;
if (use_level || fp->fd_flags == FD_LEVEL) {
- use_level = TRUE;
- if (level >= curwin->w_p_fdl)
- closed = TRUE;
- } else if (fp->fd_flags == FD_CLOSED)
- closed = TRUE;
- ++level;
+ use_level = true;
+ if (level >= wp->w_p_fdl) {
+ closed = true;
+ }
+ } else if (fp->fd_flags == FD_CLOSED) {
+ closed = true;
+ }
+ level++;
} else {
/* This fold and new fold overlap: Insert here and move some folds
* inside the new fold. */
@@ -640,26 +642,28 @@ void foldCreate(linenr_T start, linenr_T end)
/* We want the new fold to be closed. If it would remain open because
* of using 'foldlevel', need to adjust fd_flags of containing folds.
*/
- if (use_level && !closed && level < curwin->w_p_fdl)
+ if (use_level && !closed && level < wp->w_p_fdl) {
closeFold(start, 1L);
- if (!use_level)
- curwin->w_fold_manual = true;
+ }
+ if (!use_level) {
+ wp->w_fold_manual = true;
+ }
fp->fd_flags = FD_CLOSED;
fp->fd_small = kNone;
- /* redraw */
- changed_window_setting();
+ // redraw
+ changed_window_setting_win(wp);
}
}
-/* deleteFold() {{{2 */
-/*
- * Delete a fold at line "start" in the current window.
- * When "end" is not 0, delete all folds from "start" to "end".
- * When "recursive" is TRUE delete recursively.
- */
+// deleteFold() {{{2
+/// @param start delete all folds from start to end when not 0
+/// @param end delete all folds from start to end when not 0
+/// @param recursive delete recursively if true
+/// @param had_visual true when Visual selection used
void deleteFold(
+ win_T *const wp,
const linenr_T start,
const linenr_T end,
const int recursive,
@@ -676,11 +680,11 @@ void deleteFold(
linenr_T first_lnum = MAXLNUM;
linenr_T last_lnum = 0;
- checkupdate(curwin);
+ checkupdate(wp);
while (lnum <= end) {
// Find the deepest fold for "start".
- garray_T *gap = &curwin->w_folds;
+ garray_T *gap = &wp->w_folds;
garray_T *found_ga = NULL;
linenr_T lnum_off = 0;
bool use_level = false;
@@ -692,10 +696,11 @@ void deleteFold(
found_fp = fp;
found_off = lnum_off;
- /* if "lnum" is folded, don't check nesting */
- if (check_closed(curwin, fp, &use_level, level,
- &maybe_small, lnum_off))
+ // if "lnum" is folded, don't check nesting
+ if (check_closed(wp, fp, &use_level, level,
+ &maybe_small, lnum_off)) {
break;
+ }
/* check nested folds */
gap = &fp->fd_nested;
@@ -707,34 +712,41 @@ void deleteFold(
} else {
lnum = found_fp->fd_top + found_fp->fd_len + found_off;
- if (foldmethodIsManual(curwin))
- deleteFoldEntry(found_ga,
- (int)(found_fp - (fold_T *)found_ga->ga_data), recursive);
- else {
- if (first_lnum > found_fp->fd_top + found_off)
+ if (foldmethodIsManual(wp)) {
+ deleteFoldEntry(wp, found_ga,
+ (int)(found_fp - (fold_T *)found_ga->ga_data),
+ recursive);
+ } else {
+ if (first_lnum > found_fp->fd_top + found_off) {
first_lnum = found_fp->fd_top + found_off;
- if (last_lnum < lnum)
+ }
+ if (last_lnum < lnum) {
last_lnum = lnum;
- if (!did_one)
- parseMarker(curwin);
- deleteFoldMarkers(found_fp, recursive, found_off);
+ }
+ if (!did_one) {
+ parseMarker(wp);
+ }
+ deleteFoldMarkers(wp, found_fp, recursive, found_off);
}
did_one = true;
- /* redraw window */
- changed_window_setting();
+ // redraw window
+ changed_window_setting_win(wp);
}
}
if (!did_one) {
EMSG(_(e_nofold));
- /* Force a redraw to remove the Visual highlighting. */
- if (had_visual)
- redraw_curbuf_later(INVERTED);
- } else
- /* Deleting markers may make cursor column invalid. */
- check_cursor_col();
+ // Force a redraw to remove the Visual highlighting.
+ if (had_visual) {
+ redraw_buf_later(wp->w_buffer, INVERTED);
+ }
+ } else {
+ // Deleting markers may make cursor column invalid
+ check_cursor_col_win(wp);
+ }
if (last_lnum > 0) {
+ // TODO(teto): pass the buffer
changed_lines(first_lnum, (colnr_T)0, last_lnum, 0L, false);
// send one nvim_buf_lines_event at the end
@@ -743,7 +755,7 @@ void deleteFold(
// the modification of the *first* line of the fold, but we send through a
// notification that includes every line that was part of the fold
int64_t num_changed = last_lnum - first_lnum;
- buf_updates_send_changes(curbuf, first_lnum, num_changed,
+ buf_updates_send_changes(wp->w_buffer, first_lnum, num_changed,
num_changed, true);
}
}
@@ -771,6 +783,11 @@ void foldUpdate(win_T *wp, linenr_T top, linenr_T bot)
return;
}
+ if (need_diff_redraw) {
+ // will update later
+ return;
+ }
+
// Mark all folds from top to bot as maybe-small.
fold_T *fp;
(void)foldFind(&wp->w_folds, top, &fp);
@@ -1041,7 +1058,7 @@ void cloneFoldGrowArray(garray_T *from, garray_T *to)
* the first fold below it (careful: it can be beyond the end of the array!).
* Returns FALSE when there is no fold that contains "lnum".
*/
-static int foldFind(garray_T *gap, linenr_T lnum, fold_T **fpp)
+static int foldFind(const garray_T *gap, linenr_T lnum, fold_T **fpp)
{
linenr_T low, high;
fold_T *fp;
@@ -1290,7 +1307,7 @@ static void foldOpenNested(fold_T *fpr)
// Delete fold "idx" from growarray "gap".
// When "recursive" is true also delete all the folds contained in it.
// When "recursive" is false contained folds are moved one level up.
-static void deleteFoldEntry(garray_T *const gap, const int idx,
+static void deleteFoldEntry(win_T *const wp, garray_T *const gap, const int idx,
const bool recursive)
{
fold_T *fp = (fold_T *)gap->ga_data + idx;
@@ -1356,13 +1373,17 @@ void foldMarkAdjust(win_T *wp, linenr_T line1, linenr_T line2, long amount, long
line2 = line1 - amount_after - 1;
/* If appending a line in Insert mode, it should be included in the fold
* just above the line. */
- if ((State & INSERT) && amount == (linenr_T)1 && line2 == MAXLNUM)
- --line1;
- foldMarkAdjustRecurse(&wp->w_folds, line1, line2, amount, amount_after);
+ if ((State & INSERT) && amount == (linenr_T)1 && line2 == MAXLNUM) {
+ line1--;
+ }
+ foldMarkAdjustRecurse(wp, &wp->w_folds, line1, line2, amount, amount_after);
}
-/* foldMarkAdjustRecurse() {{{2 */
-static void foldMarkAdjustRecurse(garray_T *gap, linenr_T line1, linenr_T line2, long amount, long amount_after)
+// foldMarkAdjustRecurse() {{{2
+static void foldMarkAdjustRecurse(
+ win_T *wp, garray_T *gap,
+ linenr_T line1, linenr_T line2, long amount, long amount_after
+)
{
fold_T *fp;
linenr_T last;
@@ -1410,7 +1431,7 @@ static void foldMarkAdjustRecurse(garray_T *gap, linenr_T line1, linenr_T line2,
// 4. fold completely contained in range
if (amount == MAXLNUM) {
// Deleting lines: delete the fold completely
- deleteFoldEntry(gap, i, true);
+ deleteFoldEntry(wp, gap, i, true);
i--; // adjust index for deletion
fp--;
} else {
@@ -1418,9 +1439,9 @@ static void foldMarkAdjustRecurse(garray_T *gap, linenr_T line1, linenr_T line2,
}
} else {
if (fp->fd_top < top) {
- /* 2 or 3: need to correct nested folds too */
- foldMarkAdjustRecurse(&fp->fd_nested, line1 - fp->fd_top,
- line2 - fp->fd_top, amount, amount_after);
+ // 2 or 3: need to correct nested folds too
+ foldMarkAdjustRecurse(wp, &fp->fd_nested, line1 - fp->fd_top,
+ line2 - fp->fd_top, amount, amount_after);
if (last <= line2) {
/* 2. fold contains line1, line2 is below fold */
if (amount == MAXLNUM)
@@ -1435,13 +1456,13 @@ static void foldMarkAdjustRecurse(garray_T *gap, linenr_T line1, linenr_T line2,
/* 5. fold is below line1 and contains line2; need to
* correct nested folds too */
if (amount == MAXLNUM) {
- foldMarkAdjustRecurse(&fp->fd_nested, line1 - fp->fd_top,
+ foldMarkAdjustRecurse(wp, &fp->fd_nested, line1 - fp->fd_top,
line2 - fp->fd_top, amount,
amount_after + (fp->fd_top - top));
fp->fd_len -= line2 - fp->fd_top + 1;
fp->fd_top = line1;
} else {
- foldMarkAdjustRecurse(&fp->fd_nested, line1 - fp->fd_top,
+ foldMarkAdjustRecurse(wp, &fp->fd_nested, line1 - fp->fd_top,
line2 - fp->fd_top, amount,
amount_after - amount);
fp->fd_len += amount_after - amount;
@@ -1458,10 +1479,10 @@ static void foldMarkAdjustRecurse(garray_T *gap, linenr_T line1, linenr_T line2,
* Get the lowest 'foldlevel' value that makes the deepest nested fold in the
* current window open.
*/
-int getDeepestNesting(void)
+int getDeepestNesting(win_T *wp)
{
- checkupdate(curwin);
- return getDeepestNestingRecurse(&curwin->w_folds);
+ checkupdate(wp);
+ return getDeepestNestingRecurse(&wp->w_folds);
}
static int getDeepestNestingRecurse(garray_T *gap)
@@ -1480,17 +1501,22 @@ static int getDeepestNestingRecurse(garray_T *gap)
return maxlevel;
}
-/* check_closed() {{{2 */
-/*
- * Check if a fold is closed and update the info needed to check nested folds.
- */
+// check_closed() {{{2
+/// Check if a fold is closed and update the info needed to check nested folds.
+///
+/// @param[in,out] use_levelp true: outer fold had FD_LEVEL
+/// @param[in,out] fp fold to check
+/// @param level folding depth
+/// @param[out] maybe_smallp true: outer this had fd_small == kNone
+/// @param lnum_off line number offset for fp->fd_top
+/// @return true if fold is closed
static bool check_closed(
- win_T *const win,
+ win_T *const wp,
fold_T *const fp,
- bool *const use_levelp, // true: outer fold had FD_LEVEL
- const int level, // folding depth
- bool *const maybe_smallp, // true: outer this had fd_small == kNone
- const linenr_T lnum_off // line number offset for fp->fd_top
+ bool *const use_levelp,
+ const int level,
+ bool *const maybe_smallp,
+ const linenr_T lnum_off
)
{
bool closed = false;
@@ -1499,7 +1525,7 @@ static bool check_closed(
* fold and all folds it contains depend on 'foldlevel'. */
if (*use_levelp || fp->fd_flags == FD_LEVEL) {
*use_levelp = true;
- if (level >= win->w_p_fdl) {
+ if (level >= wp->w_p_fdl) {
closed = true;
}
} else if (fp->fd_flags == FD_CLOSED) {
@@ -1514,7 +1540,7 @@ static bool check_closed(
if (*maybe_smallp) {
fp->fd_small = kNone;
}
- checkSmall(win, fp, lnum_off);
+ checkSmall(wp, fp, lnum_off);
if (fp->fd_small == kTrue) {
closed = false;
}
@@ -1522,10 +1548,9 @@ static bool check_closed(
return closed;
}
-/* checkSmall() {{{2 */
-/*
- * Update fd_small field of fold "fp".
- */
+// checkSmall() {{{2
+/// Update fd_small field of fold "fp".
+/// @param lnum_off offset for fp->fd_top
static void
checkSmall(
win_T *const wp,
@@ -1537,13 +1562,13 @@ checkSmall(
// Mark any nested folds to maybe-small
setSmallMaybe(&fp->fd_nested);
- if (fp->fd_len > curwin->w_p_fml) {
+ if (fp->fd_len > wp->w_p_fml) {
fp->fd_small = kFalse;
} else {
int count = 0;
for (int n = 0; n < fp->fd_len; n++) {
count += plines_win_nofold(wp, fp->fd_top + lnum_off + n);
- if (count > curwin->w_p_fml) {
+ if (count > wp->w_p_fml) {
fp->fd_small = kFalse;
return;
}
@@ -1568,43 +1593,47 @@ static void setSmallMaybe(garray_T *gap)
* Create a fold from line "start" to line "end" (inclusive) in the current
* window by adding markers.
*/
-static void foldCreateMarkers(linenr_T start, linenr_T end)
+static void foldCreateMarkers(win_T *wp, linenr_T start, linenr_T end)
{
- if (!MODIFIABLE(curbuf)) {
+ buf_T *buf = wp->w_buffer;
+ if (!MODIFIABLE(buf)) {
EMSG(_(e_modifiable));
return;
}
- parseMarker(curwin);
+ parseMarker(wp);
- foldAddMarker(start, curwin->w_p_fmr, foldstartmarkerlen);
- foldAddMarker(end, foldendmarker, foldendmarkerlen);
+ foldAddMarker(buf, start, wp->w_p_fmr, foldstartmarkerlen);
+ foldAddMarker(buf, end, foldendmarker, foldendmarkerlen);
/* Update both changes here, to avoid all folds after the start are
* changed when the start marker is inserted and the end isn't. */
+ // TODO(teto): pass the buffer
changed_lines(start, (colnr_T)0, end, 0L, false);
// Note: foldAddMarker() may not actually change start and/or end if
// u_save() is unable to save the buffer line, but we send the
// nvim_buf_lines_event anyway since it won't do any harm.
int64_t num_changed = 1 + end - start;
- buf_updates_send_changes(curbuf, start, num_changed, num_changed, true);
+ buf_updates_send_changes(buf, start, num_changed, num_changed, true);
}
/* foldAddMarker() {{{2 */
/*
* Add "marker[markerlen]" in 'commentstring' to line "lnum".
*/
-static void foldAddMarker(linenr_T lnum, const char_u *marker, size_t markerlen)
+static void foldAddMarker(
+ buf_T *buf, linenr_T lnum, const char_u *marker, size_t markerlen)
{
- char_u *cms = curbuf->b_p_cms;
+ char_u *cms = buf->b_p_cms;
char_u *line;
char_u *newline;
- char_u *p = (char_u *)strstr((char *)curbuf->b_p_cms, "%s");
+ char_u *p = (char_u *)strstr((char *)buf->b_p_cms, "%s");
bool line_is_comment = false;
// Allocate a new line: old-line + 'cms'-start + marker + 'cms'-end
- line = ml_get(lnum);
+ line = ml_get_buf(buf, lnum, false);
size_t line_len = STRLEN(line);
+ size_t added = 0;
if (u_save(lnum - 1, lnum + 1) == OK) {
// Check if the line ends with an unclosed comment
@@ -1614,12 +1643,18 @@ static void foldAddMarker(linenr_T lnum, const char_u *marker, size_t markerlen)
// Append the marker to the end of the line
if (p == NULL || line_is_comment) {
STRLCPY(newline + line_len, marker, markerlen + 1);
+ added = markerlen;
} else {
STRCPY(newline + line_len, cms);
memcpy(newline + line_len + (p - cms), marker, markerlen);
STRCPY(newline + line_len + (p - cms) + markerlen, p + 2);
+ added = markerlen + STRLEN(cms)-2;
+ }
+ ml_replace_buf(buf, lnum, newline, false);
+ if (added) {
+ extmark_splice_cols(buf, (int)lnum-1, (int)line_len,
+ 0, (int)added, kExtmarkUndo);
}
- ml_replace(lnum, newline, false);
}
}
@@ -1629,20 +1664,22 @@ static void foldAddMarker(linenr_T lnum, const char_u *marker, size_t markerlen)
*/
static void
deleteFoldMarkers(
+ win_T *wp,
fold_T *fp,
int recursive,
linenr_T lnum_off // offset for fp->fd_top
)
{
if (recursive) {
- for (int i = 0; i < fp->fd_nested.ga_len; ++i) {
- deleteFoldMarkers((fold_T *)fp->fd_nested.ga_data + i, TRUE,
+ for (int i = 0; i < fp->fd_nested.ga_len; i++) {
+ deleteFoldMarkers(wp, (fold_T *)fp->fd_nested.ga_data + i, true,
lnum_off + fp->fd_top);
}
}
- foldDelMarker(fp->fd_top + lnum_off, curwin->w_p_fmr, foldstartmarkerlen);
- foldDelMarker(fp->fd_top + lnum_off + fp->fd_len - 1, foldendmarker,
- foldendmarkerlen);
+ foldDelMarker(wp->w_buffer, fp->fd_top+lnum_off, wp->w_p_fmr,
+ foldstartmarkerlen);
+ foldDelMarker(wp->w_buffer, fp->fd_top + lnum_off + fp->fd_len - 1,
+ foldendmarker, foldendmarkerlen);
}
// foldDelMarker() {{{2
@@ -1651,18 +1688,20 @@ deleteFoldMarkers(
// Delete 'commentstring' if it matches.
// If the marker is not found, there is no error message. Could be a missing
// close-marker.
-static void foldDelMarker(linenr_T lnum, char_u *marker, size_t markerlen)
+static void foldDelMarker(
+ buf_T *buf, linenr_T lnum, char_u *marker, size_t markerlen
+)
{
char_u *newline;
- char_u *cms = curbuf->b_p_cms;
+ char_u *cms = buf->b_p_cms;
char_u *cms2;
// end marker may be missing and fold extends below the last line
- if (lnum > curbuf->b_ml.ml_line_count) {
+ if (lnum > buf->b_ml.ml_line_count) {
return;
}
- char_u *line = ml_get(lnum);
- for (char_u *p = line; *p != NUL; ++p) {
+ char_u *line = ml_get_buf(buf, lnum, false);
+ for (char_u *p = line; *p != NUL; p++) {
if (STRNCMP(p, marker, markerlen) != 0) {
continue;
}
@@ -1686,7 +1725,10 @@ static void foldDelMarker(linenr_T lnum, char_u *marker, size_t markerlen)
assert(p >= line);
memcpy(newline, line, (size_t)(p - line));
STRCPY(newline + (p - line), p + len);
- ml_replace(lnum, newline, false);
+ ml_replace_buf(buf, lnum, newline, false);
+ extmark_splice_cols(buf, (int)lnum-1, (int)(p - line),
+ (int)len,
+ 0, kExtmarkUndo);
}
break;
}
@@ -1874,12 +1916,12 @@ void foldtext_cleanup(char_u *str)
/* foldUpdateIEMS() {{{2 */
/*
* Update the folding for window "wp", at least from lines "top" to "bot".
- * Return TRUE if any folds did change.
+ * IEMS = "Indent Expr Marker Syntax"
*/
static void foldUpdateIEMS(win_T *const wp, linenr_T top, linenr_T bot)
{
fline_T fline;
- void (*getlevel)(fline_T *);
+ LevelGetter getlevel = NULL;
fold_T *fp;
/* Avoid problems when being called recursively. */
@@ -2061,8 +2103,8 @@ static void foldUpdateIEMS(win_T *const wp, linenr_T top, linenr_T bot)
}
}
- /* There can't be any folds from start until end now. */
- foldRemove(&wp->w_folds, start, end);
+ // There can't be any folds from start until end now.
+ foldRemove(wp, &wp->w_folds, start, end);
/* If some fold changed, need to redraw and position cursor. */
if (fold_changed && wp->w_p_fen)
@@ -2242,12 +2284,12 @@ static linenr_T foldUpdateIEMSRecurse(
if (fp->fd_top > firstlnum) {
// We will move the start of this fold up, hence we move all
// nested folds (with relative line numbers) down.
- foldMarkAdjustRecurse(&fp->fd_nested,
+ foldMarkAdjustRecurse(flp->wp, &fp->fd_nested,
(linenr_T)0, (linenr_T)MAXLNUM,
(long)(fp->fd_top - firstlnum), 0L);
} else {
// Will move fold down, move nested folds relatively up.
- foldMarkAdjustRecurse(&fp->fd_nested,
+ foldMarkAdjustRecurse(flp->wp, &fp->fd_nested,
(linenr_T)0,
(long)(firstlnum - fp->fd_top - 1),
(linenr_T)MAXLNUM,
@@ -2277,10 +2319,10 @@ static linenr_T foldUpdateIEMSRecurse(
breakstart = flp->lnum;
breakend = flp->lnum;
}
- foldRemove(&fp->fd_nested, breakstart - fp->fd_top,
+ foldRemove(flp->wp, &fp->fd_nested, breakstart - fp->fd_top,
breakend - fp->fd_top);
i = (int)(fp - (fold_T *)gap->ga_data);
- foldSplit(gap, i, breakstart, breakend - 1);
+ foldSplit(flp->wp->w_buffer, gap, i, breakstart, breakend - 1);
fp = (fold_T *)gap->ga_data + i + 1;
/* If using the "marker" or "syntax" method, we
* need to continue until the end of the fold is
@@ -2296,7 +2338,7 @@ static linenr_T foldUpdateIEMSRecurse(
if (i != 0) {
fp2 = fp - 1;
if (fp2->fd_top + fp2->fd_len == fp->fd_top) {
- foldMerge(fp2, gap, fp);
+ foldMerge(flp->wp, fp2, gap, fp);
fp = fp2;
}
}
@@ -2307,12 +2349,13 @@ static linenr_T foldUpdateIEMSRecurse(
// A fold that starts at or after startlnum and stops
// before the new fold must be deleted. Continue
// looking for the next one.
- deleteFoldEntry(gap, (int)(fp - (fold_T *)gap->ga_data), true);
+ deleteFoldEntry(flp->wp, gap,
+ (int)(fp - (fold_T *)gap->ga_data), true);
} else {
/* A fold has some lines above startlnum, truncate it
* to stop just above startlnum. */
fp->fd_len = startlnum - fp->fd_top;
- foldMarkAdjustRecurse(&fp->fd_nested,
+ foldMarkAdjustRecurse(flp->wp, &fp->fd_nested,
(linenr_T)fp->fd_len, (linenr_T)MAXLNUM,
(linenr_T)MAXLNUM, 0L);
fold_changed = true;
@@ -2441,8 +2484,8 @@ static linenr_T foldUpdateIEMSRecurse(
// Delete contained folds from the end of the last one found until where
// we stopped looking.
- foldRemove(&fp->fd_nested, startlnum2 - fp->fd_top,
- flp->lnum - 1 - fp->fd_top);
+ foldRemove(flp->wp, &fp->fd_nested, startlnum2 - fp->fd_top,
+ flp->lnum - 1 - fp->fd_top);
if (lvl < level) {
// End of fold found, update the length when it got shorter.
@@ -2460,7 +2503,7 @@ static linenr_T foldUpdateIEMSRecurse(
// indent or expr method: split fold to create a new one
// below bot
i = (int)(fp - (fold_T *)gap->ga_data);
- foldSplit(gap, i, flp->lnum, bot);
+ foldSplit(flp->wp->w_buffer, gap, i, flp->lnum, bot);
fp = (fold_T *)gap->ga_data + i;
}
} else {
@@ -2478,23 +2521,23 @@ static linenr_T foldUpdateIEMSRecurse(
break;
if (fp2->fd_top + fp2->fd_len > flp->lnum) {
if (fp2->fd_top < flp->lnum) {
- /* Make fold that includes lnum start at lnum. */
- foldMarkAdjustRecurse(&fp2->fd_nested,
- (linenr_T)0, (long)(flp->lnum - fp2->fd_top - 1),
- (linenr_T)MAXLNUM, (long)(fp2->fd_top - flp->lnum));
+ // Make fold that includes lnum start at lnum.
+ foldMarkAdjustRecurse(flp->wp, &fp2->fd_nested,
+ (linenr_T)0, (long)(flp->lnum - fp2->fd_top - 1),
+ (linenr_T)MAXLNUM, (long)(fp2->fd_top-flp->lnum));
fp2->fd_len -= flp->lnum - fp2->fd_top;
fp2->fd_top = flp->lnum;
fold_changed = true;
}
if (lvl >= level) {
- /* merge new fold with existing fold that follows */
- foldMerge(fp, gap, fp2);
+ // merge new fold with existing fold that follows
+ foldMerge(flp->wp, fp, gap, fp2);
}
break;
}
fold_changed = true;
- deleteFoldEntry(gap, (int)(fp2 - (fold_T *)gap->ga_data), true);
+ deleteFoldEntry(flp->wp, gap, (int)(fp2 - (fold_T *)gap->ga_data), true);
}
/* Need to redraw the lines we inspected, which might be further down than
@@ -2530,8 +2573,10 @@ static void foldInsert(garray_T *gap, int i)
* The caller must first have taken care of any nested folds from "top" to
* "bot"!
*/
-static void foldSplit(garray_T *const gap, const int i, const linenr_T top,
- const linenr_T bot)
+static void foldSplit(buf_T *buf, garray_T *const gap,
+ const int i, const linenr_T top,
+ const linenr_T bot
+ )
{
fold_T *fp2;
@@ -2586,7 +2631,9 @@ static void foldSplit(garray_T *const gap, const int i, const linenr_T top,
* 5: made to start below "bot".
* 6: not changed
*/
-static void foldRemove(garray_T *gap, linenr_T top, linenr_T bot)
+static void foldRemove(
+ win_T *const wp, garray_T *gap, linenr_T top, linenr_T bot
+)
{
fold_T *fp = NULL;
@@ -2598,10 +2645,11 @@ static void foldRemove(garray_T *gap, linenr_T top, linenr_T bot)
// Find fold that includes top or a following one.
if (foldFind(gap, top, &fp) && fp->fd_top < top) {
// 2: or 3: need to delete nested folds
- foldRemove(&fp->fd_nested, top - fp->fd_top, bot - fp->fd_top);
+ foldRemove(wp, &fp->fd_nested, top - fp->fd_top, bot - fp->fd_top);
if (fp->fd_top + fp->fd_len - 1 > bot) {
// 3: need to split it.
- foldSplit(gap, (int)(fp - (fold_T *)gap->ga_data), top, bot);
+ foldSplit(wp->w_buffer, gap,
+ (int)(fp - (fold_T *)gap->ga_data), top, bot);
} else {
// 2: truncate fold at "top".
fp->fd_len = top - fp->fd_top;
@@ -2619,7 +2667,8 @@ static void foldRemove(garray_T *gap, linenr_T top, linenr_T bot)
fold_changed = true;
if (fp->fd_top + fp->fd_len - 1 > bot) {
// 5: Make fold that includes bot start below bot.
- foldMarkAdjustRecurse(&fp->fd_nested,
+ foldMarkAdjustRecurse(
+ wp, &fp->fd_nested,
(linenr_T)0, (long)(bot - fp->fd_top),
(linenr_T)MAXLNUM, (long)(fp->fd_top - bot - 1));
fp->fd_len -= bot - fp->fd_top + 1;
@@ -2628,7 +2677,7 @@ static void foldRemove(garray_T *gap, linenr_T top, linenr_T bot)
}
// 4: Delete completely contained fold.
- deleteFoldEntry(gap, (int)(fp - (fold_T *)gap->ga_data), true);
+ deleteFoldEntry(wp, gap, (int)(fp - (fold_T *)gap->ga_data), true);
}
}
}
@@ -2680,19 +2729,22 @@ static void foldReverseOrder(
// 8. truncated below dest and shifted up.
// 9. shifted up
// 10. not changed
-static void truncate_fold(fold_T *fp, linenr_T end)
+static void truncate_fold(win_T *const wp, fold_T *fp, linenr_T end)
{
// I want to stop *at here*, foldRemove() stops *above* top
end += 1;
- foldRemove(&fp->fd_nested, end - fp->fd_top, MAXLNUM);
+ foldRemove(wp, &fp->fd_nested, end - fp->fd_top, MAXLNUM);
fp->fd_len = end - fp->fd_top;
}
#define FOLD_END(fp) ((fp)->fd_top + (fp)->fd_len - 1)
#define VALID_FOLD(fp, gap) ((fp) < ((fold_T *)(gap)->ga_data + (gap)->ga_len))
#define FOLD_INDEX(fp, gap) ((size_t)(fp - ((fold_T *)(gap)->ga_data)))
-void foldMoveRange(garray_T *gap, const linenr_T line1, const linenr_T line2,
- const linenr_T dest)
+void foldMoveRange(
+ win_T *const wp, garray_T *gap,
+ const linenr_T line1, const linenr_T line2,
+ const linenr_T dest
+)
{
fold_T *fp;
const linenr_T range_len = line2 - line1 + 1;
@@ -2703,20 +2755,20 @@ void foldMoveRange(garray_T *gap, const linenr_T line1, const linenr_T line2,
if (FOLD_END(fp) > dest) {
// Case 4 -- don't have to change this fold, but have to move nested
// folds.
- foldMoveRange(&fp->fd_nested, line1 - fp->fd_top, line2 -
+ foldMoveRange(wp, &fp->fd_nested, line1 - fp->fd_top, line2 -
fp->fd_top, dest - fp->fd_top);
return;
} else if (FOLD_END(fp) > line2) {
// Case 3 -- Remove nested folds between line1 and line2 & reduce the
// length of fold by "range_len".
// Folds after this one must be dealt with.
- foldMarkAdjustRecurse(&fp->fd_nested, line1 - fp->fd_top,
+ foldMarkAdjustRecurse(wp, &fp->fd_nested, line1 - fp->fd_top,
line2 - fp->fd_top, MAXLNUM, -range_len);
fp->fd_len -= range_len;
} else {
// Case 2 -- truncate fold *above* line1.
// Folds after this one must be dealt with.
- truncate_fold(fp, line1 - 1);
+ truncate_fold(wp, fp, line1 - 1);
}
// Look at the next fold, and treat that one as if it were the first after
// "line1" (because now it is).
@@ -2734,13 +2786,13 @@ void foldMoveRange(garray_T *gap, const linenr_T line1, const linenr_T line2,
}
if (VALID_FOLD(fp, gap) && fp->fd_top <= dest) {
// Case 8. -- ensure truncated at dest, shift up
- truncate_fold(fp, dest);
+ truncate_fold(wp, fp, dest);
fp->fd_top -= range_len;
}
return;
} else if (FOLD_END(fp) > dest) {
// Case 7 -- remove nested folds and shrink
- foldMarkAdjustRecurse(&fp->fd_nested, line2 + 1 - fp->fd_top,
+ foldMarkAdjustRecurse(wp, &fp->fd_nested, line2 + 1 - fp->fd_top,
dest - fp->fd_top, MAXLNUM, -move_len);
fp->fd_len -= move_len;
fp->fd_top += move_len;
@@ -2756,7 +2808,7 @@ void foldMoveRange(garray_T *gap, const linenr_T line1, const linenr_T line2,
// 5, or 6
if (FOLD_END(fp) > line2) {
// 6, truncate before moving
- truncate_fold(fp, line2);
+ truncate_fold(wp, fp, line2);
}
fp->fd_top += move_len;
continue;
@@ -2768,7 +2820,7 @@ void foldMoveRange(garray_T *gap, const linenr_T line1, const linenr_T line2,
}
if (FOLD_END(fp) > dest) {
- truncate_fold(fp, dest);
+ truncate_fold(wp, fp, dest);
}
fp->fd_top -= range_len;
@@ -2800,7 +2852,7 @@ void foldMoveRange(garray_T *gap, const linenr_T line1, const linenr_T line2,
* The resulting fold is "fp1", nested folds are moved from "fp2" to "fp1".
* Fold entry "fp2" in "gap" is deleted.
*/
-static void foldMerge(fold_T *fp1, garray_T *gap, fold_T *fp2)
+static void foldMerge(win_T *const wp, fold_T *fp1, garray_T *gap, fold_T *fp2)
{
fold_T *fp3;
fold_T *fp4;
@@ -2810,8 +2862,9 @@ static void foldMerge(fold_T *fp1, garray_T *gap, fold_T *fp2)
/* If the last nested fold in fp1 touches the first nested fold in fp2,
* merge them recursively. */
- if (foldFind(gap1, fp1->fd_len - 1L, &fp3) && foldFind(gap2, 0L, &fp4))
- foldMerge(fp3, gap2, fp4);
+ if (foldFind(gap1, fp1->fd_len - 1L, &fp3) && foldFind(gap2, 0L, &fp4)) {
+ foldMerge(wp, fp3, gap2, fp4);
+ }
/* Move nested folds in fp2 to the end of fp1. */
if (!GA_EMPTY(gap2)) {
@@ -2826,7 +2879,7 @@ static void foldMerge(fold_T *fp1, garray_T *gap, fold_T *fp2)
}
fp1->fd_len += fp2->fd_len;
- deleteFoldEntry(gap, (int)(fp2 - (fold_T *)gap->ga_data), true);
+ deleteFoldEntry(wp, gap, (int)(fp2 - (fold_T *)gap->ga_data), true);
fold_changed = true;
}
diff --git a/src/nvim/func_attr.h b/src/nvim/func_attr.h
index 14b8158c7f..38b51b5564 100644
--- a/src/nvim/func_attr.h
+++ b/src/nvim/func_attr.h
@@ -211,6 +211,8 @@
# define FUNC_API_NOEXPORT
/// API function not exposed in VimL/eval.
# define FUNC_API_REMOTE_ONLY
+/// API function not exposed in VimL/remote.
+# define FUNC_API_LUA_ONLY
/// API function introduced at the given API level.
# define FUNC_API_SINCE(X)
/// API function deprecated since the given API level.
diff --git a/src/nvim/generators/c_grammar.lua b/src/nvim/generators/c_grammar.lua
index 1aa8223da0..de098b7a7b 100644
--- a/src/nvim/generators/c_grammar.lua
+++ b/src/nvim/generators/c_grammar.lua
@@ -42,6 +42,7 @@ local c_proto = Ct(
(fill * Cg((P('FUNC_API_FAST') * Cc(true)), 'fast') ^ -1) *
(fill * Cg((P('FUNC_API_NOEXPORT') * Cc(true)), 'noexport') ^ -1) *
(fill * Cg((P('FUNC_API_REMOTE_ONLY') * Cc(true)), 'remote_only') ^ -1) *
+ (fill * Cg((P('FUNC_API_LUA_ONLY') * Cc(true)), 'lua_only') ^ -1) *
(fill * Cg((P('FUNC_API_REMOTE_IMPL') * Cc(true)), 'remote_impl') ^ -1) *
(fill * Cg((P('FUNC_API_BRIDGE_IMPL') * Cc(true)), 'bridge_impl') ^ -1) *
(fill * Cg((P('FUNC_API_COMPOSITOR_IMPL') * Cc(true)), 'compositor_impl') ^ -1) *
diff --git a/src/nvim/generators/gen_api_dispatch.lua b/src/nvim/generators/gen_api_dispatch.lua
index 76dcf849d1..6e80ad0e5c 100644
--- a/src/nvim/generators/gen_api_dispatch.lua
+++ b/src/nvim/generators/gen_api_dispatch.lua
@@ -192,7 +192,7 @@ end
-- the real API.
for i = 1, #functions do
local fn = functions[i]
- if fn.impl_name == nil then
+ if fn.impl_name == nil and not fn.lua_only then
local args = {}
output:write('Object handle_'..fn.name..'(uint64_t channel_id, Array args, Error *error)')
@@ -237,6 +237,12 @@ for i = 1, #functions do
(j - 1)..'].type == kObjectTypeInteger && args.items['..(j - 1)..'].data.integer >= 0) {')
output:write('\n '..converted..' = (handle_T)args.items['..(j - 1)..'].data.integer;')
end
+ if rt:match('^Float$') then
+ -- accept integers for Floats
+ output:write('\n } else if (args.items['..
+ (j - 1)..'].type == kObjectTypeInteger) {')
+ output:write('\n '..converted..' = (Float)args.items['..(j - 1)..'].data.integer;')
+ end
-- accept empty lua tables as empty dictionarys
if rt:match('^Dictionary') then
output:write('\n } else if (args.items['..(j - 1)..'].type == kObjectTypeArray && args.items['..(j - 1)..'].data.array.size == 0) {') --luacheck: ignore 631
@@ -310,12 +316,13 @@ void msgpack_rpc_init_method_table(void)
for i = 1, #functions do
local fn = functions[i]
- output:write(' msgpack_rpc_add_method_handler('..
- '(String) {.data = "'..fn.name..'", '..
- '.size = sizeof("'..fn.name..'") - 1}, '..
- '(MsgpackRpcRequestHandler) {.fn = handle_'.. (fn.impl_name or fn.name)..
- ', .fast = '..tostring(fn.fast)..'});\n')
-
+ if not fn.lua_only then
+ output:write(' msgpack_rpc_add_method_handler('..
+ '(String) {.data = "'..fn.name..'", '..
+ '.size = sizeof("'..fn.name..'") - 1}, '..
+ '(MsgpackRpcRequestHandler) {.fn = handle_'.. (fn.impl_name or fn.name)..
+ ', .fast = '..tostring(fn.fast)..'});\n')
+ end
end
output:write('\n}\n\n')
diff --git a/src/nvim/generators/gen_char_blob.lua b/src/nvim/generators/gen_char_blob.lua
index 1702add2e4..a7dad50d48 100644
--- a/src/nvim/generators/gen_char_blob.lua
+++ b/src/nvim/generators/gen_char_blob.lua
@@ -1,49 +1,59 @@
if arg[1] == '--help' then
print('Usage:')
- print(' gencharblob.lua source target varname')
+ print(' '..arg[0]..' target source varname [source varname]...')
print('')
print('Generates C file with big uint8_t blob.')
print('Blob will be stored in a static const array named varname.')
os.exit()
end
-assert(#arg == 3)
+assert(#arg >= 3 and (#arg - 1) % 2 == 0)
-local source_file = arg[1]
-local target_file = arg[2]
-local varname = arg[3]
-
-local source = io.open(source_file, 'r')
+local target_file = arg[1] or error('Need a target file')
local target = io.open(target_file, 'w')
target:write('#include <stdint.h>\n\n')
-target:write(('static const uint8_t %s[] = {\n'):format(varname))
-
-local num_bytes = 0
-local MAX_NUM_BYTES = 15 -- 78 / 5: maximum number of bytes on one line
-target:write(' ')
-
-local increase_num_bytes
-increase_num_bytes = function()
- num_bytes = num_bytes + 1
- if num_bytes == MAX_NUM_BYTES then
- num_bytes = 0
- target:write('\n ')
+
+local varnames = {}
+for argi = 2, #arg, 2 do
+ local source_file = arg[argi]
+ local varname = arg[argi + 1]
+ if varnames[varname] then
+ error(string.format("varname %q is already specified for file %q", varname, varnames[varname]))
end
-end
+ varnames[varname] = source_file
+
+ local source = io.open(source_file, 'r')
+ or error(string.format("source_file %q doesn't exist", source_file))
+
+ target:write(('static const uint8_t %s[] = {\n'):format(varname))
-for line in source:lines() do
- for i = 1,string.len(line) do
- local byte = string.byte(line, i)
- assert(byte ~= 0)
- target:write(string.format(' %3u,', byte))
+ local num_bytes = 0
+ local MAX_NUM_BYTES = 15 -- 78 / 5: maximum number of bytes on one line
+ target:write(' ')
+
+ local increase_num_bytes
+ increase_num_bytes = function()
+ num_bytes = num_bytes + 1
+ if num_bytes == MAX_NUM_BYTES then
+ num_bytes = 0
+ target:write('\n ')
+ end
+ end
+
+ for line in source:lines() do
+ for i = 1, string.len(line) do
+ local byte = line:byte(i)
+ assert(byte ~= 0)
+ target:write(string.format(' %3u,', byte))
+ increase_num_bytes()
+ end
+ target:write(string.format(' %3u,', string.byte('\n', 1)))
increase_num_bytes()
end
- target:write(string.format(' %3u,', string.byte('\n', 1)))
- increase_num_bytes()
-end
-target:write(' 0};\n')
+ target:write(' 0};\n')
+ source:close()
+end
-source:close()
target:close()
diff --git a/src/nvim/generators/gen_declarations.lua b/src/nvim/generators/gen_declarations.lua
index ad44613f42..0782c8115d 100755
--- a/src/nvim/generators/gen_declarations.lua
+++ b/src/nvim/generators/gen_declarations.lua
@@ -207,9 +207,7 @@ preproc_f:close()
local header = [[
-#ifndef DEFINE_FUNC_ATTRIBUTES
-# define DEFINE_FUNC_ATTRIBUTES
-#endif
+#define DEFINE_FUNC_ATTRIBUTES
#include "nvim/func_attr.h"
#undef DEFINE_FUNC_ATTRIBUTES
]]
diff --git a/src/nvim/generators/gen_eval.lua b/src/nvim/generators/gen_eval.lua
index 2c6f8f2603..d16453530f 100644
--- a/src/nvim/generators/gen_eval.lua
+++ b/src/nvim/generators/gen_eval.lua
@@ -25,7 +25,7 @@ local gperfpipe = io.open(funcsfname .. '.gperf', 'wb')
local funcs = require('eval').funcs
local metadata = mpack.unpack(io.open(metadata_file, 'rb'):read("*all"))
for _,fun in ipairs(metadata) do
- if not fun.remote_only then
+ if not (fun.remote_only or fun.lua_only) then
funcs[fun.name] = {
args=#fun.parameters,
func='api_wrapper',
diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c
index 2469bb5baa..5ab5a7db1b 100644
--- a/src/nvim/getchar.c
+++ b/src/nvim/getchar.c
@@ -41,6 +41,7 @@
#include "nvim/option.h"
#include "nvim/regexp.h"
#include "nvim/screen.h"
+#include "nvim/ex_session.h"
#include "nvim/state.h"
#include "nvim/strings.h"
#include "nvim/ui.h"
@@ -1095,26 +1096,40 @@ void del_typebuf(int len, int offset)
* Write typed characters to script file.
* If recording is on put the character in the recordbuffer.
*/
-static void gotchars(char_u *chars, size_t len)
+static void gotchars(const char_u *chars, size_t len)
+ FUNC_ATTR_NONNULL_ALL
{
- char_u *s = chars;
- int c;
+ const char_u *s = chars;
+ static char_u buf[4] = { 0 };
+ static size_t buflen = 0;
+ size_t todo = len;
- // remember how many chars were last recorded
- if (reg_recording != 0) {
- last_recorded_len += len;
- }
+ while (todo--) {
+ buf[buflen++] = *s++;
+
+ // When receiving a special key sequence, store it until we have all
+ // the bytes and we can decide what to do with it.
+ if (buflen == 1 && buf[0] == K_SPECIAL) {
+ continue;
+ }
+ if (buflen == 2) {
+ continue;
+ }
- while (len--) {
// Handle one byte at a time; no translation to be done.
- c = *s++;
- updatescript(c);
+ for (size_t i = 0; i < buflen; i++) {
+ updatescript(buf[i]);
+ }
if (reg_recording != 0) {
- char buf[2] = { (char)c, NUL };
- add_buff(&recordbuff, buf, 1L);
+ buf[buflen] = NUL;
+ add_buff(&recordbuff, (char *)buf, (ptrdiff_t)buflen);
+ // remember how many chars were last recorded
+ last_recorded_len += buflen;
}
+ buflen = 0;
}
+
may_sync_undo();
/* output "debug mode" message next time in debug mode */
@@ -1165,12 +1180,12 @@ void free_typebuf(void)
if (typebuf.tb_buf == typebuf_init) {
internal_error("Free typebuf 1");
} else {
- xfree(typebuf.tb_buf);
+ XFREE_CLEAR(typebuf.tb_buf);
}
if (typebuf.tb_noremap == noremapbuf_init) {
internal_error("Free typebuf 2");
} else {
- xfree(typebuf.tb_noremap);
+ XFREE_CLEAR(typebuf.tb_noremap);
}
}
@@ -1201,7 +1216,7 @@ void save_typeahead(tasave_T *tp)
{
tp->save_typebuf = typebuf;
alloc_typebuf();
- tp->typebuf_valid = TRUE;
+ tp->typebuf_valid = true;
tp->old_char = old_char;
tp->old_mod_mask = old_mod_mask;
old_char = -1;
@@ -1518,7 +1533,7 @@ int vgetc(void)
* collection in the first next vgetc(). It's disabled after that to
* avoid internally used Lists and Dicts to be freed.
*/
- may_garbage_collect = FALSE;
+ may_garbage_collect = false;
return c;
}
@@ -1562,7 +1577,7 @@ int vpeekc(void)
{
if (old_char != -1)
return old_char;
- return vgetorpeek(FALSE);
+ return vgetorpeek(false);
}
/*
@@ -1615,20 +1630,20 @@ vungetc ( /* unget one character (can only be done once!) */
/// Also stores the result of mappings.
/// Also used for the ":normal" command.
/// 3. from the user
-/// This may do a blocking wait if "advance" is TRUE.
+/// This may do a blocking wait if "advance" is true.
///
-/// if "advance" is TRUE (vgetc()):
+/// if "advance" is true (vgetc()):
/// Really get the character.
/// KeyTyped is set to TRUE in the case the user typed the key.
/// KeyStuffed is TRUE if the character comes from the stuff buffer.
-/// if "advance" is FALSE (vpeekc()):
+/// if "advance" is false (vpeekc()):
/// Just look whether there is a character available.
/// Return NUL if not.
///
/// When `no_mapping` (global) is zero, checks for mappings in the current mode.
/// Only returns one byte (of a multi-byte character).
/// K_SPECIAL and CSI may be escaped, need to get two more bytes then.
-static int vgetorpeek(int advance)
+static int vgetorpeek(bool advance)
{
int c, c1;
int keylen;
@@ -1721,7 +1736,7 @@ static int vgetorpeek(int advance)
// flush all input
c = inchar(typebuf.tb_buf, typebuf.tb_buflen - 1, 0L);
// If inchar() returns TRUE (script file was active) or we
- // are inside a mapping, get out of insert mode.
+ // are inside a mapping, get out of Insert mode.
// Otherwise we behave like having gotten a CTRL-C.
// As a result typing CTRL-C in insert mode will
// really insert a CTRL-C.
@@ -1833,13 +1848,13 @@ static int vgetorpeek(int advance)
char_u *p1 = mp->m_keys;
char_u *p2 = (char_u *)mb_unescape((const char **)&p1);
- if (has_mbyte && p2 != NULL && MB_BYTE2LEN(c1) > MB_PTR2LEN(p2))
+ if (p2 != NULL && MB_BYTE2LEN(c1) > utfc_ptr2len(p2)) {
mlen = 0;
- /*
- * Check an entry whether it matches.
- * - Full match: mlen == keylen
- * - Partly match: mlen == typebuf.tb_len
- */
+ }
+
+ // Check an entry whether it matches.
+ // - Full match: mlen == keylen
+ // - Partly match: mlen == typebuf.tb_len
keylen = mp->m_keylen;
if (mlen == keylen
|| (mlen == typebuf.tb_len
@@ -2324,7 +2339,7 @@ static int vgetorpeek(int advance)
} /* for (;;) */
} /* if (!character from stuffbuf) */
- /* if advance is FALSE don't loop on NULs */
+ // if advance is false don't loop on NULs
} while (c < 0 || (advance && c == NUL));
/*
@@ -2409,7 +2424,6 @@ int inchar(
did_outofmem_msg = FALSE; /* display out of memory message (again) */
did_swapwrite_msg = FALSE; /* display swap file write error again */
}
- undo_off = FALSE; /* restart undo now */
// Get a character from a script file if there is one.
// If interrupted: Stop reading script files, close them all.
@@ -2481,12 +2495,11 @@ int inchar(
return fix_input_buffer(buf, len);
}
-/*
- * Fix typed characters for use by vgetc() and check_termcode().
- * buf[] must have room to triple the number of bytes!
- * Returns the new length.
- */
+// Fix typed characters for use by vgetc() and check_termcode().
+// "buf[]" must have room to triple the number of bytes!
+// Returns the new length.
int fix_input_buffer(char_u *buf, int len)
+ FUNC_ATTR_NONNULL_ALL
{
if (!using_script()) {
// Should not escape K_SPECIAL/CSI reading input from the user because vim
@@ -3107,7 +3120,7 @@ int do_map(int maptype, char_u *arg, int mode, bool is_abbrev)
case 0:
break;
case 1:
- result = 1; // invalid arguments
+ // invalid arguments
goto free_and_return;
default:
assert(false && "Unknown return code from str_to_mapargs!");
@@ -3348,7 +3361,7 @@ showmap (
msg_putchar(' ');
// Display the LHS. Get length of what we write.
- len = (size_t)msg_outtrans_special(mp->m_keys, true);
+ len = (size_t)msg_outtrans_special(mp->m_keys, true, 0);
do {
msg_putchar(' '); /* padd with blanks */
++len;
@@ -3376,7 +3389,7 @@ showmap (
// as typeahead.
char_u *s = vim_strsave(mp->m_str);
vim_unescape_csi(s);
- msg_outtrans_special(s, FALSE);
+ msg_outtrans_special(s, false, 0);
xfree(s);
}
if (p_verbose > 0) {
@@ -4419,7 +4432,7 @@ mapblock_T *get_maphash(int index, buf_T *buf)
}
/// Get command argument for <Cmd> key
-char_u * getcmdkeycmd(int promptc, void *cookie, int indent)
+char_u * getcmdkeycmd(int promptc, void *cookie, int indent, bool do_concat)
{
garray_T line_ga;
int c1 = -1, c2;
diff --git a/src/nvim/getchar.h b/src/nvim/getchar.h
index 01f60ccf49..f0b52079aa 100644
--- a/src/nvim/getchar.h
+++ b/src/nvim/getchar.h
@@ -10,12 +10,12 @@
/// Values for "noremap" argument of ins_typebuf()
///
/// Also used for map->m_noremap and menu->noremap[].
-enum {
+enum RemapValues {
REMAP_YES = 0, ///< Allow remapping.
REMAP_NONE = -1, ///< No remapping.
REMAP_SCRIPT = -2, ///< Remap script-local mappings only.
REMAP_SKIP = -3, ///< No remapping for first char.
-} RemapValues;
+};
// Argument for flush_buffers().
typedef enum {
diff --git a/src/nvim/globals.h b/src/nvim/globals.h
index 5237c621f9..d6d00d6e83 100644
--- a/src/nvim/globals.h
+++ b/src/nvim/globals.h
@@ -120,21 +120,20 @@ typedef off_t off_T;
# endif
#endif
-/*
- * When vgetc() is called, it sets mod_mask to the set of modifiers that are
- * held down based on the MOD_MASK_* symbols that are read first.
- */
-EXTERN int mod_mask INIT(= 0x0); /* current key modifiers */
-
-/*
- * Cmdline_row is the row where the command line starts, just below the
- * last window.
- * When the cmdline gets longer than the available space the screen gets
- * scrolled up. After a CTRL-D (show matches), after hitting ':' after
- * "hit return", and for the :global command, the command line is
- * temporarily moved. The old position is restored with the next call to
- * update_screen().
- */
+// When vgetc() is called, it sets mod_mask to the set of modifiers that are
+// held down based on the MOD_MASK_* symbols that are read first.
+EXTERN int mod_mask INIT(= 0x0); // current key modifiers
+
+
+EXTERN bool lua_attr_active INIT(= false);
+
+// Cmdline_row is the row where the command line starts, just below the
+// last window.
+// When the cmdline gets longer than the available space the screen gets
+// scrolled up. After a CTRL-D (show matches), after hitting ':' after
+// "hit return", and for the :global command, the command line is
+// temporarily moved. The old position is restored with the next call to
+// update_screen().
EXTERN int cmdline_row;
EXTERN int redraw_cmdline INIT(= false); // cmdline must be redrawn
@@ -146,42 +145,38 @@ EXTERN int cmdline_was_last_drawn INIT(= false); // cmdline was last drawn
EXTERN int exec_from_reg INIT(= false); // executing register
-/*
- * When '$' is included in 'cpoptions' option set:
- * When a change command is given that deletes only part of a line, a dollar
- * is put at the end of the changed text. dollar_vcol is set to the virtual
- * column of this '$'. -1 is used to indicate no $ is being displayed.
- */
+// When '$' is included in 'cpoptions' option set:
+// When a change command is given that deletes only part of a line, a dollar
+// is put at the end of the changed text. dollar_vcol is set to the virtual
+// column of this '$'. -1 is used to indicate no $ is being displayed.
EXTERN colnr_T dollar_vcol INIT(= -1);
-/*
- * Variables for Insert mode completion.
- */
+// Variables for Insert mode completion.
-/* Length in bytes of the text being completed (this is deleted to be replaced
- * by the match.) */
+// Length in bytes of the text being completed (this is deleted to be replaced
+// by the match.)
EXTERN int compl_length INIT(= 0);
-/* Set when character typed while looking for matches and it means we should
- * stop looking for matches. */
-EXTERN int compl_interrupted INIT(= FALSE);
+// Set when character typed while looking for matches and it means we should
+// stop looking for matches.
+EXTERN int compl_interrupted INIT(= false);
// Set when doing something for completion that may call edit() recursively,
// which is not allowed. Also used to disable folding during completion
EXTERN int compl_busy INIT(= false);
-/* List of flags for method of completion. */
+// List of flags for method of completion.
EXTERN int compl_cont_status INIT(= 0);
-# define CONT_ADDING 1 /* "normal" or "adding" expansion */
-# define CONT_INTRPT (2 + 4) /* a ^X interrupted the current expansion */
- /* it's set only iff N_ADDS is set */
-# define CONT_N_ADDS 4 /* next ^X<> will add-new or expand-current */
-# define CONT_S_IPOS 8 /* next ^X<> will set initial_pos?
- * if so, word-wise-expansion will set SOL */
-# define CONT_SOL 16 /* pattern includes start of line, just for
- * word-wise expansion, not set for ^X^L */
-# define CONT_LOCAL 32 /* for ctrl_x_mode 0, ^X^P/^X^N do a local
- * expansion, (eg use complete=.) */
+# define CONT_ADDING 1 // "normal" or "adding" expansion
+# define CONT_INTRPT (2 + 4) // a ^X interrupted the current expansion
+ // it's set only iff N_ADDS is set
+# define CONT_N_ADDS 4 // next ^X<> will add-new or expand-current
+# define CONT_S_IPOS 8 // next ^X<> will set initial_pos?
+ // if so, word-wise-expansion will set SOL
+# define CONT_SOL 16 // pattern includes start of line, just for
+ // word-wise expansion, not set for ^X^L
+# define CONT_LOCAL 32 // for ctrl_x_mode 0, ^X^P/^X^N do a local
+ // expansion, (eg use complete=.)
// state for putting characters in the message area
EXTERN int cmdmsg_rl INIT(= false); // cmdline is drawn right to left
@@ -197,49 +192,49 @@ EXTERN bool msg_scrolled_ign INIT(= false);
EXTERN bool msg_did_scroll INIT(= false);
-EXTERN char_u *keep_msg INIT(= NULL); /* msg to be shown after redraw */
-EXTERN int keep_msg_attr INIT(= 0); /* highlight attr for keep_msg */
-EXTERN int keep_msg_more INIT(= FALSE); /* keep_msg was set by msgmore() */
-EXTERN int need_fileinfo INIT(= FALSE); /* do fileinfo() after redraw */
-EXTERN int msg_scroll INIT(= FALSE); /* msg_start() will scroll */
-EXTERN int msg_didout INIT(= FALSE); /* msg_outstr() was used in line */
-EXTERN int msg_didany INIT(= FALSE); /* msg_outstr() was used at all */
-EXTERN int msg_nowait INIT(= FALSE); /* don't wait for this msg */
-EXTERN int emsg_off INIT(= 0); /* don't display errors for now,
- unless 'debug' is set. */
-EXTERN int info_message INIT(= FALSE); /* printing informative message */
-EXTERN int msg_hist_off INIT(= FALSE); /* don't add messages to history */
-EXTERN int need_clr_eos INIT(= FALSE); /* need to clear text before
- displaying a message. */
-EXTERN int emsg_skip INIT(= 0); /* don't display errors for
- expression that is skipped */
-EXTERN int emsg_severe INIT(= FALSE); /* use message of next of several
- emsg() calls for throw */
-EXTERN int did_endif INIT(= FALSE); /* just had ":endif" */
-EXTERN dict_T vimvardict; /* Dictionary with v: variables */
-EXTERN dict_T globvardict; /* Dictionary with g: variables */
-EXTERN int did_emsg; /* set by emsg() when the message
- is displayed or thrown */
+EXTERN char_u *keep_msg INIT(= NULL); // msg to be shown after redraw
+EXTERN int keep_msg_attr INIT(= 0); // highlight attr for keep_msg
+EXTERN int keep_msg_more INIT(= false); // keep_msg was set by msgmore()
+EXTERN int need_fileinfo INIT(= false); // do fileinfo() after redraw
+EXTERN int msg_scroll INIT(= false); // msg_start() will scroll
+EXTERN int msg_didout INIT(= false); // msg_outstr() was used in line
+EXTERN int msg_didany INIT(= false); // msg_outstr() was used at all
+EXTERN int msg_nowait INIT(= false); // don't wait for this msg
+EXTERN int emsg_off INIT(= 0); // don't display errors for now,
+ // unless 'debug' is set.
+EXTERN int info_message INIT(= false); // printing informative message
+EXTERN bool msg_hist_off INIT(= false); // don't add messages to history
+EXTERN int need_clr_eos INIT(= false); // need to clear text before
+ // displaying a message.
+EXTERN int emsg_skip INIT(= 0); // don't display errors for
+ // expression that is skipped
+EXTERN int emsg_severe INIT(= false); // use message of next of several
+ // emsg() calls for throw
+EXTERN int did_endif INIT(= false); // just had ":endif"
+EXTERN dict_T vimvardict; // Dictionary with v: variables
+EXTERN dict_T globvardict; // Dictionary with g: variables
+EXTERN int did_emsg; // set by emsg() when the message
+ // is displayed or thrown
EXTERN bool called_vim_beep; // set if vim_beep() is called
-EXTERN int did_emsg_syntax; /* did_emsg set because of a
- syntax error */
-EXTERN int called_emsg; /* always set by emsg() */
-EXTERN int ex_exitval INIT(= 0); /* exit value for ex mode */
-EXTERN int emsg_on_display INIT(= FALSE); /* there is an error message */
-EXTERN int rc_did_emsg INIT(= FALSE); /* vim_regcomp() called emsg() */
-
-EXTERN int no_wait_return INIT(= 0); /* don't wait for return for now */
-EXTERN int need_wait_return INIT(= 0); /* need to wait for return later */
-EXTERN int did_wait_return INIT(= FALSE); /* wait_return() was used and
- nothing written since then */
-EXTERN int need_maketitle INIT(= TRUE); /* call maketitle() soon */
+EXTERN int did_emsg_syntax; // did_emsg set because of a
+ // syntax error
+EXTERN int called_emsg; // always set by emsg()
+EXTERN int ex_exitval INIT(= 0); // exit value for ex mode
+EXTERN bool emsg_on_display INIT(= false); // there is an error message
+EXTERN int rc_did_emsg INIT(= false); // vim_regcomp() called emsg()
+
+EXTERN int no_wait_return INIT(= 0); // don't wait for return for now
+EXTERN int need_wait_return INIT(= 0); // need to wait for return later
+EXTERN int did_wait_return INIT(= false); // wait_return() was used and
+ // nothing written since then
+EXTERN int need_maketitle INIT(= true); // call maketitle() soon
EXTERN int quit_more INIT(= false); // 'q' hit at "--more--" msg
EXTERN int ex_keep_indent INIT(= false); // getexmodeline(): keep indent
EXTERN int vgetc_busy INIT(= 0); // when inside vgetc() then > 0
-EXTERN int didset_vim INIT(= FALSE); /* did set $VIM ourselves */
-EXTERN int didset_vimruntime INIT(= FALSE); /* idem for $VIMRUNTIME */
+EXTERN int didset_vim INIT(= false); // did set $VIM ourselves
+EXTERN int didset_vimruntime INIT(= false); // idem for $VIMRUNTIME
/// Lines left before a "more" message. Ex mode needs to be able to reset this
/// after you type something.
@@ -247,8 +242,8 @@ EXTERN int lines_left INIT(= -1); // lines left for listing
EXTERN int msg_no_more INIT(= false); // don't use more prompt, truncate
// messages
-EXTERN char_u *sourcing_name INIT( = NULL); /* name of error message source */
-EXTERN linenr_T sourcing_lnum INIT(= 0); /* line number of the source file */
+EXTERN char_u *sourcing_name INIT(= NULL); // name of error message source
+EXTERN linenr_T sourcing_lnum INIT(= 0); // line number of the source file
EXTERN int ex_nesting_level INIT(= 0); // nesting level
EXTERN int debug_break_level INIT(= -1); // break below this level
@@ -279,11 +274,11 @@ EXTERN int check_cstack INIT(= false);
/// commands).
EXTERN int trylevel INIT(= 0);
-/// When "force_abort" is TRUE, always skip commands after an error message,
+/// When "force_abort" is true, always skip commands after an error message,
/// even after the outermost ":endif", ":endwhile" or ":endfor" or for a
-/// function without the "abort" flag. It is set to TRUE when "trylevel" is
+/// function without the "abort" flag. It is set to true when "trylevel" is
/// non-zero (and ":silent!" was not used) or an exception is being thrown at
-/// the time an error is detected. It is set to FALSE when "trylevel" gets
+/// the time an error is detected. It is set to false when "trylevel" gets
/// zero again and there was no error or interrupt or throw.
EXTERN int force_abort INIT(= false);
@@ -301,7 +296,7 @@ EXTERN struct msglist **msg_list INIT(= NULL);
/// interrupt message or reporting an exception that is still uncaught at the
/// top level (which has already been discarded then). Also used for the error
/// message when no exception can be thrown.
-EXTERN int suppress_errthrow INIT(= false);
+EXTERN bool suppress_errthrow INIT(= false);
/// The stack of all caught and not finished exceptions. The exception on the
/// top of the stack is the one got by evaluation of v:exception. The complete
@@ -318,7 +313,7 @@ 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 int may_garbage_collect INIT(= false);
+EXTERN bool may_garbage_collect INIT(= false);
EXTERN int want_garbage_collect INIT(= false);
EXTERN int garbage_collect_at_exit INIT(= false);
@@ -331,6 +326,7 @@ EXTERN int garbage_collect_at_exit INIT(= false);
#define SID_NONE -6 // don't set scriptID
#define SID_LUA -7 // for Lua scripts/chunks
#define SID_API_CLIENT -8 // for API clients
+#define SID_STR -9 // for sourcing a string
// Script CTX being sourced or was sourced to define the current function.
EXTERN sctx_T current_sctx INIT(= { 0 COMMA 0 COMMA 0 });
@@ -353,41 +349,38 @@ EXTERN int provider_call_nesting INIT(= 0);
EXTERN int t_colors INIT(= 256); // int value of T_CCO
-/*
- * When highlight_match is TRUE, highlight a match, starting at the cursor
- * position. Search_match_lines is the number of lines after the match (0 for
- * a match within one line), search_match_endcol the column number of the
- * character just after the match in the last line.
- */
-EXTERN int highlight_match INIT(= FALSE); /* show search match pos */
-EXTERN linenr_T search_match_lines; /* lines of of matched string */
-EXTERN colnr_T search_match_endcol; /* col nr of match end */
-
-EXTERN int no_smartcase INIT(= FALSE); /* don't use 'smartcase' once */
-
-EXTERN int need_check_timestamps INIT(= FALSE); /* need to check file
- timestamps asap */
-EXTERN int did_check_timestamps INIT(= FALSE); /* did check timestamps
- recently */
-EXTERN int no_check_timestamps INIT(= 0); /* Don't check timestamps */
-
-EXTERN int autocmd_busy INIT(= FALSE); /* Is apply_autocmds() busy? */
-EXTERN int autocmd_no_enter INIT(= FALSE); /* *Enter autocmds disabled */
-EXTERN int autocmd_no_leave INIT(= FALSE); /* *Leave autocmds disabled */
-EXTERN int modified_was_set; /* did ":set modified" */
-EXTERN int did_filetype INIT(= FALSE); /* FileType event found */
-EXTERN int keep_filetype INIT(= FALSE); /* value for did_filetype when
- starting to execute
- autocommands */
+// When highlight_match is true, highlight a match, starting at the cursor
+// position. Search_match_lines is the number of lines after the match (0 for
+// a match within one line), search_match_endcol the column number of the
+// character just after the match in the last line.
+EXTERN int highlight_match INIT(= false); // show search match pos
+EXTERN linenr_T search_match_lines; // lines of of matched string
+EXTERN colnr_T search_match_endcol; // col nr of match end
+
+EXTERN int no_smartcase INIT(= false); // don't use 'smartcase' once
+
+EXTERN int need_check_timestamps INIT(= false); // need to check file
+ // timestamps asap
+EXTERN int did_check_timestamps INIT(= false); // did check timestamps
+ // recently
+EXTERN int no_check_timestamps INIT(= 0); // Don't check timestamps
+
+EXTERN int autocmd_busy INIT(= false); // Is apply_autocmds() busy?
+EXTERN int autocmd_no_enter INIT(= false); // *Enter autocmds disabled
+EXTERN int autocmd_no_leave INIT(= false); // *Leave autocmds disabled
+EXTERN int modified_was_set; // did ":set modified"
+EXTERN int did_filetype INIT(= false); // FileType event found
+// value for did_filetype when starting to execute autocommands
+EXTERN int keep_filetype INIT(= false);
// When deleting the current buffer, another one must be loaded.
// If we know which one is preferred, au_new_curbuf is set to it.
EXTERN bufref_T au_new_curbuf INIT(= { NULL, 0, 0 });
-// When deleting a buffer/window and autocmd_busy is TRUE, do not free the
+// When deleting a buffer/window and autocmd_busy is true, do not free the
// buffer/window. but link it in the list starting with
// au_pending_free_buf/ap_pending_free_win, using b_next/w_next.
-// Free the buffer/window when autocmd_busy is being set to FALSE.
+// Free the buffer/window when autocmd_busy is being set to false.
EXTERN buf_T *au_pending_free_buf INIT(= NULL);
EXTERN win_T *au_pending_free_win INIT(= NULL);
@@ -395,36 +388,27 @@ EXTERN win_T *au_pending_free_win INIT(= NULL);
EXTERN int mouse_grid;
EXTERN int mouse_row;
EXTERN int mouse_col;
-EXTERN bool mouse_past_bottom INIT(= false); /* mouse below last line */
-EXTERN bool mouse_past_eol INIT(= false); /* mouse right of line */
-EXTERN int mouse_dragging INIT(= 0); /* extending Visual area with
- mouse dragging */
-
-/* Value set from 'diffopt'. */
-EXTERN int diff_context INIT(= 6); /* context for folds */
-EXTERN int diff_foldcolumn INIT(= 2); /* 'foldcolumn' for diff mode */
-EXTERN int diff_need_scrollbind INIT(= FALSE);
+EXTERN bool mouse_past_bottom INIT(= false); // mouse below last line
+EXTERN bool mouse_past_eol INIT(= false); // mouse right of line
+EXTERN int mouse_dragging INIT(= 0); // extending Visual area with
+ // mouse dragging
-/* The root of the menu hierarchy. */
+// The root of the menu hierarchy.
EXTERN vimmenu_T *root_menu INIT(= NULL);
-/*
- * While defining the system menu, sys_menu is TRUE. This avoids
- * overruling of menus that the user already defined.
- */
-EXTERN int sys_menu INIT(= FALSE);
-
-/* While redrawing the screen this flag is set. It means the screen size
- * ('lines' and 'rows') must not be changed. */
-EXTERN int updating_screen INIT(= FALSE);
-
-/*
- * All windows are linked in a list. firstwin points to the first entry,
- * lastwin to the last entry (can be the same as firstwin) and curwin to the
- * currently active window.
- */
-EXTERN win_T *firstwin; /* first window */
-EXTERN win_T *lastwin; /* last window */
-EXTERN win_T *prevwin INIT(= NULL); /* previous window */
+// While defining the system menu, sys_menu is true. This avoids
+// overruling of menus that the user already defined.
+EXTERN int sys_menu INIT(= false);
+
+// While redrawing the screen this flag is set. It means the screen size
+// ('lines' and 'rows') must not be changed.
+EXTERN int updating_screen INIT(= 0);
+
+// All windows are linked in a list. firstwin points to the first entry,
+// lastwin to the last entry (can be the same as firstwin) and curwin to the
+// currently active window.
+EXTERN win_T *firstwin; // first window
+EXTERN win_T *lastwin; // last window
+EXTERN win_T *prevwin INIT(= NULL); // previous window
# define ONE_WINDOW (firstwin == lastwin)
# define FOR_ALL_FRAMES(frp, first_frame) \
for (frp = first_frame; frp != NULL; frp = frp->fr_next) // NOLINT
@@ -440,32 +424,27 @@ EXTERN win_T *prevwin INIT(= NULL); /* previous window */
for (win_T *wp = ((tp) == curtab) \
? firstwin : (tp)->tp_firstwin; wp != NULL; wp = wp->w_next)
-EXTERN win_T *curwin; /* currently active window */
+EXTERN win_T *curwin; // currently active window
-EXTERN win_T *aucmd_win; /* window used in aucmd_prepbuf() */
-EXTERN int aucmd_win_used INIT(= FALSE); /* aucmd_win is being used */
+EXTERN win_T *aucmd_win; // window used in aucmd_prepbuf()
+EXTERN int aucmd_win_used INIT(= false); // aucmd_win is being used
-/*
- * The window layout is kept in a tree of frames. topframe points to the top
- * of the tree.
- */
-EXTERN frame_T *topframe; /* top of the window frame tree */
+// The window layout is kept in a tree of frames. topframe points to the top
+// of the tree.
+EXTERN frame_T *topframe; // top of the window frame tree
-/*
- * Tab pages are alternative topframes. "first_tabpage" points to the first
- * one in the list, "curtab" is the current one.
- */
+// Tab pages are alternative topframes. "first_tabpage" points to the first
+// one in the list, "curtab" is the current one.
EXTERN tabpage_T *first_tabpage;
+EXTERN tabpage_T *lastused_tabpage;
EXTERN tabpage_T *curtab;
-EXTERN int redraw_tabline INIT(= FALSE); /* need to redraw tabline */
+EXTERN int redraw_tabline INIT(= false); // need to redraw tabline
// Iterates over all tabs in the tab list
# define FOR_ALL_TABS(tp) for (tabpage_T *tp = first_tabpage; tp != NULL; tp = tp->tp_next)
-/*
- * All buffers are linked in a list. 'firstbuf' points to the first entry,
- * 'lastbuf' to the last entry and 'curbuf' to the currently active buffer.
- */
+// All buffers are linked in a list. 'firstbuf' points to the first entry,
+// 'lastbuf' to the last entry and 'curbuf' to the currently active buffer.
EXTERN buf_T *firstbuf INIT(= NULL); // first buffer
EXTERN buf_T *lastbuf INIT(= NULL); // last buffer
EXTERN buf_T *curbuf INIT(= NULL); // currently active buffer
@@ -481,28 +460,26 @@ EXTERN buf_T *curbuf INIT(= NULL); // currently active buffer
for (sign = buf->b_signlist; sign != NULL; sign = sign->next) // NOLINT
-/*
- * List of files being edited (global argument list). curwin->w_alist points
- * to this when the window is using the global argument list.
- */
-EXTERN alist_T global_alist; /* global argument list */
+// List of files being edited (global argument list). curwin->w_alist points
+// to this when the window is using the global argument list.
+EXTERN alist_T global_alist; // global argument list
EXTERN int max_alist_id INIT(= 0); ///< the previous argument list id
EXTERN bool arg_had_last INIT(= false); // accessed last file in
// global_alist
-EXTERN int ru_col; /* column for ruler */
-EXTERN int ru_wid; /* 'rulerfmt' width of ruler when non-zero */
-EXTERN int sc_col; /* column for shown command */
+EXTERN int ru_col; // column for ruler
+EXTERN int ru_wid; // 'rulerfmt' width of ruler when non-zero
+EXTERN int sc_col; // column for shown command
-//
// When starting or exiting some things are done differently (e.g. screen
// updating).
-//
// First NO_SCREEN, then NO_BUFFERS, then 0 when startup finished.
EXTERN int starting INIT(= NO_SCREEN);
// true when planning to exit. Might keep running if there is a changed buffer.
EXTERN bool exiting INIT(= false);
+// internal value of v:dying
+EXTERN int v_dying INIT(= 0);
// is stdin a terminal?
EXTERN int stdin_isatty INIT(= true);
// is stdout a terminal?
@@ -546,98 +523,78 @@ EXTERN int VIsual_select INIT(= false);
EXTERN int VIsual_reselect;
/// Type of Visual mode.
EXTERN int VIsual_mode INIT(= 'v');
-/// TRUE when redoing Visual.
+/// true when redoing Visual.
EXTERN int redo_VIsual_busy INIT(= false);
/// When pasting text with the middle mouse button in visual mode with
/// restart_edit set, remember where it started so we can set Insstart.
EXTERN pos_T where_paste_started;
-/*
- * This flag is used to make auto-indent work right on lines where only a
- * <RETURN> or <ESC> is typed. It is set when an auto-indent is done, and
- * reset when any other editing is done on the line. If an <ESC> or <RETURN>
- * is received, and did_ai is TRUE, the line is truncated.
- */
+// This flag is used to make auto-indent work right on lines where only a
+// <RETURN> or <ESC> is typed. It is set when an auto-indent is done, and
+// reset when any other editing is done on the line. If an <ESC> or <RETURN>
+// is received, and did_ai is true, the line is truncated.
EXTERN bool did_ai INIT(= false);
-/*
- * Column of first char after autoindent. 0 when no autoindent done. Used
- * when 'backspace' is 0, to avoid backspacing over autoindent.
- */
+// Column of first char after autoindent. 0 when no autoindent done. Used
+// when 'backspace' is 0, to avoid backspacing over autoindent.
EXTERN colnr_T ai_col INIT(= 0);
-/*
- * This is a character which will end a start-middle-end comment when typed as
- * the first character on a new line. It is taken from the last character of
- * the "end" comment leader when the COM_AUTO_END flag is given for that
- * comment end in 'comments'. It is only valid when did_ai is TRUE.
- */
+// This is a character which will end a start-middle-end comment when typed as
+// the first character on a new line. It is taken from the last character of
+// the "end" comment leader when the COM_AUTO_END flag is given for that
+// comment end in 'comments'. It is only valid when did_ai is true.
EXTERN int end_comment_pending INIT(= NUL);
-/*
- * This flag is set after a ":syncbind" to let the check_scrollbind() function
- * know that it should not attempt to perform scrollbinding due to the scroll
- * that was a result of the ":syncbind." (Otherwise, check_scrollbind() will
- * undo some of the work done by ":syncbind.") -ralston
- */
-EXTERN int did_syncbind INIT(= FALSE);
-
-/*
- * This flag is set when a smart indent has been performed. When the next typed
- * character is a '{' the inserted tab will be deleted again.
- */
+// This flag is set after a ":syncbind" to let the check_scrollbind() function
+// know that it should not attempt to perform scrollbinding due to the scroll
+// that was a result of the ":syncbind." (Otherwise, check_scrollbind() will
+// undo some of the work done by ":syncbind.") -ralston
+EXTERN int did_syncbind INIT(= false);
+
+// This flag is set when a smart indent has been performed. When the next typed
+// character is a '{' the inserted tab will be deleted again.
EXTERN bool did_si INIT(= false);
-/*
- * This flag is set after an auto indent. If the next typed character is a '}'
- * one indent will be removed.
- */
+// This flag is set after an auto indent. If the next typed character is a '}'
+// one indent will be removed.
EXTERN bool can_si INIT(= false);
-/*
- * This flag is set after an "O" command. If the next typed character is a '{'
- * one indent will be removed.
- */
+// This flag is set after an "O" command. If the next typed character is a '{'
+// one indent will be removed.
EXTERN bool can_si_back INIT(= false);
// w_cursor before formatting text.
EXTERN pos_T saved_cursor INIT(= { 0, 0, 0 });
-/*
- * Stuff for insert mode.
- */
-EXTERN pos_T Insstart; /* This is where the latest
- * insert/append mode started. */
+// Stuff for insert mode.
+EXTERN pos_T Insstart; // This is where the latest
+ // insert/append mode started.
// This is where the latest insert/append mode started. In contrast to
// Insstart, this won't be reset by certain keys and is needed for
// op_insert(), to detect correctly where inserting by the user started.
EXTERN pos_T Insstart_orig;
-/*
- * Stuff for VREPLACE mode.
- */
-EXTERN int orig_line_count INIT(= 0); /* Line count when "gR" started */
-EXTERN int vr_lines_changed INIT(= 0); /* #Lines changed by "gR" so far */
+// Stuff for VREPLACE mode.
+EXTERN int orig_line_count INIT(= 0); // Line count when "gR" started
+EXTERN int vr_lines_changed INIT(= 0); // #Lines changed by "gR" so far
// increase around internal delete/replace
EXTERN int inhibit_delete_count INIT(= 0);
-/*
- * These flags are set based upon 'fileencoding'.
- * Note that "enc_utf8" is also set for "unicode", because the characters are
- * internally stored as UTF-8 (to avoid trouble with NUL bytes).
- */
-# define DBCS_JPN 932 /* japan */
-# define DBCS_JPNU 9932 /* euc-jp */
-# define DBCS_KOR 949 /* korea */
-# define DBCS_KORU 9949 /* euc-kr */
-# define DBCS_CHS 936 /* chinese */
-# define DBCS_CHSU 9936 /* euc-cn */
-# define DBCS_CHT 950 /* taiwan */
-# define DBCS_CHTU 9950 /* euc-tw */
-# define DBCS_2BYTE 1 /* 2byte- */
+// These flags are set based upon 'fileencoding'.
+// Note that "enc_utf8" is also set for "unicode", because the characters are
+// internally stored as UTF-8 (to avoid trouble with NUL bytes).
+# define DBCS_JPN 932 // japan
+# define DBCS_JPNU 9932 // euc-jp
+# define DBCS_KOR 949 // korea
+# define DBCS_KORU 9949 // euc-kr
+# define DBCS_CHS 936 // chinese
+# define DBCS_CHSU 9936 // euc-cn
+# define DBCS_CHT 950 // taiwan
+# define DBCS_CHTU 9950 // euc-tw
+# define DBCS_2BYTE 1 // 2byte-
# define DBCS_DEBUG -1
// mbyte flags that used to depend on 'encoding'. These are now deprecated, as
@@ -678,40 +635,40 @@ EXTERN int u_sync_once INIT(= 0); // Call u_sync() once when evaluating
EXTERN bool force_restart_edit INIT(= false); // force restart_edit after
// ex_normal returns
-EXTERN int restart_edit INIT(= 0); /* call edit when next cmd finished */
-EXTERN int arrow_used; /* Normally FALSE, set to TRUE after
- * hitting cursor key in insert mode.
- * Used by vgetorpeek() to decide when
- * to call u_sync() */
-EXTERN int ins_at_eol INIT(= FALSE); /* put cursor after eol when
- restarting edit after CTRL-O */
+EXTERN int restart_edit INIT(= 0); // call edit when next cmd finished
+EXTERN int arrow_used; // Normally false, set to true after
+ // hitting cursor key in insert mode.
+ // Used by vgetorpeek() to decide when
+ // to call u_sync()
+EXTERN bool ins_at_eol INIT(= false); // put cursor after eol when
+ // restarting edit after CTRL-O
EXTERN char_u *edit_submode INIT(= NULL); // msg for CTRL-X submode
EXTERN char_u *edit_submode_pre INIT(= NULL); // prepended to edit_submode
EXTERN char_u *edit_submode_extra INIT(= NULL); // appended to edit_submode
EXTERN hlf_T edit_submode_highl; // highl. method for extra info
-EXTERN int no_abbr INIT(= TRUE); /* TRUE when no abbreviations loaded */
+EXTERN int no_abbr INIT(= true); // true when no abbreviations loaded
EXTERN int mapped_ctrl_c INIT(= 0); // Modes where CTRL-C is mapped.
-EXTERN cmdmod_T cmdmod; /* Ex command modifiers */
+EXTERN cmdmod_T cmdmod; // Ex command modifiers
EXTERN int msg_silent INIT(= 0); // don't print messages
EXTERN int emsg_silent INIT(= 0); // don't print error messages
EXTERN bool emsg_noredir INIT(= false); // don't redirect error messages
EXTERN bool cmd_silent INIT(= false); // don't echo the command line
-/* Values for swap_exists_action: what to do when swap file already exists */
-#define SEA_NONE 0 /* don't use dialog */
-#define SEA_DIALOG 1 /* use dialog when possible */
-#define SEA_QUIT 2 /* quit editing the file */
-#define SEA_RECOVER 3 /* recover the file */
+// Values for swap_exists_action: what to do when swap file already exists
+#define SEA_NONE 0 // don't use dialog
+#define SEA_DIALOG 1 // use dialog when possible
+#define SEA_QUIT 2 // quit editing the file
+#define SEA_RECOVER 3 // recover the file
EXTERN int swap_exists_action INIT(= SEA_NONE);
-/* For dialog when swap file already
- * exists. */
-EXTERN int swap_exists_did_quit INIT(= FALSE);
-/* Selected "quit" at the dialog. */
+// For dialog when swap file already
+// exists.
+EXTERN int swap_exists_did_quit INIT(= false);
+// Selected "quit" at the dialog.
EXTERN char_u IObuff[IOSIZE]; ///< Buffer for sprintf, I/O, etc.
EXTERN char_u NameBuff[MAXPATHL]; ///< Buffer for expanding file names
@@ -724,11 +681,11 @@ IOSIZE
#endif
];
-/* When non-zero, postpone redrawing. */
+// When non-zero, postpone redrawing.
EXTERN int RedrawingDisabled INIT(= 0);
-EXTERN int readonlymode INIT(= FALSE); /* Set to TRUE for "view" */
-EXTERN int recoverymode INIT(= FALSE); /* Set to TRUE for "-r" option */
+EXTERN int readonlymode INIT(= false); // Set to true for "view"
+EXTERN int recoverymode INIT(= false); // Set to true for "-r" option
// typeahead buffer
EXTERN typebuf_T typebuf INIT(= { NULL, NULL, 0, 0, 0, 0, 0, 0, 0 });
@@ -738,7 +695,7 @@ EXTERN int ex_normal_lock INIT(= 0); // forbid use of ex_normal()
EXTERN int ignore_script INIT(= false); // ignore script input
EXTERN int stop_insert_mode; // for ":stopinsert" and 'insertmode'
EXTERN bool KeyTyped; // true if user typed current char
-EXTERN int KeyStuffed; // TRUE if current char from stuffbuf
+EXTERN int KeyStuffed; // true if current char from stuffbuf
EXTERN int maptick INIT(= 0); // tick for each non-mapped char
EXTERN int must_redraw INIT(= 0); // type of redraw necessary
@@ -754,21 +711,20 @@ EXTERN FILE *scriptout INIT(= NULL); ///< Stream to write script to.
// volatile because it is used in a signal handler.
EXTERN volatile int got_int INIT(= false); // set to true when interrupt
// signal occurred
-EXTERN int bangredo INIT(= FALSE); /* set to TRUE with ! command */
-EXTERN int searchcmdlen; /* length of previous search cmd */
-EXTERN int reg_do_extmatch INIT(= 0); /* Used when compiling regexp:
- * REX_SET to allow \z\(...\),
- * REX_USE to allow \z\1 et al. */
-EXTERN reg_extmatch_T *re_extmatch_in INIT(= NULL); /* Used by vim_regexec():
- * strings for \z\1...\z\9 */
-EXTERN reg_extmatch_T *re_extmatch_out INIT(= NULL); /* Set by vim_regexec()
- * to store \z\(...\) matches */
+EXTERN int bangredo INIT(= false); // set to true with ! command
+EXTERN int searchcmdlen; // length of previous search cmd
+EXTERN int reg_do_extmatch INIT(= 0); // Used when compiling regexp:
+ // REX_SET to allow \z\(...\),
+ // REX_USE to allow \z\1 et al.
+// Used by vim_regexec(): strings for \z\1...\z\9
+EXTERN reg_extmatch_T *re_extmatch_in INIT(= NULL);
+// Set by vim_regexec() to store \z\(...\) matches
+EXTERN reg_extmatch_T *re_extmatch_out INIT(= NULL);
EXTERN int did_outofmem_msg INIT(= false);
// set after out of memory msg
EXTERN int did_swapwrite_msg INIT(= false);
// set after swap write error msg
-EXTERN int undo_off INIT(= false); // undo switched off for now
EXTERN int global_busy INIT(= 0); // set when :global is executing
EXTERN int listcmd_busy INIT(= false); // set when :argdo, :windo or
// :bufdo is executing
@@ -782,23 +738,25 @@ EXTERN int autocmd_bufnr INIT(= 0); // fnum for <abuf> on cmdline
EXTERN char_u *autocmd_match INIT(= NULL); // name for <amatch> on cmdline
EXTERN int did_cursorhold INIT(= false); // set when CursorHold t'gerd
-EXTERN int postponed_split INIT(= 0); /* for CTRL-W CTRL-] command */
-EXTERN int postponed_split_flags INIT(= 0); /* args for win_split() */
-EXTERN int postponed_split_tab INIT(= 0); /* cmdmod.tab */
-EXTERN int g_do_tagpreview INIT(= 0); /* for tag preview commands:
- height of preview window */
-EXTERN int replace_offset INIT(= 0); /* offset for replace_push() */
+EXTERN int postponed_split INIT(= 0); // for CTRL-W CTRL-] command
+EXTERN int postponed_split_flags INIT(= 0); // args for win_split()
+EXTERN int postponed_split_tab INIT(= 0); // cmdmod.tab
+EXTERN int g_do_tagpreview INIT(= 0); // for tag preview commands:
+ // height of preview window
+EXTERN int g_tag_at_cursor INIT(= false); // whether the tag command comes
+ // from the command line (0) or was
+ // invoked as a normal command (1)
+
+EXTERN int replace_offset INIT(= 0); // offset for replace_push()
EXTERN char_u *escape_chars INIT(= (char_u *)" \t\\\"|");
-/* need backslash in cmd line */
+// need backslash in cmd line
-EXTERN int keep_help_flag INIT(= FALSE); /* doing :ta from help file */
+EXTERN int keep_help_flag INIT(= false); // doing :ta from help file
-/*
- * When a string option is NULL (which only happens in out-of-memory
- * situations), it is set to empty_option, to avoid having to check for NULL
- * everywhere.
- */
+// When a string option is NULL (which only happens in out-of-memory
+// situations), it is set to empty_option, to avoid having to check for NULL
+// everywhere.
EXTERN char_u *empty_option INIT(= (char_u *)"");
EXTERN int redir_off INIT(= false); // no redirection for a moment
@@ -807,10 +765,10 @@ EXTERN int redir_reg INIT(= 0); // message redirection register
EXTERN int redir_vname INIT(= 0); // message redirection variable
EXTERN garray_T *capture_ga INIT(= NULL); // captured output for execute()
-EXTERN char_u langmap_mapchar[256]; /* mapping for language keys */
+EXTERN char_u langmap_mapchar[256]; // mapping for language keys
-EXTERN int save_p_ls INIT(= -1); /* Save 'laststatus' setting */
-EXTERN int save_p_wmh INIT(= -1); /* Save 'winminheight' setting */
+EXTERN int save_p_ls INIT(= -1); // Save 'laststatus' setting
+EXTERN int save_p_wmh INIT(= -1); // Save 'winminheight' setting
EXTERN int wild_menu_showing INIT(= 0);
enum {
WM_SHOWN = 1, ///< wildmenu showing
@@ -819,25 +777,24 @@ enum {
};
-/*
- * Some file names are stored in pathdef.c, which is generated from the
- * Makefile to make their value depend on the Makefile.
- */
+// Some file names are stored in pathdef.c, which is generated from the
+// Makefile to make their value depend on the Makefile.
#ifdef HAVE_PATHDEF
extern char *default_vim_dir;
extern char *default_vimruntime_dir;
+extern char *default_lib_dir;
extern char_u *compiled_user;
extern char_u *compiled_sys;
#endif
-/* When a window has a local directory, the absolute path of the global
- * current directory is stored here (in allocated memory). If the current
- * directory is not a local directory, globaldir is NULL. */
+// When a window has a local directory, the absolute path of the global
+// current directory is stored here (in allocated memory). If the current
+// directory is not a local directory, globaldir is NULL.
EXTERN char_u *globaldir INIT(= NULL);
-/* Whether 'keymodel' contains "stopsel" and "startsel". */
-EXTERN int km_stopsel INIT(= FALSE);
-EXTERN int km_startsel INIT(= FALSE);
+// Whether 'keymodel' contains "stopsel" and "startsel".
+EXTERN int km_stopsel INIT(= false);
+EXTERN int km_startsel INIT(= false);
EXTERN int cedit_key INIT(= -1); ///< key value of 'cedit' option
EXTERN int cmdwin_type INIT(= 0); ///< type of cmdline window or 0
@@ -846,18 +803,16 @@ EXTERN int cmdwin_level INIT(= 0); ///< cmdline recursion level
EXTERN char_u no_lines_msg[] INIT(= N_("--No lines in buffer--"));
-/*
- * When ":global" is used to number of substitutions and changed lines is
- * accumulated until it's finished.
- * Also used for ":spellrepall".
- */
-EXTERN long sub_nsubs; /* total number of substitutions */
-EXTERN linenr_T sub_nlines; /* total number of lines changed */
+// When ":global" is used to number of substitutions and changed lines is
+// accumulated until it's finished.
+// Also used for ":spellrepall".
+EXTERN long sub_nsubs; // total number of substitutions
+EXTERN linenr_T sub_nlines; // total number of lines changed
-/* table to store parsed 'wildmode' */
+// table to store parsed 'wildmode'
EXTERN char_u wim_flags[4];
-/* whether titlestring and iconstring contains statusline syntax */
+// whether titlestring and iconstring contains statusline syntax
# define STL_IN_ICON 1
# define STL_IN_TITLE 2
EXTERN int stl_syntax INIT(= 0);
@@ -865,7 +820,7 @@ EXTERN int stl_syntax INIT(= 0);
// don't use 'hlsearch' temporarily
EXTERN bool no_hlsearch INIT(= false);
-/* Page number used for %N in 'pageheader' and 'guitablabel'. */
+// Page number used for %N in 'pageheader' and 'guitablabel'.
EXTERN linenr_T printer_page_num;
@@ -883,18 +838,16 @@ EXTERN char pseps[2] INIT(= { '\\', 0 }); // normal path separator string
// kNone when no operator is being executed, kFalse otherwise.
EXTERN TriState virtual_op INIT(= kNone);
-/* Display tick, incremented for each call to update_screen() */
+// Display tick, incremented for each call to update_screen()
EXTERN disptick_T display_tick INIT(= 0);
-/* Line in which spell checking wasn't highlighted because it touched the
- * cursor position in Insert mode. */
+// Line in which spell checking wasn't highlighted because it touched the
+// cursor position in Insert mode.
EXTERN linenr_T spell_redraw_lnum INIT(= 0);
-/*
- * The error messages that can be shared are included here.
- * Excluded are errors that are only used once and debugging messages.
- */
+// The error messages that can be shared are included here.
+// Excluded are errors that are only used once and debugging messages.
EXTERN char_u e_abort[] INIT(= N_("E470: Command aborted"));
EXTERN char_u e_afterinit[] INIT(= N_(
"E905: Cannot set this option after startup"));
@@ -985,6 +938,14 @@ EXTERN char_u e_re_damg[] INIT(= N_("E43: Damaged match string"));
EXTERN char_u e_re_corr[] INIT(= N_("E44: Corrupted regexp program"));
EXTERN char_u e_readonly[] INIT(= N_(
"E45: 'readonly' option is set (add ! to override)"));
+EXTERN char_u e_readonlyvar[] INIT(= N_(
+ "E46: Cannot change read-only variable \"%.*s\""));
+EXTERN char_u e_dictreq[] INIT(= N_("E715: Dictionary required"));
+EXTERN char_u e_toomanyarg[] INIT(= N_("E118: Too many arguments for function: %s"));
+EXTERN char_u e_dictkey[] INIT(= N_("E716: Key not present in Dictionary: %s"));
+EXTERN char_u e_listreq[] INIT(= N_("E714: List required"));
+EXTERN char_u e_listdictarg[] INIT(= N_(
+ "E712: Argument of %s must be a List or Dictionary"));
EXTERN char_u e_readerrf[] INIT(= N_("E47: Error while reading errorfile"));
EXTERN char_u e_sandbox[] INIT(= N_("E48: Not allowed in sandbox"));
EXTERN char_u e_secure[] INIT(= N_("E523: Not allowed here"));
@@ -1053,10 +1014,12 @@ EXTERN char_u e_floatexchange[] INIT(=N_(
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 char line_msg[] INIT(= N_(" line "));
+
// For undo we need to know the lowest time possible.
EXTERN time_t starttime;
-EXTERN FILE *time_fd INIT(= NULL); /* where to write startup timing */
+EXTERN FILE *time_fd INIT(= NULL); // where to write startup timing
// Some compilers warn for not using a return value, but in some situations we
// can't do anything useful with the value. Assign to this variable to avoid
@@ -1092,4 +1055,4 @@ typedef enum {
#define MIN_CD_SCOPE kCdScopeWindow
#define MAX_CD_SCOPE kCdScopeGlobal
-#endif /* NVIM_GLOBALS_H */
+#endif // NVIM_GLOBALS_H
diff --git a/src/nvim/grid_defs.h b/src/nvim/grid_defs.h
index 9e588d0387..e14aae73d8 100644
--- a/src/nvim/grid_defs.h
+++ b/src/nvim/grid_defs.h
@@ -11,14 +11,14 @@
// The characters and attributes drawn on grids.
typedef char_u schar_T[(MAX_MCO+1) * 4 + 1];
-typedef int16_t sattr_T;
+typedef int sattr_T;
/// ScreenGrid represents a resizable rectuangular grid displayed by UI clients.
///
/// chars[] contains the UTF-8 text that is currently displayed on the grid.
/// It is stored as a single block of cells. When redrawing a part of the grid,
/// the new state can be compared with the existing state of the grid. This way
-/// we can avoid sending bigger updates than neccessary to the Ul layer.
+/// we can avoid sending bigger updates than necessary to the Ul layer.
///
/// Screen cells are stored as NUL-terminated UTF-8 strings, and a cell can
/// contain up to MAX_MCO composing characters after the base character.
diff --git a/src/nvim/hardcopy.c b/src/nvim/hardcopy.c
index 4b361d2d45..4a64cc31b1 100644
--- a/src/nvim/hardcopy.c
+++ b/src/nvim/hardcopy.c
@@ -162,21 +162,21 @@ static option_table_T mbfont_opts[OPT_MBFONT_NUM_OPTIONS] =
* These values determine the print position on a page.
*/
typedef struct {
- int lead_spaces; /* remaining spaces for a TAB */
- int print_pos; /* virtual column for computing TABs */
- colnr_T column; /* byte column */
- linenr_T file_line; /* line nr in the buffer */
- size_t bytes_printed; /* bytes printed so far */
- int ff; /* seen form feed character */
+ int lead_spaces; // remaining spaces for a TAB
+ int print_pos; // virtual column for computing TABs
+ colnr_T column; // byte column
+ linenr_T file_line; // line nr in the buffer
+ size_t bytes_printed; // bytes printed so far
+ int ff; // seen form feed character
} prt_pos_T;
struct prt_mediasize_S {
char *name;
- double width; /* width and height in points for portrait */
+ double width; // width and height in points for portrait
double height;
};
-/* PS font names, must be in Roman, Bold, Italic, Bold-Italic order */
+// PS font names, must be in Roman, Bold, Italic, Bold-Italic order
struct prt_ps_font_S {
int wx;
int uline_offset;
@@ -200,7 +200,7 @@ struct prt_ps_charset_S {
int has_charset;
};
-/* Collections of encodings and charsets for multi-byte printing */
+// Collections of encodings and charsets for multi-byte printing
struct prt_ps_mbfont_S {
int num_encodings;
struct prt_ps_encoding_S *encodings;
@@ -210,10 +210,25 @@ struct prt_ps_mbfont_S {
char *defcs;
};
+// Types of PS resource file currently used
+typedef enum {
+ PRT_RESOURCE_TYPE_PROCSET = 0,
+ PRT_RESOURCE_TYPE_ENCODING = 1,
+ PRT_RESOURCE_TYPE_CMAP = 2,
+} PrtResourceType;
+
+// String versions of PS resource types
+static const char *const prt_resource_types[] =
+{
+ [PRT_RESOURCE_TYPE_PROCSET] = "procset",
+ [PRT_RESOURCE_TYPE_ENCODING] = "encoding",
+ [PRT_RESOURCE_TYPE_CMAP] = "cmap",
+};
+
struct prt_ps_resource_S {
char_u name[64];
char_u filename[MAXPATHL + 1];
- int type;
+ PrtResourceType type;
char_u title[256];
char_u version[256];
};
@@ -361,9 +376,10 @@ static uint32_t darken_rgb(uint32_t rgb)
static uint32_t prt_get_term_color(int colorindex)
{
- /* TODO: Should check for xterm with 88 or 256 colors. */
- if (t_colors > 8)
+ // TODO(vim): Should check for xterm with 88 or 256 colors.
+ if (t_colors > 8) {
return cterm_color_16[colorindex % 16];
+ }
return cterm_color_8[colorindex % 8];
}
@@ -535,7 +551,7 @@ static void prt_header(prt_settings_T *const psettings, const int pagenum,
p_header, use_sandbox,
' ', width, NULL, NULL);
- /* Reset line numbers */
+ // Reset line numbers
curwin->w_cursor.lnum = tmp_lnum;
curwin->w_topline = tmp_topline;
curwin->w_botline = tmp_botline;
@@ -602,7 +618,7 @@ void ex_hardcopy(exarg_T *eap)
if (*eap->arg == '>') {
char_u *errormsg = NULL;
- /* Expand things like "%.ps". */
+ // Expand things like "%.ps".
if (expand_filename(eap, eap->cmdlinep, &errormsg) == FAIL) {
if (errormsg != NULL)
EMSG(errormsg);
@@ -666,7 +682,7 @@ void ex_hardcopy(exarg_T *eap)
goto print_fail_no_begin;
}
- /* Set colors and font to normal. */
+ // Set colors and font to normal.
curr_bg = 0xffffffff;
curr_fg = 0xffffffff;
curr_italic = kNone;
@@ -691,8 +707,8 @@ void ex_hardcopy(exarg_T *eap)
for (collated_copies = 0;
collated_copies < settings.n_collated_copies;
collated_copies++) {
- prt_pos_T prtpos; /* current print position */
- prt_pos_T page_prtpos; /* print position at page start */
+ prt_pos_T prtpos; // current print position
+ prt_pos_T page_prtpos; // print position at page start
int side;
memset(&page_prtpos, 0, sizeof(prt_pos_T));
@@ -700,7 +716,7 @@ void ex_hardcopy(exarg_T *eap)
prtpos = page_prtpos;
if (jobsplit && collated_copies > 0) {
- /* Splitting jobs: Stop a previous job and start a new one. */
+ // Splitting jobs: Stop a previous job and start a new one.
mch_print_end(&settings);
if (!mch_print_begin(&settings))
goto print_fail_no_begin;
@@ -717,7 +733,7 @@ void ex_hardcopy(exarg_T *eap)
for (uncollated_copies = 0;
uncollated_copies < settings.n_uncollated_copies;
uncollated_copies++) {
- /* Set the print position to the start of this page. */
+ // Set the print position to the start of this page.
prtpos = page_prtpos;
/*
@@ -728,7 +744,7 @@ void ex_hardcopy(exarg_T *eap)
* Print one page.
*/
- /* Check for interrupt character every page. */
+ // Check for interrupt character every page.
os_breakcheck();
if (got_int || settings.user_abort)
goto print_fail;
@@ -759,11 +775,12 @@ void ex_hardcopy(exarg_T *eap)
prtpos.column = hardcopy_line(&settings,
page_line, &prtpos);
if (prtpos.column == 0) {
- /* finished a file line */
+ // finished a file line
prtpos.bytes_printed +=
STRLEN(skipwhite(ml_get(prtpos.file_line)));
- if (++prtpos.file_line > eap->line2)
- break; /* reached the end */
+ if (++prtpos.file_line > eap->line2) {
+ break; // reached the end
+ }
} else if (prtpos.ff) {
/* Line had a formfeed in it - start new page but
* stay on the current line */
@@ -771,10 +788,12 @@ void ex_hardcopy(exarg_T *eap)
}
}
- if (!mch_print_end_page())
+ if (!mch_print_end_page()) {
goto print_fail;
- if (prtpos.file_line > eap->line2)
- break; /* reached the end */
+ }
+ if (prtpos.file_line > eap->line2) {
+ break; // reached the end
+ }
}
/*
@@ -791,7 +810,7 @@ void ex_hardcopy(exarg_T *eap)
if (settings.duplex && prtpos.file_line <= eap->line2)
++page_count;
- /* Remember the position where the next page starts. */
+ // Remember the position where the next page starts.
page_prtpos = prtpos;
}
@@ -855,7 +874,7 @@ static colnr_T hardcopy_line(prt_settings_T *psettings, int page_line, prt_pos_T
id = syn_get_final_id(id);
else
id = 0;
- /* Get the line again, a multi-line regexp may invalidate it. */
+ // Get the line again, a multi-line regexp may invalidate it.
line = ml_get(ppos->file_line);
if (id != current_syn_id) {
@@ -881,9 +900,10 @@ static colnr_T hardcopy_line(prt_settings_T *psettings, int page_line, prt_pos_T
if (need_break)
break;
}
- /* Keep the TAB if we didn't finish it. */
- if (need_break && tab_spaces > 0)
+ // Keep the TAB if we didn't finish it.
+ if (need_break && tab_spaces > 0) {
break;
+ }
} else if (line[col] == FF
&& printer_opts[OPT_PRINT_FORMFEED].present
&& TOLOWER_ASC(printer_opts[OPT_PRINT_FORMFEED].string[0])
@@ -942,7 +962,7 @@ static colnr_T hardcopy_line(prt_settings_T *psettings, int page_line, prt_pos_T
* http://www.adobe.com
*/
-#define PRT_PS_DEFAULT_DPI (72) /* Default user space resolution */
+#define PRT_PS_DEFAULT_DPI (72) // Default user space resolution
#define PRT_PS_DEFAULT_FONTSIZE (10)
#define PRT_PS_DEFAULT_BUFFER_SIZE (80)
@@ -951,20 +971,20 @@ static colnr_T hardcopy_line(prt_settings_T *psettings, int page_line, prt_pos_T
static struct prt_mediasize_S prt_mediasize[] =
{
- {"A4", 595.0, 842.0},
- {"letter", 612.0, 792.0},
- {"10x14", 720.0, 1008.0},
- {"A3", 842.0, 1191.0},
- {"A5", 420.0, 595.0},
- {"B4", 729.0, 1032.0},
- {"B5", 516.0, 729.0},
- {"executive", 522.0, 756.0},
- {"folio", 595.0, 935.0},
- {"ledger", 1224.0, 792.0}, /* Yes, it is wider than taller! */
- {"legal", 612.0, 1008.0},
- {"quarto", 610.0, 780.0},
- {"statement", 396.0, 612.0},
- {"tabloid", 792.0, 1224.0}
+ { "A4", 595.0, 842.0 },
+ { "letter", 612.0, 792.0 },
+ { "10x14", 720.0, 1008.0 },
+ { "A3", 842.0, 1191.0 },
+ { "A5", 420.0, 595.0 },
+ { "B4", 729.0, 1032.0 },
+ { "B5", 516.0, 729.0 },
+ { "executive", 522.0, 756.0 },
+ { "folio", 595.0, 935.0 },
+ { "ledger", 1224.0, 792.0 }, // Yes, it is wider than taller!
+ { "legal", 612.0, 1008.0 },
+ { "quarto", 610.0, 780.0 },
+ { "statement", 396.0, 612.0 },
+ { "tabloid", 792.0, 1224.0 }
};
#define PRT_PS_FONT_ROMAN (0)
@@ -972,7 +992,7 @@ static struct prt_mediasize_S prt_mediasize[] =
#define PRT_PS_FONT_OBLIQUE (2)
#define PRT_PS_FONT_BOLDOBLIQUE (3)
-/* Standard font metrics for Courier family */
+// Standard font metrics for Courier family
static struct prt_ps_font_S prt_ps_courier_font =
{
600,
@@ -981,7 +1001,7 @@ static struct prt_ps_font_S prt_ps_courier_font =
{"Courier", "Courier-Bold", "Courier-Oblique", "Courier-BoldOblique"}
};
-/* Generic font metrics for multi-byte fonts */
+// Generic font metrics for multi-byte fonts
static struct prt_ps_font_S prt_ps_mb_font =
{
1000,
@@ -990,9 +1010,8 @@ static struct prt_ps_font_S prt_ps_mb_font =
{NULL, NULL, NULL, NULL}
};
-/* Pointer to current font set being used */
-static struct prt_ps_font_S* prt_ps_font;
-
+// Pointer to current font set being used
+static struct prt_ps_font_S *prt_ps_font;
#define CS_JIS_C_1978 (0x01)
#define CS_JIS_X_1983 (0x02)
@@ -1003,7 +1022,7 @@ static struct prt_ps_font_S* prt_ps_font;
#define CS_KANJITALK6 (0x40)
#define CS_KANJITALK7 (0x80)
-/* Japanese encodings and charsets */
+// Japanese encodings and charsets
static struct prt_ps_encoding_S j_encodings[] =
{
{"iso-2022-jp", NULL, (CS_JIS_C_1978|CS_JIS_X_1983|CS_JIS_X_1990|
@@ -1035,7 +1054,7 @@ static struct prt_ps_charset_S j_charsets[] =
#define CS_GBK (0x20)
#define CS_SC_ISO10646 (0x40)
-/* Simplified Chinese encodings and charsets */
+// Simplified Chinese encodings and charsets
static struct prt_ps_encoding_S sc_encodings[] =
{
{"iso-2022", NULL, (CS_GB_2312_80|CS_GBT_12345_90)},
@@ -1071,7 +1090,7 @@ static struct prt_ps_charset_S sc_charsets[] =
#define CS_DLHKS (0x800)
#define CS_TC_ISO10646 (0x1000)
-/* Traditional Chinese encodings and charsets */
+// Traditional Chinese encodings and charsets
static struct prt_ps_encoding_S tc_encodings[] =
{
{"iso-2022", NULL, (CS_CNS_PLANE_1|CS_CNS_PLANE_2)},
@@ -1108,7 +1127,7 @@ static struct prt_ps_charset_S tc_charsets[] =
#define CS_KR_X_1992_MS (0x04)
#define CS_KR_ISO10646 (0x08)
-/* Korean encodings and charsets */
+// Korean encodings and charsets
static struct prt_ps_encoding_S k_encodings[] =
{
{"iso-2022-kr", NULL, CS_KR_X_1992},
@@ -1167,11 +1186,6 @@ static struct prt_ps_mbfont_S prt_ps_mbfonts[] =
}
};
-/* Types of PS resource file currently used */
-#define PRT_RESOURCE_TYPE_PROCSET (0)
-#define PRT_RESOURCE_TYPE_ENCODING (1)
-#define PRT_RESOURCE_TYPE_CMAP (2)
-
/* The PS prolog file version number has to match - if the prolog file is
* updated, increment the number in the file and here. Version checking was
* added as of VIM 6.2.
@@ -1185,17 +1199,7 @@ static struct prt_ps_mbfont_S prt_ps_mbfonts[] =
#define PRT_PROLOG_VERSION ((char_u *)"1.4")
#define PRT_CID_PROLOG_VERSION ((char_u *)"1.0")
-/* String versions of PS resource types - indexed by constants above so don't
- * re-order!
- */
-static char *prt_resource_types[] =
-{
- "procset",
- "encoding",
- "cmap"
-};
-
-/* Strings to look for in a PS resource file */
+// Strings to look for in a PS resource file
#define PRT_RESOURCE_HEADER "%!PS-Adobe-"
#define PRT_RESOURCE_RESOURCE "Resource-"
#define PRT_RESOURCE_PROCSET "ProcSet"
@@ -1255,20 +1259,20 @@ static double prt_pos_y_moveto = 0.0;
* Various control variables used to decide when and how to change the
* PostScript graphics state.
*/
-static int prt_need_moveto;
-static int prt_do_moveto;
-static int prt_need_font;
+static bool prt_need_moveto;
+static bool prt_do_moveto;
+static bool prt_need_font;
static int prt_font;
-static int prt_need_underline;
+static bool prt_need_underline;
static TriState prt_underline;
static TriState prt_do_underline;
-static int prt_need_fgcol;
+static bool prt_need_fgcol;
static uint32_t prt_fgcol;
-static int prt_need_bgcol;
-static int prt_do_bgcol;
+static bool prt_need_bgcol;
+static bool prt_do_bgcol;
static uint32_t prt_bgcol;
static uint32_t prt_new_bgcol;
-static int prt_attribute_change;
+static bool prt_attribute_change;
static double prt_text_run;
static int prt_page_num;
static int prt_bufsiz;
@@ -1296,8 +1300,8 @@ static int prt_out_mbyte;
static int prt_custom_cmap;
static char prt_cmap[80];
static int prt_use_courier;
-static int prt_in_ascii;
-static int prt_half_width;
+static bool prt_in_ascii;
+static bool prt_half_width;
static char *prt_ascii_encoding;
static char_u prt_hexchar[] = "0123456789abcdef";
@@ -1416,18 +1420,19 @@ static void prt_write_real(double val, int prec)
int fraction;
prt_real_bits(val, prec, &integer, &fraction);
- /* Emit integer part */
- sprintf((char *)prt_line_buffer, "%d", integer);
+ // Emit integer part
+ snprintf((char *)prt_line_buffer, sizeof(prt_line_buffer), "%d", integer);
prt_write_file(prt_line_buffer);
- /* Only emit fraction if necessary */
+ // Only emit fraction if necessary
if (fraction != 0) {
- /* Remove any trailing zeros */
+ // Remove any trailing zeros
while ((fraction % 10) == 0) {
prec--;
fraction /= 10;
}
- /* Emit fraction left padded with zeros */
- sprintf((char *)prt_line_buffer, ".%0*d", prec, fraction);
+ // Emit fraction left padded with zeros
+ snprintf((char *)prt_line_buffer, sizeof(prt_line_buffer), ".%0*d",
+ prec, fraction);
prt_write_file(prt_line_buffer);
}
sprintf((char *)prt_line_buffer, " ");
@@ -1447,13 +1452,13 @@ static void prt_def_var(char *name, double value, int prec)
prt_write_file(prt_line_buffer);
}
-/* Convert size from font space to user space at current font scale */
+// Convert size from font space to user space at current font scale
#define PRT_PS_FONT_TO_USER(scale, size) ((size) * ((scale)/1000.0))
static void prt_flush_buffer(void)
{
if (!GA_EMPTY(&prt_ps_buffer)) {
- /* Any background color must be drawn first */
+ // Any background color must be drawn first
if (prt_do_bgcol && (prt_new_bgcol != PRCOLOR_WHITE)) {
unsigned int r, g, b;
@@ -1461,14 +1466,14 @@ static void prt_flush_buffer(void)
prt_write_real(prt_pos_x_moveto, 2);
prt_write_real(prt_pos_y_moveto, 2);
prt_write_string("m\n");
- prt_do_moveto = FALSE;
+ prt_do_moveto = false;
}
- /* Size of rect of background color on which text is printed */
+ // Size of rect of background color on which text is printed
prt_write_real(prt_text_run, 2);
prt_write_real(prt_line_height, 2);
- /* Lastly add the color of the background */
+ // Lastly add the color of the background
r = (prt_new_bgcol & 0xff0000) >> 16;
g = (prt_new_bgcol & 0xff00) >> 8;
b = prt_new_bgcol & 0xff;
@@ -1485,10 +1490,10 @@ static void prt_flush_buffer(void)
prt_write_real(prt_pos_x_moveto, 2);
prt_write_real(prt_pos_y_moveto, 2);
prt_write_string("m\n");
- prt_do_moveto = FALSE;
+ prt_do_moveto = false;
}
- /* Underline length of text run */
+ // Underline length of text run
prt_write_real(prt_text_run, 2);
prt_write_string("ul\n");
}
@@ -1503,16 +1508,16 @@ static void prt_flush_buffer(void)
prt_write_string(">");
else
prt_write_string(")");
- /* Add a moveto if need be and use the appropriate show procedure */
+ // Add a moveto if need be and use the appropriate show procedure
if (prt_do_moveto) {
prt_write_real(prt_pos_x_moveto, 2);
prt_write_real(prt_pos_y_moveto, 2);
- /* moveto and a show */
+ // moveto and a show
prt_write_string("ms\n");
- prt_do_moveto = FALSE;
- } else /* Simple show */
+ prt_do_moveto = false;
+ } else { // Simple show
prt_write_string("s\n");
-
+ }
ga_clear(&prt_ps_buffer);
ga_init(&prt_ps_buffer, (int)sizeof(char), prt_bufsiz);
}
@@ -1536,7 +1541,7 @@ static int prt_find_resource(char *name, struct prt_ps_resource_S *resource)
buffer = xmallocz(MAXPATHL);
STRLCPY(resource->name, name, 64);
- /* Look for named resource file in runtimepath */
+ // Look for named resource file in runtimepath
STRCPY(buffer, "print");
add_pathsep((char *)buffer);
xstrlcat((char *)buffer, name, MAXPATHL);
@@ -1548,7 +1553,7 @@ static int prt_find_resource(char *name, struct prt_ps_resource_S *resource)
return retval;
}
-/* PS CR and LF characters have platform independent values */
+// PS CR and LF characters have platform independent values
#define PSLF (0x0a)
#define PSCR (0x0d)
@@ -1558,7 +1563,7 @@ static int prt_resfile_next_line(void)
{
int idx;
- /* Move to start of next line and then find end of line */
+ // Move to start of next line and then find end of line
idx = prt_resfile.line_end + 1;
while (idx < prt_resfile.len) {
if (prt_resfile.buffer[idx] != PSLF && prt_resfile.buffer[idx] != PSCR)
@@ -1577,12 +1582,13 @@ static int prt_resfile_next_line(void)
return idx < prt_resfile.len;
}
-static int prt_resfile_strncmp(int offset, char *string, int len)
+static int prt_resfile_strncmp(int offset, const char *string, int len)
+ FUNC_ATTR_NONNULL_ALL
{
- /* Force not equal if string is longer than remainder of line */
- if (len > (prt_resfile.line_end - (prt_resfile.line_start + offset)))
+ // Force not equal if string is longer than remainder of line
+ if (len > (prt_resfile.line_end - (prt_resfile.line_start + offset))) {
return 1;
-
+ }
return STRNCMP(&prt_resfile.buffer[prt_resfile.line_start + offset],
string, len);
}
@@ -1615,178 +1621,182 @@ static int prt_resfile_skip_ws(int offset)
/* prt_next_dsc() - returns detail on next DSC comment line found. Returns true
* if a DSC comment is found, else false */
-static int prt_next_dsc(struct prt_dsc_line_S *p_dsc_line)
+static bool prt_next_dsc(struct prt_dsc_line_S *p_dsc_line)
+ FUNC_ATTR_NONNULL_ALL
{
int comment;
int offset;
- /* Move to start of next line */
- if (!prt_resfile_next_line())
- return FALSE;
-
- /* DSC comments always start %% */
- if (prt_resfile_strncmp(0, "%%", 2) != 0)
- return FALSE;
-
- /* Find type of DSC comment */
- for (comment = 0; comment < (int)ARRAY_SIZE(prt_dsc_table); comment++)
+ // Move to start of next line
+ if (!prt_resfile_next_line()) {
+ return false;
+ }
+ // DSC comments always start %%
+ if (prt_resfile_strncmp(0, "%%", 2) != 0) {
+ return false;
+ }
+ // Find type of DSC comment
+ for (comment = 0; comment < (int)ARRAY_SIZE(prt_dsc_table); comment++) {
if (prt_resfile_strncmp(0, prt_dsc_table[comment].string,
- prt_dsc_table[comment].len) == 0)
+ prt_dsc_table[comment].len) == 0) {
break;
-
+ }
+ }
if (comment != ARRAY_SIZE(prt_dsc_table)) {
- /* Return type of comment */
+ // Return type of comment
p_dsc_line->type = prt_dsc_table[comment].type;
offset = prt_dsc_table[comment].len;
} else {
- /* Unrecognised DSC comment, skip to ws after comment leader */
+ // Unrecognised DSC comment, skip to ws after comment leader
p_dsc_line->type = PRT_DSC_MISC_TYPE;
offset = prt_resfile_skip_nonws(0);
- if (offset == -1)
- return FALSE;
+ if (offset == -1) {
+ return false;
+ }
}
- /* Skip ws to comment value */
+ // Skip ws to comment value
offset = prt_resfile_skip_ws(offset);
- if (offset == -1)
- return FALSE;
-
+ if (offset == -1) {
+ return false;
+ }
p_dsc_line->string = &prt_resfile.buffer[prt_resfile.line_start + offset];
p_dsc_line->len = prt_resfile.line_end - (prt_resfile.line_start + offset);
- return TRUE;
+ return true;
}
/* Improved hand crafted parser to get the type, title, and version number of a
* PS resource file so the file details can be added to the DSC header comments.
*/
-static int prt_open_resource(struct prt_ps_resource_S *resource)
+static bool prt_open_resource(struct prt_ps_resource_S *resource)
+ FUNC_ATTR_NONNULL_ALL
{
- int offset;
- int seen_all;
- int seen_title;
- int seen_version;
- FILE *fd_resource;
struct prt_dsc_line_S dsc_line;
- fd_resource = os_fopen((char *)resource->filename, READBIN);
+ FILE *fd_resource = os_fopen((char *)resource->filename, READBIN);
if (fd_resource == NULL) {
EMSG2(_("E624: Can't open file \"%s\""), resource->filename);
- return FALSE;
+ return false;
}
memset(prt_resfile.buffer, NUL, PRT_FILE_BUFFER_LEN);
- /* Parse first line to ensure valid resource file */
+ // Parse first line to ensure valid resource file
prt_resfile.len = (int)fread((char *)prt_resfile.buffer, sizeof(char_u),
PRT_FILE_BUFFER_LEN, fd_resource);
if (ferror(fd_resource)) {
EMSG2(_("E457: Can't read PostScript resource file \"%s\""),
resource->filename);
fclose(fd_resource);
- return FALSE;
+ return false;
}
fclose(fd_resource);
prt_resfile.line_end = -1;
prt_resfile.line_start = 0;
- if (!prt_resfile_next_line())
- return FALSE;
-
- offset = 0;
+ if (!prt_resfile_next_line()) {
+ return false;
+ }
+ int offset = 0;
if (prt_resfile_strncmp(offset, PRT_RESOURCE_HEADER,
(int)STRLEN(PRT_RESOURCE_HEADER)) != 0) {
EMSG2(_("E618: file \"%s\" is not a PostScript resource file"),
- resource->filename);
- return FALSE;
+ resource->filename);
+ return false;
}
- /* Skip over any version numbers and following ws */
+ // Skip over any version numbers and following ws
offset += (int)STRLEN(PRT_RESOURCE_HEADER);
offset = prt_resfile_skip_nonws(offset);
- if (offset == -1)
- return FALSE;
+ if (offset == -1) {
+ return false;
+ }
offset = prt_resfile_skip_ws(offset);
- if (offset == -1)
- return FALSE;
-
+ if (offset == -1) {
+ return false;
+ }
if (prt_resfile_strncmp(offset, PRT_RESOURCE_RESOURCE,
(int)STRLEN(PRT_RESOURCE_RESOURCE)) != 0) {
EMSG2(_("E619: file \"%s\" is not a supported PostScript resource file"),
- resource->filename);
- return FALSE;
+ resource->filename);
+ return false;
}
offset += (int)STRLEN(PRT_RESOURCE_RESOURCE);
- /* Decide type of resource in the file */
+ // Decide type of resource in the file
if (prt_resfile_strncmp(offset, PRT_RESOURCE_PROCSET,
- (int)STRLEN(PRT_RESOURCE_PROCSET)) == 0)
+ (int)STRLEN(PRT_RESOURCE_PROCSET)) == 0) {
resource->type = PRT_RESOURCE_TYPE_PROCSET;
- else if (prt_resfile_strncmp(offset, PRT_RESOURCE_ENCODING,
- (int)STRLEN(PRT_RESOURCE_ENCODING)) == 0)
+ } else if (prt_resfile_strncmp(offset, PRT_RESOURCE_ENCODING,
+ (int)STRLEN(PRT_RESOURCE_ENCODING)) == 0) {
resource->type = PRT_RESOURCE_TYPE_ENCODING;
- else if (prt_resfile_strncmp(offset, PRT_RESOURCE_CMAP,
- (int)STRLEN(PRT_RESOURCE_CMAP)) == 0)
+ } else if (prt_resfile_strncmp(offset, PRT_RESOURCE_CMAP,
+ (int)STRLEN(PRT_RESOURCE_CMAP)) == 0) {
resource->type = PRT_RESOURCE_TYPE_CMAP;
- else {
+ } else {
EMSG2(_("E619: file \"%s\" is not a supported PostScript resource file"),
- resource->filename);
- return FALSE;
+ resource->filename);
+ return false;
}
- /* Look for title and version of resource */
+ // Look for title and version of resource
resource->title[0] = '\0';
resource->version[0] = '\0';
- seen_title = FALSE;
- seen_version = FALSE;
- seen_all = FALSE;
+ bool seen_title = false;
+ bool seen_version = false;
+ bool seen_all = false;
while (!seen_all && prt_next_dsc(&dsc_line)) {
switch (dsc_line.type) {
case PRT_DSC_TITLE_TYPE:
STRLCPY(resource->title, dsc_line.string, dsc_line.len + 1);
- seen_title = TRUE;
- if (seen_version)
- seen_all = TRUE;
+ seen_title = true;
+ if (seen_version) {
+ seen_all = true;
+ }
break;
case PRT_DSC_VERSION_TYPE:
STRLCPY(resource->version, dsc_line.string, dsc_line.len + 1);
- seen_version = TRUE;
- if (seen_title)
- seen_all = TRUE;
+ seen_version = true;
+ if (seen_title) {
+ seen_all = true;
+ }
break;
case PRT_DSC_ENDCOMMENTS_TYPE:
- /* Wont find title or resource after this comment, stop searching */
- seen_all = TRUE;
+ // Wont find title or resource after this comment, stop searching
+ seen_all = true;
break;
case PRT_DSC_MISC_TYPE:
- /* Not interested in whatever comment this line had */
+ // Not interested in whatever comment this line had
break;
}
}
if (!seen_title || !seen_version) {
EMSG2(_("E619: file \"%s\" is not a supported PostScript resource file"),
- resource->filename);
- return FALSE;
+ resource->filename);
+ return false;
}
- return TRUE;
+ return true;
}
-static int prt_check_resource(struct prt_ps_resource_S *resource, char_u *version)
+static bool prt_check_resource(const struct prt_ps_resource_S *resource,
+ const char_u *version)
+ FUNC_ATTR_NONNULL_ALL
{
- /* Version number m.n should match, the revision number does not matter */
+ // Version number m.n should match, the revision number does not matter
if (STRNCMP(resource->version, version, STRLEN(version))) {
EMSG2(_("E621: \"%s\" resource file has wrong version"),
- resource->name);
- return FALSE;
+ resource->name);
+ return false;
}
- /* Other checks to be added as needed */
- return TRUE;
+ // Other checks to be added as needed
+ return true;
}
static void prt_dsc_start(void)
@@ -1810,7 +1820,7 @@ static void prt_dsc_textline(char *comment, char *text)
static void prt_dsc_text(char *comment, char *text)
{
- /* TODO - should scan 'text' for any chars needing escaping! */
+ // TODO(vim): - should scan 'text' for any chars needing escaping!
vim_snprintf((char *)prt_line_buffer, sizeof(prt_line_buffer),
"%%%%%s: (%s)\n", comment, text);
prt_write_file(prt_line_buffer);
@@ -1834,12 +1844,12 @@ static void prt_dsc_ints(char *comment, int count, int *ints)
prt_write_string("\n");
}
-static void
-prt_dsc_resources (
- char *comment, /* if NULL add to previous */
- char *type,
- char *string
+static void prt_dsc_resources(
+ const char *comment, // if NULL add to previous
+ const char *type,
+ const char *string
)
+ FUNC_ATTR_NONNULL_ARG(2, 3)
{
if (comment != NULL)
vim_snprintf((char *)prt_line_buffer, sizeof(prt_line_buffer),
@@ -1887,8 +1897,9 @@ static void prt_dsc_requirements(int duplex, int tumble, int collate, int color,
prt_write_string(" color");
if (num_copies > 1) {
prt_write_string(" numcopies(");
- /* Note: no space wanted so don't use prt_write_int() */
- sprintf((char *)prt_line_buffer, "%d", num_copies);
+ // Note: no space wanted so don't use prt_write_int()
+ snprintf((char *)prt_line_buffer, sizeof(prt_line_buffer), "%d",
+ num_copies);
prt_write_file(prt_line_buffer);
prt_write_string(")");
}
@@ -2038,13 +2049,13 @@ static int prt_get_lpp(void)
prt_ps_font->bbox_min_y)) / 2);
}
- /* Get height for topmost line based on background rect offset. */
+ // Get height for topmost line based on background rect offset.
prt_first_line_height = prt_line_height + prt_bgcol_offset;
- /* Calculate lpp */
+ // Calculate lpp
lpp = (int)((prt_top_margin - prt_bottom_margin) / prt_line_height);
- /* Adjust top margin if there is a header */
+ // Adjust top margin if there is a header
prt_top_margin -= prt_line_height * prt_header_height();
return lpp - prt_header_height();
@@ -2057,7 +2068,7 @@ static int prt_match_encoding(char *p_encoding, struct prt_ps_mbfont_S *p_cmap,
struct prt_ps_encoding_S *p_mbenc;
*pp_mbenc = NULL;
- /* Look for recognised encoding */
+ // Look for recognised encoding
enc_len = (int)STRLEN(p_encoding);
p_mbenc = p_cmap->encodings;
for (mbenc = 0; mbenc < p_cmap->num_encodings; mbenc++) {
@@ -2076,9 +2087,10 @@ static int prt_match_charset(char *p_charset, struct prt_ps_mbfont_S *p_cmap, st
int char_len;
struct prt_ps_charset_S *p_mbchar;
- /* Look for recognised character set, using default if one is not given */
- if (*p_charset == NUL)
+ // Look for recognised character set, using default if one is not given
+ if (*p_charset == NUL) {
p_charset = p_cmap->defcs;
+ }
char_len = (int)STRLEN(p_charset);
p_mbchar = p_cmap->charsets;
for (mbchar = 0; mbchar < p_cmap->num_charsets; mbchar++) {
@@ -2133,7 +2145,7 @@ int mch_print_init(prt_settings_T *psettings, char_u *jobname, int forceit)
break;
}
- /* Use first encoding matched if no charset matched */
+ // Use first encoding matched if no charset matched
if (p_mbenc_first != NULL && p_mbchar == NULL) {
p_mbenc = p_mbenc_first;
cmap = effective_cmap;
@@ -2144,24 +2156,24 @@ int mch_print_init(prt_settings_T *psettings, char_u *jobname, int forceit)
prt_out_mbyte = (p_mbenc != NULL);
if (prt_out_mbyte) {
- /* Build CMap name - will be same for all multi-byte fonts used */
+ // Build CMap name - will be same for all multi-byte fonts used
prt_cmap[0] = NUL;
prt_custom_cmap = (p_mbchar == NULL);
if (!prt_custom_cmap) {
- /* Check encoding and character set are compatible */
+ // Check encoding and character set are compatible
if ((p_mbenc->needs_charset & p_mbchar->has_charset) == 0) {
EMSG(_("E673: Incompatible multi-byte encoding and character set."));
return FALSE;
}
- /* Add charset name if not empty */
+ // Add charset name if not empty
if (p_mbchar->cmap_charset != NULL) {
STRLCPY(prt_cmap, p_mbchar->cmap_charset, sizeof(prt_cmap) - 2);
STRCAT(prt_cmap, "-");
}
} else {
- /* Add custom CMap character set name */
+ // Add custom CMap character set name
if (*p_pmcs == NUL) {
EMSG(_("E674: printmbcharset cannot be empty with multi-byte encoding."));
return FALSE;
@@ -2170,7 +2182,7 @@ int mch_print_init(prt_settings_T *psettings, char_u *jobname, int forceit)
STRCAT(prt_cmap, "-");
}
- /* CMap name ends with (optional) encoding name and -H for horizontal */
+ // CMap name ends with (optional) encoding name and -H for horizontal
if (p_mbenc->cmap_encoding != NULL && STRLEN(prt_cmap)
+ STRLEN(p_mbenc->cmap_encoding) + 3 < sizeof(prt_cmap)) {
STRCAT(prt_cmap, p_mbenc->cmap_encoding);
@@ -2183,7 +2195,7 @@ int mch_print_init(prt_settings_T *psettings, char_u *jobname, int forceit)
return FALSE;
}
- /* Derive CID font names with fallbacks if not defined */
+ // Derive CID font names with fallbacks if not defined
prt_build_cid_fontname(PRT_PS_FONT_ROMAN,
mbfont_opts[OPT_MBFONT_REGULAR].string,
mbfont_opts[OPT_MBFONT_REGULAR].strlen);
@@ -2288,9 +2300,10 @@ int mch_print_init(prt_settings_T *psettings, char_u *jobname, int forceit)
psettings->chars_per_line = prt_get_cpl();
psettings->lines_per_page = prt_get_lpp();
- /* Catch margin settings that leave no space for output! */
- if (psettings->chars_per_line <= 0 || psettings->lines_per_page <= 0)
+ // Catch margin settings that leave no space for output!
+ if (psettings->chars_per_line <= 0 || psettings->lines_per_page <= 0) {
return FAIL;
+ }
/*
* Sort out the number of copies to be printed. PS by default will do
@@ -2305,13 +2318,10 @@ int mch_print_init(prt_settings_T *psettings, char_u *jobname, int forceit)
|| TOLOWER_ASC(printer_opts[OPT_PRINT_COLLATE].string[0]) ==
'y');
if (prt_collate) {
- /* TODO: Get number of collated copies wanted. */
- psettings->n_collated_copies = 1;
+ // TODO(vim): Get number of collated copies wanted.
} else {
- /* TODO: Get number of uncollated copies wanted and update the cached
- * count.
- */
- prt_num_copies = 1;
+ // TODO(vim): Get number of uncollated copies wanted and update the cached
+ // count.
}
psettings->jobname = jobname;
@@ -2332,10 +2342,10 @@ int mch_print_init(prt_settings_T *psettings, char_u *jobname, int forceit)
prt_tumble = TRUE;
}
- /* For now user abort not supported */
+ // For now user abort not supported
psettings->user_abort = 0;
- /* If the user didn't specify a file name, use a temp file. */
+ // If the user didn't specify a file name, use a temp file.
if (psettings->outfile == NULL) {
prt_ps_file_name = vim_tempname();
if (prt_ps_file_name == NULL) {
@@ -2363,12 +2373,12 @@ int mch_print_init(prt_settings_T *psettings, char_u *jobname, int forceit)
prt_page_num = 0;
- prt_attribute_change = FALSE;
- prt_need_moveto = FALSE;
- prt_need_font = FALSE;
- prt_need_fgcol = FALSE;
- prt_need_bgcol = FALSE;
- prt_need_underline = FALSE;
+ prt_attribute_change = false;
+ prt_need_moveto = false;
+ prt_need_font = false;
+ prt_need_fgcol = false;
+ prt_need_bgcol = false;
+ prt_need_underline = false;
prt_file_error = FALSE;
@@ -2419,9 +2429,7 @@ static int prt_add_resource(struct prt_ps_resource_S *resource)
int mch_print_begin(prt_settings_T *psettings)
{
- time_t now;
int bbox[4];
- char *p_time;
double left;
double right;
double top;
@@ -2445,10 +2453,10 @@ int mch_print_begin(prt_settings_T *psettings)
}
prt_dsc_textline("For", buffer);
prt_dsc_textline("Creator", longVersion);
- /* Note: to ensure Clean8bit I don't think we can use LC_TIME */
- now = time(NULL);
- p_time = ctime(&now);
- /* Note: ctime() adds a \n so we have to remove it :-( */
+ // Note: to ensure Clean8bit I don't think we can use LC_TIME
+ char ctime_buf[50];
+ char *p_time = os_ctime(ctime_buf, sizeof(ctime_buf));
+ // Note: os_ctime() adds a \n so we have to remove it :-(
p = vim_strchr((char_u *)p_time, '\n');
if (p != NULL)
*p = NUL;
@@ -2486,14 +2494,15 @@ int mch_print_begin(prt_settings_T *psettings)
+ 0.5);
}
prt_dsc_ints("BoundingBox", 4, bbox);
- /* The media width and height does not change with landscape printing! */
+ // The media width and height does not change with landscape printing!
prt_dsc_docmedia(prt_mediasize[prt_media].name,
- prt_mediasize[prt_media].width,
- prt_mediasize[prt_media].height,
- (double)0, NULL, NULL);
- /* Define fonts needed */
- if (!prt_out_mbyte || prt_use_courier)
+ prt_mediasize[prt_media].width,
+ prt_mediasize[prt_media].height,
+ (double)0, NULL, NULL);
+ // Define fonts needed
+ if (!prt_out_mbyte || prt_use_courier) {
prt_dsc_font_resource("DocumentNeededResources", &prt_ps_courier_font);
+ }
if (prt_out_mbyte) {
prt_dsc_font_resource((prt_use_courier ? NULL
: "DocumentNeededResources"), &prt_ps_mb_font);
@@ -2501,7 +2510,7 @@ int mch_print_begin(prt_settings_T *psettings)
prt_dsc_resources(NULL, "cmap", prt_cmap);
}
- /* Search for external resources VIM supplies */
+ // Search for external resources VIM supplies
if (!prt_find_resource("prolog", &res_prolog)) {
EMSG(_("E456: Can't find PostScript resource file \"prolog.ps\""));
return FALSE;
@@ -2511,7 +2520,7 @@ int mch_print_begin(prt_settings_T *psettings)
if (!prt_check_resource(&res_prolog, PRT_PROLOG_VERSION))
return FALSE;
if (prt_out_mbyte) {
- /* Look for required version of multi-byte printing procset */
+ // Look for required version of multi-byte printing procset
if (!prt_find_resource("cidfont", &res_cidfont)) {
EMSG(_("E456: Can't find PostScript resource file \"cidfont.ps\""));
return FALSE;
@@ -2531,15 +2540,15 @@ int mch_print_begin(prt_settings_T *psettings)
p_encoding = enc_skip(p_penc);
if (*p_encoding == NUL
|| !prt_find_resource((char *)p_encoding, &res_encoding)) {
- /* 'printencoding' not set or not supported - find alternate */
+ // 'printencoding' not set or not supported - find alternate
int props;
p_encoding = enc_skip(p_enc);
props = enc_canon_props(p_encoding);
if (!(props & ENC_8BIT)
|| !prt_find_resource((char *)p_encoding, &res_encoding)) {
- /* 8-bit 'encoding' is not supported */
- /* Use latin1 as default printing encoding */
+ // 8-bit 'encoding' is not supported
+ // Use latin1 as default printing encoding
p_encoding = (char_u *)"latin1";
if (!prt_find_resource((char *)p_encoding, &res_encoding)) {
EMSG2(_("E456: Can't find PostScript resource file \"%s.ps\""),
@@ -2557,7 +2566,7 @@ int mch_print_begin(prt_settings_T *psettings)
if (*p_encoding == NUL)
p_encoding = enc_skip(p_enc);
if (prt_use_courier) {
- /* Include ASCII range encoding vector */
+ // Include ASCII range encoding vector
if (!prt_find_resource(prt_ascii_encoding, &res_encoding)) {
EMSG2(_("E456: Can't find PostScript resource file \"%s.ps\""),
prt_ascii_encoding);
@@ -2582,7 +2591,7 @@ int mch_print_begin(prt_settings_T *psettings)
prt_do_conv = prt_conv.vc_type != CONV_NONE;
if (prt_out_mbyte && prt_custom_cmap) {
- /* Find user supplied CMap */
+ // Find user supplied CMap
if (!prt_find_resource(prt_cmap, &res_cmap)) {
EMSG2(_("E456: Can't find PostScript resource file \"%s.ps\""),
prt_cmap);
@@ -2592,7 +2601,7 @@ int mch_print_begin(prt_settings_T *psettings)
return FALSE;
}
- /* List resources supplied */
+ // List resources supplied
STRCPY(buffer, res_prolog.title);
STRCAT(buffer, " ");
STRCAT(buffer, res_prolog.version);
@@ -2626,9 +2635,10 @@ int mch_print_begin(prt_settings_T *psettings)
*/
prt_dsc_noarg("BeginDefaults");
- /* List font resources most likely common to all pages */
- if (!prt_out_mbyte || prt_use_courier)
+ // List font resources most likely common to all pages
+ if (!prt_out_mbyte || prt_use_courier) {
prt_dsc_font_resource("PageResources", &prt_ps_courier_font);
+ }
if (prt_out_mbyte) {
prt_dsc_font_resource((prt_use_courier ? NULL : "PageResources"),
&prt_ps_mb_font);
@@ -2636,7 +2646,7 @@ int mch_print_begin(prt_settings_T *psettings)
prt_dsc_resources(NULL, "cmap", prt_cmap);
}
- /* Paper will be used for all pages */
+ // Paper will be used for all pages
prt_dsc_textline("PageMedia", prt_mediasize[prt_media].name);
prt_dsc_noarg("EndDefaults");
@@ -2646,15 +2656,18 @@ int mch_print_begin(prt_settings_T *psettings)
*/
prt_dsc_noarg("BeginProlog");
- /* Add required procsets - NOTE: order is important! */
- if (!prt_add_resource(&res_prolog))
- return FALSE;
+ // Add required procsets - NOTE: order is important!
+ if (!prt_add_resource(&res_prolog)) {
+ return false;
+ }
if (prt_out_mbyte) {
- /* Add CID font procset, and any user supplied CMap */
- if (!prt_add_resource(&res_cidfont))
- return FALSE;
- if (prt_custom_cmap && !prt_add_resource(&res_cmap))
- return FALSE;
+ // Add CID font procset, and any user supplied CMap
+ if (!prt_add_resource(&res_cidfont)) {
+ return false;
+ }
+ if (prt_custom_cmap && !prt_add_resource(&res_cmap)) {
+ return false;
+ }
}
if (!prt_out_mbyte || prt_use_courier)
@@ -2670,7 +2683,7 @@ int mch_print_begin(prt_settings_T *psettings)
*/
prt_dsc_noarg("BeginSetup");
- /* Device setup - page size and number of uncollated copies */
+ // Device setup - page size and number of uncollated copies
prt_write_int((int)prt_mediasize[prt_media].width);
prt_write_int((int)prt_mediasize[prt_media].height);
prt_write_int(0);
@@ -2683,7 +2696,7 @@ int mch_print_begin(prt_settings_T *psettings)
prt_write_boolean(prt_collate);
prt_write_string("c\n");
- /* Font resource inclusion and definition */
+ // Font resource inclusion and definition
if (!prt_out_mbyte || prt_use_courier) {
/* When using Courier for ASCII range when printing multi-byte, need to
* pick up ASCII encoding to use with it. */
@@ -2726,35 +2739,36 @@ int mch_print_begin(prt_settings_T *psettings)
if (!prt_custom_cmap)
prt_dsc_resources("IncludeResource", "cmap", prt_cmap);
prt_def_cidfont("CF1", (int)prt_line_height,
- prt_ps_mb_font.ps_fontname[PRT_PS_FONT_BOLD]);
- } else
- /* Use ROMAN for BOLD */
+ prt_ps_mb_font.ps_fontname[PRT_PS_FONT_BOLD]);
+ } else {
+ // Use ROMAN for BOLD
prt_dup_cidfont("CF0", "CF1");
-
+ }
if (prt_ps_mb_font.ps_fontname[PRT_PS_FONT_OBLIQUE] != NULL) {
prt_dsc_resources("IncludeResource", "font",
prt_ps_mb_font.ps_fontname[PRT_PS_FONT_OBLIQUE]);
if (!prt_custom_cmap)
prt_dsc_resources("IncludeResource", "cmap", prt_cmap);
prt_def_cidfont("CF2", (int)prt_line_height,
- prt_ps_mb_font.ps_fontname[PRT_PS_FONT_OBLIQUE]);
- } else
- /* Use ROMAN for OBLIQUE */
+ prt_ps_mb_font.ps_fontname[PRT_PS_FONT_OBLIQUE]);
+ } else {
+ // Use ROMAN for OBLIQUE
prt_dup_cidfont("CF0", "CF2");
-
+ }
if (prt_ps_mb_font.ps_fontname[PRT_PS_FONT_BOLDOBLIQUE] != NULL) {
prt_dsc_resources("IncludeResource", "font",
prt_ps_mb_font.ps_fontname[PRT_PS_FONT_BOLDOBLIQUE]);
if (!prt_custom_cmap)
prt_dsc_resources("IncludeResource", "cmap", prt_cmap);
prt_def_cidfont("CF3", (int)prt_line_height,
- prt_ps_mb_font.ps_fontname[PRT_PS_FONT_BOLDOBLIQUE]);
- } else
- /* Use BOLD for BOLDOBLIQUE */
+ prt_ps_mb_font.ps_fontname[PRT_PS_FONT_BOLDOBLIQUE]);
+ } else {
+ // Use BOLD for BOLDOBLIQUE
prt_dup_cidfont("CF1", "CF3");
+ }
}
- /* Misc constant vars used for underlining and background rects */
+ // Misc constant vars used for underlining and background rects
prt_def_var("UO", PRT_PS_FONT_TO_USER(prt_line_height,
prt_ps_font->uline_offset), 2);
prt_def_var("UW", PRT_PS_FONT_TO_USER(prt_line_height,
@@ -2763,7 +2777,7 @@ int mch_print_begin(prt_settings_T *psettings)
prt_dsc_noarg("EndSetup");
- /* Fail if any problems writing out to the PS file */
+ // Fail if any problems writing out to the PS file
retval = !prt_file_error;
return retval;
@@ -2786,7 +2800,7 @@ void mch_print_end(prt_settings_T *psettings)
if (!prt_file_error && psettings->outfile == NULL
&& !got_int && !psettings->user_abort) {
- /* Close the file first. */
+ // Close the file first.
if (prt_ps_fd != NULL) {
fclose(prt_ps_fd);
prt_ps_fd = NULL;
@@ -2837,7 +2851,7 @@ int mch_print_begin_page(char_u *str)
prt_bgcol = PRCOLOR_WHITE;
prt_font = PRT_PS_FONT_ROMAN;
- /* Set up page transformation for landscape printing. */
+ // Set up page transformation for landscape printing.
if (!prt_portrait) {
prt_write_int(-((int)prt_mediasize[prt_media].width));
prt_write_string("sl\n");
@@ -2845,7 +2859,7 @@ int mch_print_begin_page(char_u *str)
prt_dsc_noarg("EndPageSetup");
- /* We have reset the font attributes, force setting them again. */
+ // We have reset the font attributes, force setting them again.
curr_bg = 0xffffffff;
curr_fg = 0xffffffff;
curr_bold = kNone;
@@ -2871,9 +2885,9 @@ void mch_print_start_line(const bool margin, const int page_line)
prt_pos_y = prt_top_margin - prt_first_line_height -
page_line * prt_line_height;
- prt_attribute_change = TRUE;
- prt_need_moveto = TRUE;
- prt_half_width = FALSE;
+ prt_attribute_change = true;
+ prt_need_moveto = true;
+ prt_half_width = false;
}
int mch_print_text_out(char_u *const textp, size_t len)
@@ -2895,16 +2909,16 @@ int mch_print_text_out(char_u *const textp, size_t len)
const bool in_ascii = (len == 1 && *p < 0x80);
if (prt_in_ascii) {
if (!in_ascii) {
- /* No longer in ASCII range - need to switch font */
- prt_in_ascii = FALSE;
- prt_need_font = TRUE;
- prt_attribute_change = TRUE;
+ // No longer in ASCII range - need to switch font
+ prt_in_ascii = false;
+ prt_need_font = true;
+ prt_attribute_change = true;
}
} else if (in_ascii) {
- /* Now in ASCII range - need to switch font */
- prt_in_ascii = TRUE;
- prt_need_font = TRUE;
- prt_attribute_change = TRUE;
+ // Now in ASCII range - need to switch font
+ prt_in_ascii = true;
+ prt_need_font = true;
+ prt_attribute_change = true;
}
}
if (prt_out_mbyte) {
@@ -2914,16 +2928,16 @@ int mch_print_text_out(char_u *const textp, size_t len)
}
if (prt_half_width) {
if (!half_width) {
- prt_half_width = FALSE;
+ prt_half_width = false;
prt_pos_x += prt_char_width/4;
- prt_need_moveto = TRUE;
- prt_attribute_change = TRUE;
+ prt_need_moveto = true;
+ prt_attribute_change = true;
}
} else if (half_width) {
- prt_half_width = TRUE;
+ prt_half_width = true;
prt_pos_x += prt_char_width/4;
- prt_need_moveto = TRUE;
- prt_attribute_change = TRUE;
+ prt_need_moveto = true;
+ prt_attribute_change = true;
}
}
@@ -2932,24 +2946,25 @@ int mch_print_text_out(char_u *const textp, size_t len)
*/
if (prt_attribute_change) {
prt_flush_buffer();
- /* Reset count of number of chars that will be printed */
+ // Reset count of number of chars that will be printed
prt_text_run = 0;
if (prt_need_moveto) {
prt_pos_x_moveto = prt_pos_x;
prt_pos_y_moveto = prt_pos_y;
- prt_do_moveto = TRUE;
+ prt_do_moveto = true;
- prt_need_moveto = FALSE;
+ prt_need_moveto = false;
}
if (prt_need_font) {
- if (!prt_in_ascii)
+ if (!prt_in_ascii) {
prt_write_string("CF");
- else
+ } else {
prt_write_string("F");
+ }
prt_write_int(prt_font);
prt_write_string("sf\n");
- prt_need_font = FALSE;
+ prt_need_font = false;
}
if (prt_need_fgcol) {
unsigned int r, g, b;
@@ -2965,22 +2980,24 @@ int mch_print_text_out(char_u *const textp, size_t len)
prt_write_real(b / 255.0, 3);
prt_write_string("r\n");
}
- prt_need_fgcol = FALSE;
+ prt_need_fgcol = false;
}
if (prt_bgcol != PRCOLOR_WHITE) {
prt_new_bgcol = prt_bgcol;
- if (prt_need_bgcol)
- prt_do_bgcol = TRUE;
- } else
- prt_do_bgcol = FALSE;
- prt_need_bgcol = FALSE;
+ if (prt_need_bgcol) {
+ prt_do_bgcol = true;
+ }
+ } else {
+ prt_do_bgcol = false;
+ }
+ prt_need_bgcol = false;
if (prt_need_underline)
prt_do_underline = prt_underline;
- prt_need_underline = FALSE;
+ prt_need_underline = false;
- prt_attribute_change = FALSE;
+ prt_attribute_change = false;
}
if (prt_do_conv) {
@@ -3065,29 +3082,29 @@ void mch_print_set_font(const TriState iBold, const TriState iItalic,
if (font != prt_font) {
prt_font = font;
- prt_attribute_change = TRUE;
- prt_need_font = TRUE;
+ prt_attribute_change = true;
+ prt_need_font = true;
}
if (prt_underline != iUnderline) {
prt_underline = iUnderline;
- prt_attribute_change = TRUE;
- prt_need_underline = TRUE;
+ prt_attribute_change = true;
+ prt_need_underline = true;
}
}
void mch_print_set_bg(uint32_t bgcol)
{
prt_bgcol = bgcol;
- prt_attribute_change = TRUE;
- prt_need_bgcol = TRUE;
+ prt_attribute_change = true;
+ prt_need_bgcol = true;
}
void mch_print_set_fg(uint32_t fgcol)
{
if (fgcol != prt_fgcol) {
prt_fgcol = fgcol;
- prt_attribute_change = TRUE;
- prt_need_fgcol = TRUE;
+ prt_attribute_change = true;
+ prt_need_fgcol = true;
}
}
diff --git a/src/nvim/hashtab.h b/src/nvim/hashtab.h
index a70a8bea63..19633d455f 100644
--- a/src/nvim/hashtab.h
+++ b/src/nvim/hashtab.h
@@ -81,10 +81,10 @@ typedef struct hashtable_S {
size_t hi##todo_ = hi##ht_->ht_used; \
for (hashitem_T *hi = hi##ht_->ht_array; hi##todo_; hi++) { \
if (!HASHITEM_EMPTY(hi)) { \
+ hi##todo_--; \
{ \
code \
} \
- hi##todo_--; \
} \
} \
} while (0)
diff --git a/src/nvim/highlight.c b/src/nvim/highlight.c
index 093cc4923b..262afba07a 100644
--- a/src/nvim/highlight.c
+++ b/src/nvim/highlight.c
@@ -90,7 +90,12 @@ static int get_attr_entry(HlEntry entry)
}
}
- id = (int)kv_size(attr_entries);
+ 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);
@@ -308,23 +313,39 @@ int hl_combine_attr(int char_attr, int prim_attr)
// start with low-priority attribute, and override colors if present below.
HlAttrs new_en = char_aep;
- new_en.cterm_ae_attr |= spell_aep.cterm_ae_attr;
- new_en.rgb_ae_attr |= spell_aep.rgb_ae_attr;
+ if (spell_aep.cterm_ae_attr & HL_NOCOMBINE) {
+ new_en.cterm_ae_attr = spell_aep.cterm_ae_attr;
+ } else {
+ new_en.cterm_ae_attr |= spell_aep.cterm_ae_attr;
+ }
+ if (spell_aep.rgb_ae_attr & HL_NOCOMBINE) {
+ new_en.rgb_ae_attr = spell_aep.rgb_ae_attr;
+ } else {
+ new_en.rgb_ae_attr |= spell_aep.rgb_ae_attr;
+ }
if (spell_aep.cterm_fg_color > 0) {
new_en.cterm_fg_color = spell_aep.cterm_fg_color;
+ new_en.rgb_ae_attr &= ((~HL_FG_INDEXED)
+ | (spell_aep.rgb_ae_attr & HL_FG_INDEXED));
}
if (spell_aep.cterm_bg_color > 0) {
new_en.cterm_bg_color = spell_aep.cterm_bg_color;
+ new_en.rgb_ae_attr &= ((~HL_BG_INDEXED)
+ | (spell_aep.rgb_ae_attr & HL_BG_INDEXED));
}
if (spell_aep.rgb_fg_color >= 0) {
new_en.rgb_fg_color = spell_aep.rgb_fg_color;
+ new_en.rgb_ae_attr &= ((~HL_FG_INDEXED)
+ | (spell_aep.rgb_ae_attr & HL_FG_INDEXED));
}
if (spell_aep.rgb_bg_color >= 0) {
new_en.rgb_bg_color = spell_aep.rgb_bg_color;
+ new_en.rgb_ae_attr &= ((~HL_BG_INDEXED)
+ | (spell_aep.rgb_ae_attr & HL_BG_INDEXED));
}
if (spell_aep.rgb_sp_color >= 0) {
@@ -414,6 +435,7 @@ int hl_blend_attrs(int back_attr, int front_attr, bool *through)
cattrs.cterm_bg_color = fattrs.cterm_bg_color;
cattrs.cterm_fg_color = cterm_blend(ratio, battrs.cterm_fg_color,
fattrs.cterm_bg_color);
+ cattrs.rgb_ae_attr &= ~(HL_FG_INDEXED | HL_BG_INDEXED);
} else {
cattrs = fattrs;
if (ratio >= 50) {
@@ -427,6 +449,8 @@ int hl_blend_attrs(int back_attr, int front_attr, bool *through)
} else {
cattrs.rgb_sp_color = -1;
}
+
+ cattrs.rgb_ae_attr &= ~HL_BG_INDEXED;
}
cattrs.rgb_bg_color = rgb_blend(ratio, battrs.rgb_bg_color,
fattrs.rgb_bg_color);
@@ -603,6 +627,14 @@ Dictionary hlattrs2dict(HlAttrs ae, bool use_rgb)
}
if (use_rgb) {
+ if (mask & HL_FG_INDEXED) {
+ PUT(hl, "fg_indexed", BOOLEAN_OBJ(true));
+ }
+
+ if (mask & HL_BG_INDEXED) {
+ PUT(hl, "bg_indexed", BOOLEAN_OBJ(true));
+ }
+
if (ae.rgb_fg_color != -1) {
PUT(hl, "foreground", INTEGER_OBJ(ae.rgb_fg_color));
}
@@ -615,11 +647,11 @@ Dictionary hlattrs2dict(HlAttrs ae, bool use_rgb)
PUT(hl, "special", INTEGER_OBJ(ae.rgb_sp_color));
}
} else {
- if (cterm_normal_fg_color != ae.cterm_fg_color) {
+ if (cterm_normal_fg_color != ae.cterm_fg_color && ae.cterm_fg_color != 0) {
PUT(hl, "foreground", INTEGER_OBJ(ae.cterm_fg_color - 1));
}
- if (cterm_normal_bg_color != ae.cterm_bg_color) {
+ if (cterm_normal_bg_color != ae.cterm_bg_color && ae.cterm_bg_color != 0) {
PUT(hl, "background", INTEGER_OBJ(ae.cterm_bg_color - 1));
}
}
diff --git a/src/nvim/highlight_defs.h b/src/nvim/highlight_defs.h
index f9c2c03fc6..36f3181674 100644
--- a/src/nvim/highlight_defs.h
+++ b/src/nvim/highlight_defs.h
@@ -18,6 +18,9 @@ typedef enum {
HL_UNDERCURL = 0x10,
HL_STANDOUT = 0x20,
HL_STRIKETHROUGH = 0x40,
+ HL_NOCOMBINE = 0x80,
+ HL_BG_INDEXED = 0x0100,
+ HL_FG_INDEXED = 0x0200,
} HlAttrFlags;
/// Stores a complete highlighting entry, including colors and attributes
diff --git a/src/nvim/if_cscope.c b/src/nvim/if_cscope.c
index 0f9984ec38..2af09f10cb 100644
--- a/src/nvim/if_cscope.c
+++ b/src/nvim/if_cscope.c
@@ -71,10 +71,10 @@ static void cs_usage_msg(csid_e x)
static enum {
- EXP_CSCOPE_SUBCMD, /* expand ":cscope" sub-commands */
- EXP_SCSCOPE_SUBCMD, /* expand ":scscope" sub-commands */
- EXP_CSCOPE_FIND, /* expand ":cscope find" arguments */
- EXP_CSCOPE_KILL /* expand ":cscope kill" arguments */
+ EXP_CSCOPE_SUBCMD, // expand ":cscope" sub-commands
+ EXP_SCSCOPE_SUBCMD, // expand ":scscope" sub-commands
+ EXP_CSCOPE_FIND, // expand ":cscope find" arguments
+ EXP_CSCOPE_KILL // expand ":cscope kill" arguments
} expand_what;
/*
@@ -87,13 +87,13 @@ char_u *get_cscope_name(expand_T *xp, int idx)
switch (expand_what) {
case EXP_CSCOPE_SUBCMD:
- /* Complete with sub-commands of ":cscope":
- * add, find, help, kill, reset, show */
+ // Complete with sub-commands of ":cscope":
+ // add, find, help, kill, reset, show
return (char_u *)cs_cmds[idx].name;
case EXP_SCSCOPE_SUBCMD:
{
- /* Complete with sub-commands of ":scscope": same sub-commands as
- * ":cscope" but skip commands which don't support split windows */
+ // Complete with sub-commands of ":scscope": same sub-commands as
+ // ":cscope" but skip commands which don't support split windows
int i;
for (i = 0, current_idx = 0; cs_cmds[i].name != NULL; i++)
if (cs_cmds[i].cansplit)
@@ -118,10 +118,10 @@ char_u *get_cscope_name(expand_T *xp, int idx)
{
static char connection[5];
- /* ":cscope kill" accepts connection numbers or partial names of
- * the pathname of the cscope database as argument. Only complete
- * with connection numbers. -1 can also be used to kill all
- * connections. */
+ // ":cscope kill" accepts connection numbers or partial names of
+ // the pathname of the cscope database as argument. Only complete
+ // with connection numbers. -1 can also be used to kill all
+ // connections.
size_t i;
for (i = 0, current_idx = 0; i < csinfo_size; i++) {
if (csinfo[i].fname == NULL)
@@ -149,7 +149,7 @@ void set_context_in_cscope_cmd(expand_T *xp, const char *arg, cmdidx_T cmdidx)
expand_what = ((cmdidx == CMD_scscope)
? EXP_SCSCOPE_SUBCMD : EXP_CSCOPE_SUBCMD);
- /* (part of) subcommand already typed */
+ // (part of) subcommand already typed
if (*arg != NUL) {
const char *p = (const char *)skiptowhite((const char_u *)arg);
if (*p != NUL) { // Past first word.
@@ -175,7 +175,7 @@ void set_context_in_cscope_cmd(expand_T *xp, const char *arg, cmdidx_T cmdidx)
static void
do_cscope_general(
exarg_T *eap,
- int make_split /* whether to split window */
+ int make_split // whether to split window
)
{
cscmd_T *cmdp;
@@ -276,17 +276,19 @@ void ex_cstag(exarg_T *eap)
/// This simulates a vim_fgets(), but for cscope, returns the next line
/// from the cscope output. should only be called from find_tags()
///
-/// @return TRUE if eof, FALSE otherwise
-int cs_fgets(char_u *buf, int size)
+/// @return true if eof, FALSE otherwise
+bool cs_fgets(char_u *buf, int size)
+ FUNC_ATTR_NONNULL_ALL
{
char *p;
- if ((p = cs_manage_matches(NULL, NULL, 0, Get)) == NULL)
+ if ((p = cs_manage_matches(NULL, NULL, 0, Get)) == NULL) {
return true;
+ }
STRLCPY(buf, p, size);
- return FALSE;
-} /* cs_fgets */
+ return false;
+}
/// Called only from do_tag(), when popping the tag stack.
@@ -328,48 +330,53 @@ void cs_print_tags(void)
*
* Note: All string comparisons are case sensitive!
*/
-int cs_connection(int num, char_u *dbpath, char_u *ppath)
+bool cs_connection(int num, char_u *dbpath, char_u *ppath)
{
- if (num < 0 || num > 4 || (num > 0 && !dbpath))
+ if (num < 0 || num > 4 || (num > 0 && !dbpath)) {
return false;
+ }
for (size_t i = 0; i < csinfo_size; i++) {
- if (!csinfo[i].fname)
+ if (!csinfo[i].fname) {
continue;
-
- if (num == 0)
- return TRUE;
-
+ }
+ if (num == 0) {
+ return true;
+ }
switch (num) {
case 1:
- if (strstr(csinfo[i].fname, (char *)dbpath))
- return TRUE;
+ if (strstr(csinfo[i].fname, (char *)dbpath)) {
+ return true;
+ }
break;
case 2:
- if (strcmp(csinfo[i].fname, (char *)dbpath) == 0)
- return TRUE;
+ if (strcmp(csinfo[i].fname, (char *)dbpath) == 0) {
+ return true;
+ }
break;
case 3:
if (strstr(csinfo[i].fname, (char *)dbpath)
&& ((!ppath && !csinfo[i].ppath)
|| (ppath
&& csinfo[i].ppath
- && strstr(csinfo[i].ppath, (char *)ppath))))
- return TRUE;
+ && strstr(csinfo[i].ppath, (char *)ppath)))) {
+ return true;
+ }
break;
case 4:
if ((strcmp(csinfo[i].fname, (char *)dbpath) == 0)
&& ((!ppath && !csinfo[i].ppath)
|| (ppath
&& csinfo[i].ppath
- && (strcmp(csinfo[i].ppath, (char *)ppath) == 0))))
- return TRUE;
+ && (strcmp(csinfo[i].ppath, (char *)ppath) == 0)))) {
+ return true;
+ }
break;
}
}
- return FALSE;
-} /* cs_connection */
+ return false;
+} // cs_connection
/*
@@ -419,7 +426,7 @@ cs_add_common(
size_t usedlen = 0;
char_u *fbuf = NULL;
- /* get the filename (arg1), expand it, and try to stat it */
+ // get the filename (arg1), expand it, and try to stat it
fname = xmalloc(MAXPATHL + 1);
expand_env((char_u *)arg1, (char_u *)fname, MAXPATHL);
@@ -451,7 +458,7 @@ staterr:
}
int i;
- /* if filename is a directory, append the cscope database name to it */
+ // if filename is a directory, append the cscope database name to it
if ((file_info.stat.st_mode & S_IFMT) == S_IFDIR) {
fname2 = (char *)xmalloc(strlen(CSCOPE_DBFILE) + strlen(fname) + 2);
@@ -512,18 +519,18 @@ add_err:
xfree(fname);
xfree(ppath);
return CSCOPE_FAILURE;
-} /* cs_add_common */
+}
static int cs_check_for_connections(void)
{
return cs_cnt_connections() > 0;
-} /* cs_check_for_connections */
+}
static int cs_check_for_tags(void)
{
return p_tags[0] != NUL && curbuf->b_p_tags != NULL;
-} /* cs_check_for_tags */
+}
/// Count the number of cscope connections.
static size_t cs_cnt_connections(void)
@@ -535,10 +542,10 @@ static size_t cs_cnt_connections(void)
cnt++;
}
return cnt;
-} /* cs_cnt_connections */
+}
static void cs_reading_emsg(
- size_t idx /* connection index */
+ size_t idx // connection index
)
{
EMSGU(_("E262: error reading cscope connection %" PRIu64), idx);
@@ -602,7 +609,7 @@ static int cs_cnt_matches(size_t idx)
xfree(buf);
return nlines;
-} /* cs_cnt_matches */
+}
/// Creates the actual cscope command query from what the user entered.
@@ -646,8 +653,8 @@ static char *cs_create_cmd(char *csoption, char *pattern)
return NULL;
}
- /* Skip white space before the patter, except for text and pattern search,
- * they may want to use the leading white space. */
+ // Skip white space before the patter, except for text and pattern search,
+ // they may want to use the leading white space.
pat = pattern;
if (search != 4 && search != 6)
while (ascii_iswhite(*pat))
@@ -658,7 +665,7 @@ static char *cs_create_cmd(char *csoption, char *pattern)
(void)sprintf(cmd, "%d%s", search, pat);
return cmd;
-} /* cs_create_cmd */
+}
/// This piece of code was taken/adapted from nvi. do we need to add
@@ -694,15 +701,18 @@ err_closing:
case -1:
(void)EMSG(_("E622: Could not fork for cscope"));
goto err_closing;
- case 0: /* child: run cscope. */
- if (dup2(to_cs[0], STDIN_FILENO) == -1)
+ case 0: // child: run cscope.
+ if (dup2(to_cs[0], STDIN_FILENO) == -1) {
PERROR("cs_create_connection 1");
- if (dup2(from_cs[1], STDOUT_FILENO) == -1)
+ }
+ if (dup2(from_cs[1], STDOUT_FILENO) == -1) {
PERROR("cs_create_connection 2");
- if (dup2(from_cs[1], STDERR_FILENO) == -1)
+ }
+ if (dup2(from_cs[1], STDERR_FILENO) == -1) {
PERROR("cs_create_connection 3");
+ }
- /* close unused */
+ // close unused
(void)close(to_cs[1]);
(void)close(from_cs[0]);
#else
@@ -735,14 +745,14 @@ err_closing:
return CSCOPE_FAILURE;
}
#endif
- /* expand the cscope exec for env var's */
+ // expand the cscope exec for env var's
prog = xmalloc(MAXPATHL + 1);
expand_env(p_csprg, (char_u *)prog, MAXPATHL);
- /* alloc space to hold the cscope command */
+ // alloc space to hold the cscope command
size_t len = strlen(prog) + strlen(csinfo[i].fname) + 32;
if (csinfo[i].ppath) {
- /* expand the prepend path for env var's */
+ // expand the prepend path for env var's
ppath = xmalloc(MAXPATHL + 1);
expand_env((char_u *)csinfo[i].ppath, (char_u *)ppath, MAXPATHL);
@@ -754,12 +764,12 @@ err_closing:
cmd = xmalloc(len);
- /* run the cscope command; is there execl for non-unix systems? */
+ // run the cscope command; is there execl for non-unix systems?
#if defined(UNIX)
- (void)sprintf(cmd, "exec %s -dl -f %s", prog, csinfo[i].fname);
+ (void)snprintf(cmd, len, "exec %s -dl -f %s", prog, csinfo[i].fname);
#else
- /* WIN32 */
- (void)sprintf(cmd, "%s -dl -f %s", prog, csinfo[i].fname);
+ // WIN32
+ (void)snprintf(cmd, len, "%s -dl -f %s", prog, csinfo[i].fname);
#endif
if (csinfo[i].ppath != NULL) {
(void)strcat(cmd, " -P");
@@ -770,14 +780,14 @@ err_closing:
(void)strcat(cmd, csinfo[i].flags);
}
# ifdef UNIX
- /* on Win32 we still need prog */
+ // on Win32 we still need prog
xfree(prog);
# endif
xfree(ppath);
#if defined(UNIX)
# if defined(HAVE_SETSID) || defined(HAVE_SETPGID)
- /* Change our process group to avoid cscope receiving SIGWINCH. */
+ // Change our process group to avoid cscope receiving SIGWINCH.
# if defined(HAVE_SETSID)
(void)setsid();
# else
@@ -789,18 +799,18 @@ err_closing:
PERROR(_("cs_create_connection exec failed"));
exit(127);
- /* NOTREACHED */
- default: /* parent. */
- /*
- * Save the file descriptors for later duplication, and
- * reopen as streams.
- */
- if ((csinfo[i].to_fp = fdopen(to_cs[1], "w")) == NULL)
+ // NOTREACHED
+ default: // parent.
+ // Save the file descriptors for later duplication, and
+ // reopen as streams.
+ if ((csinfo[i].to_fp = fdopen(to_cs[1], "w")) == NULL) {
PERROR(_("cs_create_connection: fdopen for to_fp failed"));
- if ((csinfo[i].fr_fp = fdopen(from_cs[0], "r")) == NULL)
+ }
+ if ((csinfo[i].fr_fp = fdopen(from_cs[0], "r")) == NULL) {
PERROR(_("cs_create_connection: fdopen for fr_fp failed"));
+ }
- /* close unused */
+ // close unused
(void)close(to_cs[0]);
(void)close(from_cs[1]);
@@ -808,11 +818,11 @@ err_closing:
}
#else
- /* WIN32 */
- /* Create a new process to run cscope and use pipes to talk with it */
+ // WIN32
+ // Create a new process to run cscope and use pipes to talk with it
GetStartupInfo(&si);
si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
- si.wShowWindow = SW_HIDE; /* Hide child application window */
+ si.wShowWindow = SW_HIDE; // Hide child application window
si.hStdOutput = stdout_wr;
si.hStdError = stdout_wr;
si.hStdInput = stdin_rd;
@@ -826,7 +836,7 @@ err_closing:
(void)EMSG(_("E623: Could not spawn cscope process"));
goto err_closing;
}
- /* else */
+ // else
csinfo[i].pid = pi.dwProcessId;
csinfo[i].hProc = pi.hProcess;
CloseHandle(pi.hThread);
@@ -844,10 +854,10 @@ err_closing:
CloseHandle(stdin_rd);
CloseHandle(stdout_wr);
-#endif /* !UNIX */
+#endif // !UNIX
return CSCOPE_SUCCESS;
-} /* cs_create_connection */
+}
/// Query cscope using command line interface. Parse the output and use tselect
@@ -882,9 +892,9 @@ static int cs_find(exarg_T *eap)
if (NUL == eap->arg[i])
eap->arg[i] = ' ';
- return cs_find_common(opt, pat, eap->forceit, TRUE,
- eap->cmdidx == CMD_lcscope, *eap->cmdlinep);
-} /* cs_find */
+ return cs_find_common(opt, pat, eap->forceit, true,
+ eap->cmdidx == CMD_lcscope, *eap->cmdlinep);
+}
/// Common code for cscope find, shared by cs_find() and ex_cstag().
@@ -897,7 +907,7 @@ static int cs_find_common(char *opt, char *pat, int forceit, int verbose,
char cmdletter;
char *qfpos;
- /* get cmd letter */
+ // get cmd letter
switch (opt[0]) {
case '0':
cmdletter = 's';
@@ -933,10 +943,10 @@ static int cs_find_common(char *opt, char *pat, int forceit, int verbose,
qfpos = (char *)vim_strchr(p_csqf, cmdletter);
if (qfpos != NULL) {
qfpos++;
- /* next symbol must be + or - */
+ // next symbol must be + or -
if (strchr(CSQF_FLAGS, *qfpos) == NULL) {
char *nf = _("E469: invalid cscopequickfix flag %c for %c");
- /* strlen will be enough because we use chars */
+ // strlen will be enough because we use chars
char *buf = xmalloc(strlen(nf));
sprintf(buf, nf, *qfpos, *(qfpos-1));
@@ -954,23 +964,24 @@ static int cs_find_common(char *opt, char *pat, int forceit, int verbose,
}
}
- /* create the actual command to send to cscope */
+ // create the actual command to send to cscope
cmd = cs_create_cmd(opt, pat);
if (cmd == NULL)
return FALSE;
nummatches = xmalloc(sizeof(int) * csinfo_size);
- /* Send query to all open connections, then count the total number
- * of matches so we can alloc all in one swell foop. */
- for (size_t i = 0; i < csinfo_size; i++)
+ // Send query to all open connections, then count the total number
+ // of matches so we can alloc all in one swell foop.
+ for (size_t i = 0; i < csinfo_size; i++) {
nummatches[i] = 0;
+ }
totmatches = 0;
for (size_t i = 0; i < csinfo_size; i++) {
if (csinfo[i].fname == NULL || csinfo[i].to_fp == NULL)
continue;
- /* send cmd to cscope */
+ // send cmd to cscope
(void)fprintf(csinfo[i].to_fp, "%s\n", cmd);
(void)fflush(csinfo[i].to_fp);
@@ -1014,8 +1025,9 @@ static int cs_find_common(char *opt, char *pat, int forceit, int verbose,
} else {
cs_file_results(f, nummatches);
fclose(f);
- if (use_ll) /* Use location list */
+ if (use_ll) { // Use location list
wp = curwin;
+ }
// '-' starts a new error list
if (qf_init(wp, tmp, (char_u *)"%f%*\\t%l%*\\t%m",
*qfpos == '-', cmdline, NULL) > 0) {
@@ -1046,7 +1058,7 @@ static int cs_find_common(char *opt, char *pat, int forceit, int verbose,
char **matches = NULL, **contexts = NULL;
size_t matched = 0;
- /* read output */
+ // read output
cs_fill_results((char *)pat, totmatches, nummatches, &matches,
&contexts, &matched);
xfree(nummatches);
@@ -1057,8 +1069,7 @@ static int cs_find_common(char *opt, char *pat, int forceit, int verbose,
return do_tag((char_u *)pat, DT_CSCOPE, 0, forceit, verbose);
}
-
-} /* cs_find_common */
+}
/// Print help.
static int cs_help(exarg_T *eap)
@@ -1070,9 +1081,10 @@ static int cs_help(exarg_T *eap)
char *help = _(cmdp->help);
int space_cnt = 30 - vim_strsize((char_u *)help);
- /* Use %*s rather than %30s to ensure proper alignment in utf-8 */
- if (space_cnt < 0)
+ // Use %*s rather than %30s to ensure proper alignment in utf-8
+ if (space_cnt < 0) {
space_cnt = 0;
+ }
(void)smsg(_("%-5s: %s%*s (Usage: %s)"),
cmdp->name,
help, space_cnt, " ",
@@ -1094,7 +1106,7 @@ static int cs_help(exarg_T *eap)
wait_return(TRUE);
return CSCOPE_SUCCESS;
-} /* cs_help */
+}
static void clear_csinfo(size_t i)
@@ -1124,7 +1136,7 @@ static int cs_insert_filelist(char *fname, char *ppath, char *flags,
}
if (csinfo[j].fname == NULL && !empty_found) {
- i = j; /* remember first empty entry */
+ i = j; // remember first empty entry
empty_found = true;
}
}
@@ -1132,13 +1144,13 @@ static int cs_insert_filelist(char *fname, char *ppath, char *flags,
if (!empty_found) {
i = csinfo_size;
if (csinfo_size == 0) {
- /* First time allocation: allocate only 1 connection. It should
- * be enough for most users. If more is needed, csinfo will be
- * reallocated. */
+ // First time allocation: allocate only 1 connection. It should
+ // be enough for most users. If more is needed, csinfo will be
+ // reallocated.
csinfo_size = 1;
csinfo = xcalloc(1, sizeof(csinfo_T));
} else {
- /* Reallocate space for more connections. */
+ // Reallocate space for more connections.
csinfo_size *= 2;
csinfo = xrealloc(csinfo, sizeof(csinfo_T)*csinfo_size);
}
@@ -1165,7 +1177,7 @@ static int cs_insert_filelist(char *fname, char *ppath, char *flags,
os_fileinfo_id(file_info, &(csinfo[i].file_id));
assert(i <= INT_MAX);
return (int)i;
-} /* cs_insert_filelist */
+}
/// Find cscope command in command table.
@@ -1178,7 +1190,7 @@ static cscmd_T * cs_lookup_cmd(exarg_T *eap)
if (eap->arg == NULL)
return NULL;
- /* Store length of eap->arg before it gets modified by strtok(). */
+ // Store length of eap->arg before it gets modified by strtok().
eap_arg_len = (int)STRLEN(eap->arg);
if ((stok = strtok((char *)(eap->arg), (const char *)" ")) == NULL)
@@ -1190,7 +1202,7 @@ static cscmd_T * cs_lookup_cmd(exarg_T *eap)
return cmdp;
}
return NULL;
-} /* cs_lookup_cmd */
+}
/// Nuke em.
@@ -1247,13 +1259,13 @@ static int cs_kill(exarg_T *eap)
}
return CSCOPE_SUCCESS;
-} /* cs_kill */
+}
/// Actually kills a specific cscope connection.
static void cs_kill_execute(
- size_t i, /* cscope table index */
- char *cname /* cscope database name */
+ size_t i, // cscope table index
+ char *cname // cscope database name
)
{
if (p_csverbose) {
@@ -1284,17 +1296,16 @@ static void cs_kill_execute(
static char *cs_make_vim_style_matches(char *fname, char *slno, char *search,
char *tagstr)
{
- /* vim style is ctags:
- *
- * <tagstr>\t<filename>\t<linenum_or_search>"\t<extra>
- *
- * but as mentioned above, we'll always use the line number and
- * put the search pattern (if one exists) as "extra"
- *
- * buf is used as part of vim's method of handling tags, and
- * (i think) vim frees it when you pop your tags and get replaced
- * by new ones on the tag stack.
- */
+ // vim style is ctags:
+ //
+ // <tagstr>\t<filename>\t<linenum_or_search>"\t<extra>
+ //
+ // but as mentioned above, we'll always use the line number and
+ // put the search pattern (if one exists) as "extra"
+ //
+ // buf is used as part of vim's method of handling tags, and
+ // (i think) vim frees it when you pop your tags and get replaced
+ // by new ones on the tag stack.
char *buf;
size_t amt;
@@ -1311,7 +1322,7 @@ static char *cs_make_vim_style_matches(char *fname, char *slno, char *search,
}
return buf;
-} /* cs_make_vim_style_matches */
+}
/// This is kind of hokey, but i don't see an easy way round this.
@@ -1381,7 +1392,7 @@ static char *cs_manage_matches(char **matches, char **contexts,
}
return p;
-} /* cs_manage_matches */
+}
/// Parse cscope output.
@@ -1408,7 +1419,7 @@ retry:
return NULL;
}
- /* If the line's too long for the buffer, discard it. */
+ // If the line's too long for the buffer, discard it.
if ((p = strchr(buf, '\n')) == NULL) {
while ((ch = getc(csinfo[cnumber].fr_fp)) != EOF && ch != '\n')
;
@@ -1427,15 +1438,15 @@ retry:
return NULL;
if ((*linenumber = strtok(NULL, (const char *)" ")) == NULL)
return NULL;
- *search = *linenumber + strlen(*linenumber) + 1; /* +1 to skip \0 */
+ *search = *linenumber + strlen(*linenumber) + 1; // +1 to skip \0
- /* --- nvi ---
- * If the file is older than the cscope database, that is,
- * the database was built since the file was last modified,
- * or there wasn't a search string, use the line number.
- */
- if (strcmp(*search, "<unknown>") == 0)
+ // --- nvi ---
+ // If the file is older than the cscope database, that is,
+ // the database was built since the file was last modified,
+ // or there wasn't a search string, use the line number.
+ if (strcmp(*search, "<unknown>") == 0) {
*search = NULL;
+ }
name = cs_resolve_file(cnumber, name);
return name;
@@ -1474,11 +1485,10 @@ static void cs_file_results(FILE *f, int *nummatches_a)
xfree(context);
xfree(fullname);
- } /* for all matches */
+ } // for all matches
(void)cs_read_prompt(i);
-
- } /* for all cscope connections */
+ } // for all cscope connections
xfree(buf);
}
@@ -1539,10 +1549,10 @@ static void cs_fill_results(char *tagstr, size_t totmatches, int *nummatches_a,
*cntxts_p = cntxts;
xfree(buf);
-} // cs_fill_results
+}
-/* get the requested path components */
+// get the requested path components
static char *cs_pathcomponents(char *path)
{
if (p_cspc == 0) {
@@ -1688,7 +1698,7 @@ static int cs_read_prompt(size_t i)
static char *eprompt = "Press the RETURN key to continue:";
size_t epromptlen = strlen(eprompt);
- /* compute maximum allowed len for Cscope error message */
+ // compute maximum allowed len for Cscope error message
assert(IOSIZE >= cs_emsg_len);
size_t maxlen = IOSIZE - cs_emsg_len;
@@ -1738,11 +1748,12 @@ static int cs_read_prompt(size_t i)
}
if (ch == EOF) {
PERROR("cs_read_prompt EOF");
- if (buf != NULL && buf[0] != NUL)
+ if (buf != NULL && buf[0] != NUL) {
(void)EMSG2(cs_emsg, buf);
- else if (p_csverbose)
- cs_reading_emsg(i); /* don't have additional information */
- cs_release_csp(i, TRUE);
+ } else if (p_csverbose) {
+ cs_reading_emsg(i); // don't have additional information
+ }
+ cs_release_csp(i, true);
xfree(buf);
return CSCOPE_FAILURE;
}
@@ -1753,9 +1764,10 @@ static int cs_read_prompt(size_t i)
}
}
- if (ch == EOF)
- continue; /* didn't find the prompt */
- break; /* did find the prompt */
+ if (ch == EOF) {
+ continue; // didn't find the prompt
+ }
+ break; // did find the prompt
}
xfree(buf);
@@ -1766,8 +1778,9 @@ static int cs_read_prompt(size_t i)
/*
* Used to catch and ignore SIGALRM below.
*/
-static void sig_handler(int s) {
- /* do nothing */
+static void sig_handler(int s)
+{
+ // do nothing
return;
}
@@ -1775,7 +1788,7 @@ static void sig_handler(int s) {
/// Does the actual free'ing for the cs ptr with an optional flag of whether
/// or not to free the filename. Called by cs_kill and cs_reset.
-static void cs_release_csp(size_t i, int freefnpp)
+static void cs_release_csp(size_t i, bool freefnpp)
{
// Trying to exit normally (not sure whether it is fit to Unix cscope)
if (csinfo[i].to_fp != NULL) {
@@ -1791,7 +1804,7 @@ static void cs_release_csp(size_t i, int freefnpp)
# if defined(HAVE_SIGACTION)
struct sigaction sa, old;
- /* Use sigaction() to limit the waiting time to two seconds. */
+ // Use sigaction() to limit the waiting time to two seconds.
sigemptyset(&sa.sa_mask);
sa.sa_handler = sig_handler;
# ifdef SA_NODEFER
@@ -1800,27 +1813,28 @@ static void cs_release_csp(size_t i, int freefnpp)
sa.sa_flags = 0;
# endif
sigaction(SIGALRM, &sa, &old);
- alarm(2); /* 2 sec timeout */
+ alarm(2); // 2 sec timeout
- /* Block until cscope exits or until timer expires */
+ // Block until cscope exits or until timer expires
pid = waitpid(csinfo[i].pid, &pstat, 0);
waitpid_errno = errno;
- /* cancel pending alarm if still there and restore signal */
+ // cancel pending alarm if still there and restore signal
alarm(0);
sigaction(SIGALRM, &old, NULL);
# else
int waited;
- /* Can't use sigaction(), loop for two seconds. First yield the CPU
- * to give cscope a chance to exit quickly. */
+ // Can't use sigaction(), loop for two seconds. First yield the CPU
+ // to give cscope a chance to exit quickly.
sleep(0);
for (waited = 0; waited < 40; ++waited) {
pid = waitpid(csinfo[i].pid, &pstat, WNOHANG);
waitpid_errno = errno;
- if (pid != 0)
- break; /* break unless the process is still running */
- os_delay(50L, false); /* sleep 50 ms */
+ if (pid != 0) {
+ break; // break unless the process is still running
+ }
+ os_delay(50L, false); // sleep 50 ms
}
# endif
/*
@@ -1830,7 +1844,7 @@ static void cs_release_csp(size_t i, int freefnpp)
*/
if (pid < 0 && csinfo[i].pid > 1) {
# ifdef ECHILD
- int alive = TRUE;
+ bool alive = true;
if (waitpid_errno == ECHILD) {
/*
@@ -1845,13 +1859,13 @@ static void cs_release_csp(size_t i, int freefnpp)
int waited;
sleep(0);
- for (waited = 0; waited < 40; ++waited) {
- /* Check whether cscope process is still alive */
+ for (waited = 0; waited < 40; waited++) {
+ // Check whether cscope process is still alive
if (kill(csinfo[i].pid, 0) != 0) {
- alive = FALSE; /* cscope process no longer exists */
+ alive = false; // cscope process no longer exists
break;
}
- os_delay(50L, false); /* sleep 50ms */
+ os_delay(50L, false); // sleep 50ms
}
}
if (alive)
@@ -1862,11 +1876,12 @@ static void cs_release_csp(size_t i, int freefnpp)
}
}
}
-#else /* !UNIX */
+#else // !UNIX
if (csinfo[i].hProc != NULL) {
- /* Give cscope a chance to exit normally */
- if (WaitForSingleObject(csinfo[i].hProc, 1000) == WAIT_TIMEOUT)
+ // Give cscope a chance to exit normally
+ if (WaitForSingleObject(csinfo[i].hProc, 1000) == WAIT_TIMEOUT) {
TerminateProcess(csinfo[i].hProc, 0);
+ }
CloseHandle(csinfo[i].hProc);
}
#endif
@@ -1883,7 +1898,7 @@ static void cs_release_csp(size_t i, int freefnpp)
}
clear_csinfo(i);
-} /* cs_release_csp */
+}
/// Calls cs_kill on all cscope connections then reinits.
@@ -1895,7 +1910,7 @@ static int cs_reset(exarg_T *eap)
if (csinfo_size == 0)
return CSCOPE_SUCCESS;
- /* malloc our db and ppath list */
+ // malloc our db and ppath list
dblist = xmalloc(csinfo_size * sizeof(char *));
pplist = xmalloc(csinfo_size * sizeof(char *));
fllist = xmalloc(csinfo_size * sizeof(char *));
@@ -1908,15 +1923,14 @@ static int cs_reset(exarg_T *eap)
cs_release_csp(i, FALSE);
}
- /* rebuild the cscope connection list */
+ // rebuild the cscope connection list
for (size_t i = 0; i < csinfo_size; i++) {
if (dblist[i] != NULL) {
cs_add_common(dblist[i], pplist[i], fllist[i]);
if (p_csverbose) {
- /* don't use smsg_attr() because we want to display the
- * connection number in the same line as
- * "Added cscope database..."
- */
+ // don't use smsg_attr() because we want to display the
+ // connection number in the same line as
+ // "Added cscope database..."
snprintf(buf, ARRAY_SIZE(buf), " (#%zu)", i);
MSG_PUTS_ATTR(buf, HL_ATTR(HLF_R));
}
@@ -1933,7 +1947,7 @@ static int cs_reset(exarg_T *eap)
msg_attr(_("All cscope databases reset"), HL_ATTR(HLF_R) | MSG_HIST);
}
return CSCOPE_SUCCESS;
-} /* cs_reset */
+}
/// Construct the full pathname to a file found in the cscope database.
@@ -1954,11 +1968,11 @@ static char *cs_resolve_file(size_t i, char *name)
* copied into the tag buffer used by Vim.
*/
size_t len = strlen(name) + 2;
- if (csinfo[i].ppath != NULL)
+ if (csinfo[i].ppath != NULL) {
len += strlen(csinfo[i].ppath);
- else if (p_csre && csinfo[i].fname != NULL) {
- /* If 'cscoperelative' is set and ppath is not set, use cscope.out
- * path in path resolution. */
+ } else if (p_csre && csinfo[i].fname != NULL) {
+ // If 'cscoperelative' is set and ppath is not set, use cscope.out
+ // path in path resolution.
csdir = xmalloc(MAXPATHL);
STRLCPY(csdir, csinfo[i].fname,
path_tail((char_u *)csinfo[i].fname)
@@ -1966,9 +1980,9 @@ static char *cs_resolve_file(size_t i, char *name)
len += STRLEN(csdir);
}
- /* Note/example: this won't work if the cscope output already starts
- * "../.." and the prefix path is also "../..". if something like this
- * happens, you are screwed up and need to fix how you're using cscope. */
+ // Note/example: this won't work if the cscope output already starts
+ // "../.." and the prefix path is also "../..". if something like this
+ // happens, you are screwed up and need to fix how you're using cscope.
if (csinfo[i].ppath != NULL
&& (strncmp(name, csinfo[i].ppath, strlen(csinfo[i].ppath)) != 0)
&& (name[0] != '/')
@@ -1976,9 +1990,9 @@ static char *cs_resolve_file(size_t i, char *name)
fullname = xmalloc(len);
(void)sprintf(fullname, "%s/%s", csinfo[i].ppath, name);
} else if (csdir != NULL && csinfo[i].fname != NULL && *csdir != NUL) {
- /* Check for csdir to be non empty to avoid empty path concatenated to
- * cscope output. */
- fullname = concat_fnames((char *)csdir, name, TRUE);
+ // Check for csdir to be non empty to avoid empty path concatenated to
+ // cscope output.
+ fullname = concat_fnames((char *)csdir, name, true);
} else {
fullname = xstrdup(name);
}
@@ -2013,7 +2027,7 @@ static int cs_show(exarg_T *eap)
wait_return(TRUE);
return CSCOPE_SUCCESS;
-} /* cs_show */
+}
/// Only called when VIM exits to quit any cscope sessions.
@@ -2025,4 +2039,4 @@ void cs_end(void)
csinfo_size = 0;
}
-/* the end */
+// the end
diff --git a/src/nvim/if_cscope_defs.h b/src/nvim/if_cscope_defs.h
index fa18866840..d2d8b0fb62 100644
--- a/src/nvim/if_cscope_defs.h
+++ b/src/nvim/if_cscope_defs.h
@@ -1,19 +1,16 @@
#ifndef NVIM_IF_CSCOPE_DEFS_H
#define NVIM_IF_CSCOPE_DEFS_H
-/*
- * CSCOPE support for Vim added by Andy Kahn <kahn@zk3.dec.com>
- * Ported to Win32 by Sergey Khorev <sergey.khorev@gmail.com>
- *
- * The basic idea/structure of cscope for Vim was borrowed from Nvi.
- * There might be a few lines of code that look similar to what Nvi
- * has. If this is a problem and requires inclusion of the annoying
- * BSD license, then sue me; I'm not worth much anyway.
- */
-
+// CSCOPE support for Vim added by Andy Kahn <kahn@zk3.dec.com>
+// Ported to Win32 by Sergey Khorev <sergey.khorev@gmail.com>
+//
+// The basic idea/structure of cscope for Vim was borrowed from Nvi.
+// There might be a few lines of code that look similar to what Nvi
+// has. If this is a problem and requires inclusion of the annoying
+// BSD license, then sue me; I'm not worth much anyway.
#if defined(UNIX)
-# include <sys/types.h> /* pid_t */
+# include <sys/types.h> // pid_t
#endif
#include "nvim/os/os_defs.h"
@@ -33,13 +30,13 @@ typedef struct {
int (*func)(exarg_T *eap);
char * help;
char * usage;
- int cansplit; /* if supports splitting window */
+ int cansplit; // if supports splitting window
} cscmd_T;
typedef struct csi {
- char * fname; /* cscope db name */
- char * ppath; /* path to prepend (the -P option) */
- char * flags; /* additional cscope flags/options (e.g, -p2) */
+ char * fname; // cscope db name
+ char * ppath; // path to prepend (the -P option)
+ char * flags; // additional cscope flags/options (e.g, -p2)
#if defined(UNIX)
pid_t pid; // PID of the connected cscope process
#else
@@ -51,8 +48,8 @@ typedef struct csi {
#endif
FileID file_id;
- FILE * fr_fp; /* from cscope: FILE. */
- FILE * to_fp; /* to cscope: FILE. */
+ FILE * fr_fp; // from cscope: FILE.
+ FILE * to_fp; // to cscope: FILE.
} csinfo_T;
typedef enum { Add, Find, Help, Kill, Reset, Show } csid_e;
diff --git a/src/nvim/indent.c b/src/nvim/indent.c
index efbfea33aa..fb277b25fd 100644
--- a/src/nvim/indent.c
+++ b/src/nvim/indent.c
@@ -13,6 +13,7 @@
#include "nvim/charset.h"
#include "nvim/cursor.h"
#include "nvim/mark.h"
+#include "nvim/extmark.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
#include "nvim/misc1.h"
@@ -55,7 +56,8 @@ int get_indent_buf(buf_T *buf, linenr_T lnum)
// Count the size (in window cells) of the indent in line "ptr", with
// 'tabstop' at "ts".
// If @param list is TRUE, count only screen size for tabs.
-int get_indent_str(char_u *ptr, int ts, int list)
+int get_indent_str(const char_u *ptr, int ts, int list)
+ FUNC_ATTR_NONNULL_ALL
{
int count = 0;
@@ -88,6 +90,7 @@ int get_indent_str(char_u *ptr, int ts, int list)
// SIN_CHANGED: call changed_bytes() if the line was changed.
// SIN_INSERT: insert the indent in front of the line.
// SIN_UNDO: save line for undo before changing it.
+// SIN_NOMARK: don't move extmarks (because just after ml_append or something)
// @param size measured in spaces
// Returns true if the line was changed.
int set_indent(int size, int flags)
@@ -205,6 +208,7 @@ int set_indent(int size, int flags)
// If 'preserveindent' and 'expandtab' are both set keep the original
// characters and allocate accordingly. We will fill the rest with spaces
// after the if (!curbuf->b_p_et) below.
+ int skipcols = 0; // number of columns (in bytes) that were presved
if (orig_char_len != -1) {
int newline_size; // = orig_char_len + size - ind_done + line_len
STRICT_ADD(orig_char_len, size, &newline_size, int);
@@ -219,6 +223,7 @@ int set_indent(int size, int flags)
ind_len = orig_char_len + todo;
p = oldline;
s = newline;
+ skipcols = orig_char_len;
while (orig_char_len > 0) {
*s++ = *p++;
@@ -263,6 +268,7 @@ int set_indent(int size, int flags)
ind_done++;
}
*s++ = *p++;
+ skipcols++;
}
// Fill to next tabstop with a tab, if possible.
@@ -290,6 +296,13 @@ int set_indent(int size, int flags)
// Replace the line (unless undo fails).
if (!(flags & SIN_UNDO) || (u_savesub(curwin->w_cursor.lnum) == OK)) {
ml_replace(curwin->w_cursor.lnum, newline, false);
+ if (!(flags & SIN_NOMARK)) {
+ extmark_splice(curbuf,
+ (int)curwin->w_cursor.lnum-1, skipcols,
+ 0, (int)(p-oldline) - skipcols,
+ 0, (int)(s-newline) - skipcols,
+ kExtmarkUndo);
+ }
if (flags & SIN_CHANGED) {
changed_bytes(curwin->w_cursor.lnum, 0);
@@ -363,12 +376,12 @@ int get_number_indent(linenr_T lnum)
* parameters into account. Window must be specified, since it is not
* necessarily always the current one.
*/
-int get_breakindent_win(win_T *wp, char_u *line)
- FUNC_ATTR_NONNULL_ARG(1)
+int get_breakindent_win(win_T *wp, const char_u *line)
+ FUNC_ATTR_NONNULL_ALL
{
static int prev_indent = 0; // Cached indent value.
static long prev_ts = 0; // Cached tabstop value.
- static char_u *prev_line = NULL; // cached pointer to line.
+ static const char_u *prev_line = NULL; // cached pointer to line.
static varnumber_T prev_tick = 0; // Changedtick of cached value.
int bri = 0;
// window width minus window margin space, i.e. what rests for text
@@ -377,7 +390,7 @@ int get_breakindent_win(win_T *wp, char_u *line)
&& (vim_strchr(p_cpo, CPO_NUMCOL) == NULL)
? number_width(wp) + 1 : 0);
- /* used cached indent, unless pointer or 'tabstop' changed */
+ // used cached indent, unless pointer or 'tabstop' changed
if (prev_line != line || prev_ts != wp->w_buffer->b_p_ts
|| prev_tick != buf_get_changedtick(wp->w_buffer)) {
prev_line = line;
@@ -385,23 +398,24 @@ int get_breakindent_win(win_T *wp, char_u *line)
prev_tick = buf_get_changedtick(wp->w_buffer);
prev_indent = get_indent_str(line, (int)wp->w_buffer->b_p_ts, wp->w_p_list);
}
- bri = prev_indent + wp->w_p_brishift;
+ bri = prev_indent + wp->w_briopt_shift;
- /* indent minus the length of the showbreak string */
- if (wp->w_p_brisbr)
+ // indent minus the length of the showbreak string
+ if (wp->w_briopt_sbr) {
bri -= vim_strsize(p_sbr);
-
- /* Add offset for number column, if 'n' is in 'cpoptions' */
+ }
+ // Add offset for number column, if 'n' is in 'cpoptions'
bri += win_col_off2(wp);
- /* never indent past left window margin */
- if (bri < 0)
+ // never indent past left window margin
+ if (bri < 0) {
bri = 0;
- /* always leave at least bri_min characters on the left,
- * if text width is sufficient */
- else if (bri > eff_wwidth - wp->w_p_brimin)
- bri = (eff_wwidth - wp->w_p_brimin < 0)
- ? 0 : eff_wwidth - wp->w_p_brimin;
+ } else if (bri > eff_wwidth - wp->w_briopt_min) {
+ // always leave at least bri_min characters on the left,
+ // if text width is sufficient
+ bri = (eff_wwidth - wp->w_briopt_min < 0)
+ ? 0 : eff_wwidth - wp->w_briopt_min;
+ }
return bri;
}
diff --git a/src/nvim/indent.h b/src/nvim/indent.h
index 6552157d20..f96732bf1c 100644
--- a/src/nvim/indent.h
+++ b/src/nvim/indent.h
@@ -3,10 +3,11 @@
#include "nvim/vim.h"
-/* 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 */
+// 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
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "indent.h.generated.h"
diff --git a/src/nvim/indent_c.c b/src/nvim/indent_c.c
index 1ca1f3dae5..bb443161ef 100644
--- a/src/nvim/indent_c.c
+++ b/src/nvim/indent_c.c
@@ -461,6 +461,9 @@ cin_iscase (
if (cin_starts_with(s, "case")) {
for (s += 4; *s; ++s) {
s = cin_skipcomment(s);
+ if (*s == NUL) {
+ break;
+ }
if (*s == ':') {
if (s[1] == ':') /* skip over "::" for C++ */
++s;
@@ -3369,11 +3372,9 @@ term_again:
continue;
}
- /*
- * Are we at the start of a cpp base class declaration or
- * constructor initialization?
- */ /* XXX */
- n = false;
+ // Are we at the start of a cpp base class declaration or
+ // constructor initialization? XXX
+ n = 0;
if (curbuf->b_ind_cpp_baseclass != 0 && theline[0] != '{') {
n = cin_is_cpp_baseclass(&cache_cpp_baseclass);
l = get_cursor_line_ptr();
@@ -3406,7 +3407,6 @@ term_again:
* } foo,
* bar;
*/
- n = 0;
if (cin_ends_in(l, (char_u *)",", NULL)
|| (*l != NUL && (n = l[STRLEN(l) - 1]) == '\\')) {
/* take us back to opening paren */
diff --git a/src/nvim/keymap.c b/src/nvim/keymap.c
index b8b9c945b9..a553110552 100644
--- a/src/nvim/keymap.c
+++ b/src/nvim/keymap.c
@@ -309,6 +309,7 @@ static const struct key_name_entry {
{ K_ZERO, "Nul" },
{ K_SNR, "SNR" },
{ K_PLUG, "Plug" },
+ { K_IGNORE, "Ignore" },
{ K_COMMAND, "Cmd" },
{ 0, NULL }
// NOTE: When adding a long name update MAX_KEY_NAME_LEN.
@@ -516,8 +517,8 @@ char_u *get_special_key_name(int c, int modifiers)
/// @param[in,out] srcp Source from which <> are translated. Is advanced to
/// after the <> name if there is a match.
/// @param[in] src_len Length of the srcp.
-/// @param[out] dst Location where translation result will be kept. Must have
-/// at least six bytes.
+/// @param[out] dst Location where translation result will be kept. It must
+// be at least 19 bytes per "<x>" form.
/// @param[in] keycode Prefer key code, e.g. K_DEL in place of DEL.
/// @param[in] in_string Inside a double quoted string
///
diff --git a/src/nvim/keymap.h b/src/nvim/keymap.h
index cc02a6fb4f..ada9bc5780 100644
--- a/src/nvim/keymap.h
+++ b/src/nvim/keymap.h
@@ -13,7 +13,7 @@
* For MSDOS some keys produce codes larger than 0xff. They are split into two
* chars, the first one is K_NUL.
*/
-#define K_NUL (0xce) /* for MSDOS: special key follows */
+#define K_NUL (0xce) // for MSDOS: special key follows
/*
* K_SPECIAL is the first byte of a special key code and is always followed by
@@ -78,13 +78,13 @@
#define KS_SELECT 245
#define K_SELECT_STRING (char_u *)"\200\365X"
-/* Used a termcap entry that produces a normal character. */
+// Used a termcap entry that produces a normal character.
#define KS_KEY 242
-/* Used for click in a tab pages label. */
+// Used for click in a tab pages label.
#define KS_TABLINE 240
-/* Used for menu in a tab pages line. */
+// Used for menu in a tab pages line.
#define KS_TABMENU 239
/*
@@ -240,8 +240,8 @@ enum key_extra {
, KE_DROP = 95 // DnD data is available
// , KE_CURSORHOLD = 96 // CursorHold event
, KE_NOP = 97 // no-op: does nothing
- , KE_FOCUSGAINED = 98 // focus gained
- , KE_FOCUSLOST = 99 // focus lost
+ // , KE_FOCUSGAINED = 98 // focus gained
+ // , KE_FOCUSLOST = 99 // focus lost
// , KE_MOUSEMOVE = 100 // mouse moved with no button down
// , KE_CANCEL = 101 // return from vgetc
, KE_EVENT = 102 // event
@@ -274,13 +274,13 @@ enum key_extra {
#define K_TAB TERMCAP2KEY(KS_EXTRA, KE_TAB)
#define K_S_TAB TERMCAP2KEY('k', 'B')
-/* extra set of function keys F1-F4, for vt100 compatible xterm */
+// extra set of function keys F1-F4, for vt100 compatible xterm
#define K_XF1 TERMCAP2KEY(KS_EXTRA, KE_XF1)
#define K_XF2 TERMCAP2KEY(KS_EXTRA, KE_XF2)
#define K_XF3 TERMCAP2KEY(KS_EXTRA, KE_XF3)
#define K_XF4 TERMCAP2KEY(KS_EXTRA, KE_XF4)
-/* extra set of cursor keys for vt100 compatible xterm */
+// extra set of cursor keys for vt100 compatible xterm
#define K_XUP TERMCAP2KEY(KS_EXTRA, KE_XUP)
#define K_XDOWN TERMCAP2KEY(KS_EXTRA, KE_XDOWN)
#define K_XLEFT TERMCAP2KEY(KS_EXTRA, KE_XLEFT)
@@ -327,7 +327,7 @@ enum key_extra {
#define K_F36 TERMCAP2KEY('F', 'Q')
#define K_F37 TERMCAP2KEY('F', 'R')
-/* extra set of shifted function keys F1-F4, for vt100 compatible xterm */
+// extra set of shifted function keys F1-F4, for vt100 compatible xterm
#define K_S_XF1 TERMCAP2KEY(KS_EXTRA, KE_S_XF1)
#define K_S_XF2 TERMCAP2KEY(KS_EXTRA, KE_S_XF2)
#define K_S_XF3 TERMCAP2KEY(KS_EXTRA, KE_S_XF3)
@@ -346,7 +346,7 @@ enum key_extra {
#define K_S_F11 TERMCAP2KEY(KS_EXTRA, KE_S_F11)
#define K_S_F12 TERMCAP2KEY(KS_EXTRA, KE_S_F12)
-/* K_S_F13 to K_S_F37 are currently not used */
+// K_S_F13 to K_S_F37 are currently not used
#define K_HELP TERMCAP2KEY('%', '1')
#define K_UNDO TERMCAP2KEY('&', '8')
@@ -443,8 +443,8 @@ enum key_extra {
#define K_EVENT TERMCAP2KEY(KS_EXTRA, KE_EVENT)
#define K_COMMAND TERMCAP2KEY(KS_EXTRA, KE_COMMAND)
-/* Bits for modifier mask */
-/* 0x01 cannot be used, because the modifier must be 0x02 or higher */
+// Bits for modifier mask
+// 0x01 cannot be used, because the modifier must be 0x02 or higher
#define MOD_MASK_SHIFT 0x02
#define MOD_MASK_CTRL 0x04
#define MOD_MASK_ALT 0x08 // aka META
diff --git a/src/nvim/lib/kbtree.h b/src/nvim/lib/kbtree.h
index 33aeff1d89..bef37f8ba9 100644
--- a/src/nvim/lib/kbtree.h
+++ b/src/nvim/lib/kbtree.h
@@ -25,6 +25,12 @@
* SUCH DAMAGE.
*/
+// Gotchas
+// -------
+//
+// if you delete from a kbtree while iterating over it you must use
+// kb_del_itr and not kb_del otherwise the iterator might point to freed memory.
+
#ifndef NVIM_LIB_KBTREE_H
#define NVIM_LIB_KBTREE_H
diff --git a/src/nvim/log.c b/src/nvim/log.c
index db36611933..225e40cdb4 100644
--- a/src/nvim/log.c
+++ b/src/nvim/log.c
@@ -93,6 +93,7 @@ static bool log_path_init(void)
void log_init(void)
{
uv_mutex_init(&mutex);
+ log_path_init();
}
void log_lock(void)
diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c
index 9665655e74..32e804d213 100644
--- a/src/nvim/lua/converter.c
+++ b/src/nvim/lua/converter.c
@@ -19,6 +19,7 @@
#include "nvim/globals.h"
#include "nvim/message.h"
#include "nvim/eval/typval.h"
+#include "nvim/eval/userfunc.h"
#include "nvim/ascii.h"
#include "nvim/macros.h"
@@ -50,6 +51,7 @@ typedef struct {
#define LUA_PUSH_STATIC_STRING(lstate, s) \
lua_pushlstring(lstate, s, sizeof(s) - 1)
+
static LuaTableProps nlua_traverse_table(lua_State *const lstate)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
{
@@ -156,6 +158,13 @@ static LuaTableProps nlua_traverse_table(lua_State *const lstate)
&& other_keys_num == 0
&& ret.string_keys_num == 0)) {
ret.type = kObjectTypeArray;
+ if (tsize == 0 && lua_getmetatable(lstate, -1)) {
+ nlua_pushref(lstate, nlua_empty_dict_ref);
+ if (lua_rawequal(lstate, -2, -1)) {
+ ret.type = kObjectTypeDictionary;
+ }
+ lua_pop(lstate, 2);
+ }
} else if (ret.string_keys_num == tsize) {
ret.type = kObjectTypeDictionary;
} else {
@@ -279,10 +288,10 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv)
break;
}
case LUA_TBOOLEAN: {
- cur.tv->v_type = VAR_SPECIAL;
- cur.tv->vval.v_special = (lua_toboolean(lstate, -1)
- ? kSpecialVarTrue
- : kSpecialVarFalse);
+ cur.tv->v_type = VAR_BOOL;
+ cur.tv->vval.v_bool = (lua_toboolean(lstate, -1)
+ ? kBoolVarTrue
+ : kBoolVarFalse);
break;
}
case LUA_TSTRING: {
@@ -307,6 +316,13 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv)
break;
}
case LUA_TTABLE: {
+ // Only need to track table refs if we have a metatable associated.
+ LuaRef table_ref = LUA_NOREF;
+ if (lua_getmetatable(lstate, -1)) {
+ lua_pop(lstate, 1);
+ table_ref = nlua_ref(lstate, -1);
+ }
+
const LuaTableProps table_props = nlua_traverse_table(lstate);
for (size_t i = 0; i < kv_size(stack); i++) {
@@ -322,6 +338,7 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv)
case kObjectTypeArray: {
cur.tv->v_type = VAR_LIST;
cur.tv->vval.v_list = tv_list_alloc((ptrdiff_t)table_props.maxidx);
+ cur.tv->vval.v_list->lua_table_ref = table_ref;
tv_list_ref(cur.tv->vval.v_list);
if (table_props.maxidx != 0) {
cur.container = true;
@@ -335,6 +352,7 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv)
cur.tv->v_type = VAR_DICT;
cur.tv->vval.v_dict = tv_dict_alloc();
cur.tv->vval.v_dict->dv_refcount++;
+ cur.tv->vval.v_dict->lua_table_ref = table_ref;
} else {
cur.special = table_props.has_string_with_nul;
if (table_props.has_string_with_nul) {
@@ -345,11 +363,13 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv)
S_LEN("_VAL"));
assert(val_di != NULL);
cur.tv = &val_di->di_tv;
+ cur.tv->vval.v_list->lua_table_ref = table_ref;
assert(cur.tv->v_type == VAR_LIST);
} else {
cur.tv->v_type = VAR_DICT;
cur.tv->vval.v_dict = tv_dict_alloc();
cur.tv->vval.v_dict->dv_refcount++;
+ cur.tv->vval.v_dict->lua_table_ref = table_ref;
}
cur.container = true;
cur.idx = lua_gettop(lstate);
@@ -377,6 +397,33 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv)
nlua_pop_typval_table_processing_end:
break;
}
+ case LUA_TFUNCTION: {
+ LuaCFunctionState *state = xmalloc(sizeof(LuaCFunctionState));
+ state->lua_callable.func_ref = nlua_ref(lstate, -1);
+ state->lua_callable.table_ref = LUA_NOREF;
+
+ char_u *name = register_cfunc(
+ &nlua_CFunction_func_call,
+ &nlua_CFunction_func_free,
+ state);
+
+ cur.tv->v_type = VAR_FUNC;
+ cur.tv->vval.v_string = vim_strsave(name);
+ break;
+ }
+ case LUA_TUSERDATA: {
+ nlua_pushref(lstate, nlua_nil_ref);
+ bool is_nil = lua_rawequal(lstate, -2, -1);
+ lua_pop(lstate, 1);
+ if (is_nil) {
+ cur.tv->v_type = VAR_SPECIAL;
+ cur.tv->vval.v_special = kSpecialVarNull;
+ } else {
+ EMSG(_("E5101: Cannot convert given lua type"));
+ ret = false;
+ }
+ break;
+ }
default: {
EMSG(_("E5101: Cannot convert given lua type"));
ret = false;
@@ -401,10 +448,18 @@ nlua_pop_typval_table_processing_end:
return ret;
}
+static bool typval_conv_special = false;
+
#define TYPVAL_ENCODE_ALLOW_SPECIALS true
#define TYPVAL_ENCODE_CONV_NIL(tv) \
- lua_pushnil(lstate)
+ do { \
+ if (typval_conv_special) { \
+ lua_pushnil(lstate); \
+ } else { \
+ nlua_pushref(lstate, nlua_nil_ref); \
+ } \
+ } while (0)
#define TYPVAL_ENCODE_CONV_BOOL(tv, num) \
lua_pushboolean(lstate, (bool)(num))
@@ -439,7 +494,15 @@ nlua_pop_typval_table_processing_end:
lua_createtable(lstate, 0, 0)
#define TYPVAL_ENCODE_CONV_EMPTY_DICT(tv, dict) \
- nlua_create_typed_table(lstate, 0, 0, kObjectTypeDictionary)
+ do { \
+ if (typval_conv_special) { \
+ nlua_create_typed_table(lstate, 0, 0, kObjectTypeDictionary); \
+ } else { \
+ lua_createtable(lstate, 0, 0); \
+ nlua_pushref(lstate, nlua_empty_dict_ref); \
+ lua_setmetatable(lstate, -2); \
+ } \
+ } while (0)
#define TYPVAL_ENCODE_CONV_LIST_START(tv, len) \
do { \
@@ -548,9 +611,11 @@ nlua_pop_typval_table_processing_end:
/// @param[in] tv typval_T to convert.
///
/// @return true in case of success, false otherwise.
-bool nlua_push_typval(lua_State *lstate, typval_T *const tv)
+bool nlua_push_typval(lua_State *lstate, typval_T *const tv, bool special)
{
+ typval_conv_special = special;
const int initial_size = lua_gettop(lstate);
+
if (!lua_checkstack(lstate, initial_size + 2)) {
emsgf(_("E1502: Lua failed to grow stack to %i"), initial_size + 4);
return false;
@@ -666,6 +731,10 @@ void nlua_push_Dictionary(lua_State *lstate, const Dictionary dict,
nlua_create_typed_table(lstate, 0, 0, kObjectTypeDictionary);
} else {
lua_createtable(lstate, 0, (int)dict.size);
+ if (dict.size == 0 && !special) {
+ nlua_pushref(lstate, nlua_empty_dict_ref);
+ lua_setmetatable(lstate, -2);
+ }
}
for (size_t i = 0; i < dict.size; i++) {
nlua_push_String(lstate, dict.items[i].key, special);
@@ -708,7 +777,11 @@ void nlua_push_Object(lua_State *lstate, const Object obj, bool special)
{
switch (obj.type) {
case kObjectTypeNil: {
- lua_pushnil(lstate);
+ if (special) {
+ lua_pushnil(lstate);
+ } else {
+ nlua_pushref(lstate, nlua_nil_ref);
+ }
break;
}
case kObjectTypeLuaRef: {
@@ -1142,6 +1215,19 @@ Object nlua_pop_Object(lua_State *const lstate, bool ref, Error *const err)
break;
}
+ case LUA_TUSERDATA: {
+ nlua_pushref(lstate, nlua_nil_ref);
+ bool is_nil = lua_rawequal(lstate, -2, -1);
+ lua_pop(lstate, 1);
+ if (is_nil) {
+ *cur.obj = NIL;
+ } else {
+ api_set_error(err, kErrorTypeValidation,
+ "Cannot convert userdata");
+ }
+ break;
+ }
+
default: {
type_error:
api_set_error(err, kErrorTypeValidation,
@@ -1179,7 +1265,7 @@ GENERATE_INDEX_FUNCTION(Tabpage)
#undef GENERATE_INDEX_FUNCTION
-/// Record some auxilary values in vim module
+/// Record some auxiliary values in vim module
///
/// Assumes that module table is on top of the stack.
///
diff --git a/src/nvim/lua/converter.h b/src/nvim/lua/converter.h
index 542c56ea3e..8601a32418 100644
--- a/src/nvim/lua/converter.h
+++ b/src/nvim/lua/converter.h
@@ -9,6 +9,15 @@
#include "nvim/func_attr.h"
#include "nvim/eval.h"
+typedef struct {
+ LuaRef func_ref;
+ LuaRef table_ref;
+} LuaCallable;
+
+typedef struct {
+ LuaCallable lua_callable;
+} LuaCFunctionState;
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "lua/converter.h.generated.h"
#endif
diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c
index f51aa3c6d4..0d5622f1e7 100644
--- a/src/nvim/lua/executor.c
+++ b/src/nvim/lua/executor.c
@@ -11,26 +11,33 @@
#include "nvim/func_attr.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
+#include "nvim/api/private/handle.h"
#include "nvim/api/vim.h"
+#include "nvim/msgpack_rpc/channel.h"
#include "nvim/vim.h"
#include "nvim/ex_getln.h"
#include "nvim/ex_cmds2.h"
#include "nvim/message.h"
#include "nvim/memline.h"
#include "nvim/buffer_defs.h"
+#include "nvim/regexp.h"
#include "nvim/macros.h"
#include "nvim/screen.h"
#include "nvim/cursor.h"
#include "nvim/undo.h"
#include "nvim/ascii.h"
#include "nvim/change.h"
+#include "nvim/eval/userfunc.h"
+#include "nvim/event/time.h"
+#include "nvim/event/loop.h"
#ifdef WIN32
#include "nvim/os/os.h"
#endif
-#include "nvim/lua/executor.h"
#include "nvim/lua/converter.h"
+#include "nvim/lua/executor.h"
+#include "nvim/lua/treesitter.h"
#include "luv/luv.h"
@@ -46,8 +53,14 @@ typedef struct {
# include "lua/executor.c.generated.h"
#endif
-/// Name of the run code for use in messages
-#define NLUA_EVAL_NAME "<VimL compiled string>"
+#define PUSH_ALL_TYPVALS(lstate, args, argcount, special) \
+ for (int i = 0; i < argcount; i++) { \
+ if (args[i].v_type == VAR_UNKNOWN) { \
+ lua_pushnil(lstate); \
+ } else { \
+ nlua_push_typval(lstate, &args[i], special); \
+ } \
+ }
/// Convert lua error into a Vim error message
///
@@ -245,6 +258,109 @@ static int nlua_schedule(lua_State *const lstate)
return 0;
}
+static struct luaL_Reg regex_meta[] = {
+ { "__gc", regex_gc },
+ { "__tostring", regex_tostring },
+ { "match_str", regex_match_str },
+ { "match_line", regex_match_line },
+ { NULL, NULL }
+};
+
+// Dummy timer callback. Used by f_wait().
+static void dummy_timer_due_cb(TimeWatcher *tw, void *data)
+{
+}
+
+// Dummy timer close callback. Used by f_wait().
+static void dummy_timer_close_cb(TimeWatcher *tw, void *data)
+{
+ xfree(tw);
+}
+
+static bool nlua_wait_condition(lua_State *lstate, int *status,
+ bool *callback_result)
+{
+ lua_pushvalue(lstate, 2);
+ *status = lua_pcall(lstate, 0, 1, 0);
+ if (*status) {
+ return true; // break on error, but keep error on stack
+ }
+ *callback_result = lua_toboolean(lstate, -1);
+ lua_pop(lstate, 1);
+ return *callback_result; // break if true
+}
+
+/// "vim.wait(timeout, condition[, interval])" function
+static int nlua_wait(lua_State *lstate)
+ FUNC_ATTR_NONNULL_ALL
+{
+ intptr_t timeout = luaL_checkinteger(lstate, 1);
+ if (timeout < 0) {
+ return luaL_error(lstate, "timeout must be > 0");
+ }
+
+ // Check if condition can be called.
+ bool is_function = (lua_type(lstate, 2) == LUA_TFUNCTION);
+
+ // Check if condition is callable table
+ if (!is_function && luaL_getmetafield(lstate, 2, "__call") != 0) {
+ is_function = (lua_type(lstate, -1) == LUA_TFUNCTION);
+ lua_pop(lstate, 1);
+ }
+
+ if (!is_function) {
+ lua_pushliteral(lstate, "vim.wait: condition must be a function");
+ return lua_error(lstate);
+ }
+
+ intptr_t interval = 200;
+ if (lua_gettop(lstate) >= 3) {
+ interval = luaL_checkinteger(lstate, 3);
+ if (interval < 0) {
+ return luaL_error(lstate, "interval must be > 0");
+ }
+ }
+
+ TimeWatcher *tw = xmalloc(sizeof(TimeWatcher));
+
+ // Start dummy timer.
+ time_watcher_init(&main_loop, tw, NULL);
+ tw->events = main_loop.events;
+ tw->blockable = true;
+ time_watcher_start(tw, dummy_timer_due_cb,
+ (uint64_t)interval, (uint64_t)interval);
+
+ int pcall_status = 0;
+ bool callback_result = false;
+
+ LOOP_PROCESS_EVENTS_UNTIL(
+ &main_loop,
+ main_loop.events,
+ (int)timeout,
+ nlua_wait_condition(lstate, &pcall_status, &callback_result) || got_int);
+
+ // Stop dummy timer
+ time_watcher_stop(tw);
+ time_watcher_close(tw, dummy_timer_close_cb);
+
+ if (pcall_status) {
+ return lua_error(lstate);
+ } else if (callback_result) {
+ lua_pushboolean(lstate, 1);
+ lua_pushnil(lstate);
+ } else if (got_int) {
+ got_int = false;
+ vgetc();
+ lua_pushboolean(lstate, 0);
+ lua_pushinteger(lstate, -2);
+ } else {
+ lua_pushboolean(lstate, 0);
+ lua_pushinteger(lstate, -1);
+ }
+
+ return 2;
+}
+
/// Initialize lua interpreter state
///
/// Called by lua interpreter itself to initialize state.
@@ -269,12 +385,7 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
#endif
// vim
- const char *code = (char *)&vim_module[0];
- if (luaL_loadbuffer(lstate, code, strlen(code), "@vim.lua")
- || lua_pcall(lstate, 0, LUA_MULTRET, 0)) {
- nlua_error(lstate, _("E5106: Error while creating vim module: %.*s"));
- return 1;
- }
+ lua_newtable(lstate);
// vim.api
nlua_add_api_functions(lstate);
// vim.types, vim.type_idx, vim.val_idx
@@ -294,6 +405,29 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
// in_fast_event
lua_pushcfunction(lstate, &nlua_in_fast_event);
lua_setfield(lstate, -2, "in_fast_event");
+ // call
+ lua_pushcfunction(lstate, &nlua_call);
+ lua_setfield(lstate, -2, "call");
+ // regex
+ lua_pushcfunction(lstate, &nlua_regex);
+ lua_setfield(lstate, -2, "regex");
+ luaL_newmetatable(lstate, "nvim_regex");
+ luaL_register(lstate, NULL, regex_meta);
+ lua_pushvalue(lstate, -1); // [meta, meta]
+ lua_setfield(lstate, -2, "__index"); // [meta]
+ lua_pop(lstate, 1); // don't use metatable now
+
+ // rpcrequest
+ lua_pushcfunction(lstate, &nlua_rpcrequest);
+ lua_setfield(lstate, -2, "rpcrequest");
+
+ // rpcnotify
+ lua_pushcfunction(lstate, &nlua_rpcnotify);
+ lua_setfield(lstate, -2, "rpcnotify");
+
+ // wait
+ lua_pushcfunction(lstate, &nlua_wait);
+ lua_setfield(lstate, -2, "wait");
// vim.loop
luv_set_loop(lstate, &main_loop.uv);
@@ -310,7 +444,45 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
lua_setfield(lstate, -2, "luv");
lua_pop(lstate, 3);
+ // vim.NIL
+ lua_newuserdata(lstate, 0);
+ lua_createtable(lstate, 0, 0);
+ lua_pushcfunction(lstate, &nlua_nil_tostring);
+ lua_setfield(lstate, -2, "__tostring");
+ lua_setmetatable(lstate, -2);
+ nlua_nil_ref = nlua_ref(lstate, -1);
+ lua_setfield(lstate, -2, "NIL");
+
+ // vim._empty_dict_mt
+ lua_createtable(lstate, 0, 0);
+ lua_pushcfunction(lstate, &nlua_empty_dict_tostring);
+ lua_setfield(lstate, -2, "__tostring");
+ nlua_empty_dict_ref = nlua_ref(lstate, -1);
+ lua_setfield(lstate, -2, "_empty_dict_mt");
+
+ // internal vim._treesitter... API
+ nlua_add_treesitter(lstate);
+
lua_setglobal(lstate, "vim");
+
+ {
+ const char *code = (char *)&shared_module[0];
+ if (luaL_loadbuffer(lstate, code, strlen(code), "@shared.lua")
+ || lua_pcall(lstate, 0, 0, 0)) {
+ nlua_error(lstate, _("E5106: Error while creating shared module: %.*s"));
+ return 1;
+ }
+ }
+
+ {
+ const char *code = (char *)&vim_module[0];
+ if (luaL_loadbuffer(lstate, code, strlen(code), "@vim.lua")
+ || lua_pcall(lstate, 0, 0, 0)) {
+ nlua_error(lstate, _("E5106: Error while creating vim module: %.*s"));
+ return 1;
+ }
+ }
+
return 0;
}
@@ -371,29 +543,6 @@ static lua_State *nlua_enter(void)
return lstate;
}
-/// Execute lua string
-///
-/// @param[in] str String to execute.
-/// @param[out] ret_tv Location where result will be saved.
-///
-/// @return Result of the execution.
-void executor_exec_lua(const String str, typval_T *const ret_tv)
- FUNC_ATTR_NONNULL_ALL
-{
- lua_State *const lstate = nlua_enter();
-
- if (luaL_loadbuffer(lstate, str.data, str.size, NLUA_EVAL_NAME)) {
- nlua_error(lstate, _("E5104: Error while creating lua chunk: %.*s"));
- return;
- }
- if (lua_pcall(lstate, 0, 1, 0)) {
- nlua_error(lstate, _("E5105: Error while calling lua chunk: %.*s"));
- return;
- }
-
- nlua_pop_typval(lstate, ret_tv);
-}
-
static void nlua_print_event(void **argv)
{
char *str = argv[0];
@@ -508,7 +657,7 @@ int nlua_debug(lua_State *lstate)
for (;;) {
lua_settop(lstate, 0);
typval_T input;
- get_user_input(input_args, &input, false);
+ get_user_input(input_args, &input, false, false);
msg_putchar('\n'); // Avoid outputting on input line.
if (input.v_type != VAR_STRING
|| input.vval.v_string == NULL
@@ -534,6 +683,132 @@ int nlua_in_fast_event(lua_State *lstate)
return 1;
}
+int nlua_call(lua_State *lstate)
+{
+ Error err = ERROR_INIT;
+ size_t name_len;
+ const char_u *name = (const char_u *)luaL_checklstring(lstate, 1, &name_len);
+ if (!nlua_is_deferred_safe(lstate)) {
+ return luaL_error(lstate, e_luv_api_disabled, "vimL function");
+ }
+
+ int nargs = lua_gettop(lstate)-1;
+ if (nargs > MAX_FUNC_ARGS) {
+ return luaL_error(lstate, "Function called with too many arguments");
+ }
+
+ typval_T vim_args[MAX_FUNC_ARGS + 1];
+ int i = 0; // also used for freeing the variables
+ for (; i < nargs; i++) {
+ lua_pushvalue(lstate, (int)i+2);
+ if (!nlua_pop_typval(lstate, &vim_args[i])) {
+ api_set_error(&err, kErrorTypeException,
+ "error converting argument %d", i+1);
+ goto free_vim_args;
+ }
+ }
+
+ TRY_WRAP({
+ // TODO(bfredl): this should be simplified in error handling refactor
+ force_abort = false;
+ suppress_errthrow = false;
+ current_exception = NULL;
+ did_emsg = false;
+
+ try_start();
+ typval_T rettv;
+ int dummy;
+ // call_func() retval is deceptive, ignore it. Instead we set `msg_list`
+ // (TRY_WRAP) to capture abort-causing non-exception errors.
+ (void)call_func(name, (int)name_len, &rettv, nargs,
+ vim_args, NULL,
+ curwin->w_cursor.lnum, curwin->w_cursor.lnum,
+ &dummy, true, NULL, NULL);
+ if (!try_end(&err)) {
+ nlua_push_typval(lstate, &rettv, false);
+ }
+ tv_clear(&rettv);
+ });
+
+free_vim_args:
+ while (i > 0) {
+ tv_clear(&vim_args[--i]);
+ }
+ if (ERROR_SET(&err)) {
+ lua_pushstring(lstate, err.msg);
+ api_clear_error(&err);
+ return lua_error(lstate);
+ }
+ return 1;
+}
+
+static int nlua_rpcrequest(lua_State *lstate)
+{
+ if (!nlua_is_deferred_safe(lstate)) {
+ return luaL_error(lstate, e_luv_api_disabled, "rpcrequest");
+ }
+ return nlua_rpc(lstate, true);
+}
+
+static int nlua_rpcnotify(lua_State *lstate)
+{
+ return nlua_rpc(lstate, false);
+}
+
+static int nlua_rpc(lua_State *lstate, bool request)
+{
+ size_t name_len;
+ uint64_t chan_id = (uint64_t)luaL_checkinteger(lstate, 1);
+ const char *name = luaL_checklstring(lstate, 2, &name_len);
+ int nargs = lua_gettop(lstate)-2;
+ Error err = ERROR_INIT;
+ Array args = ARRAY_DICT_INIT;
+
+ for (int i = 0; i < nargs; i++) {
+ lua_pushvalue(lstate, (int)i+3);
+ ADD(args, nlua_pop_Object(lstate, false, &err));
+ if (ERROR_SET(&err)) {
+ api_free_array(args);
+ goto check_err;
+ }
+ }
+
+ if (request) {
+ Object result = rpc_send_call(chan_id, name, args, &err);
+ if (!ERROR_SET(&err)) {
+ nlua_push_Object(lstate, result, false);
+ api_free_object(result);
+ }
+ } else {
+ if (!rpc_send_event(chan_id, name, args)) {
+ api_set_error(&err, kErrorTypeValidation,
+ "Invalid channel: %"PRIu64, chan_id);
+ }
+ }
+
+check_err:
+ if (ERROR_SET(&err)) {
+ lua_pushstring(lstate, err.msg);
+ api_clear_error(&err);
+ return lua_error(lstate);
+ }
+
+ return request ? 1 : 0;
+}
+
+static int nlua_nil_tostring(lua_State *lstate)
+{
+ lua_pushstring(lstate, "vim.NIL");
+ return 1;
+}
+
+static int nlua_empty_dict_tostring(lua_State *lstate)
+{
+ lua_pushstring(lstate, "vim.empty_dict()");
+ return 1;
+}
+
+
#ifdef WIN32
/// os.getenv: override os.getenv to maintain coherency. #9681
///
@@ -568,12 +843,25 @@ void executor_free_luaref(LuaRef ref)
nlua_unref(lstate, ref);
}
-/// push a value referenced in the regirstry
+/// push a value referenced in the registry
void nlua_pushref(lua_State *lstate, LuaRef ref)
{
lua_rawgeti(lstate, LUA_REGISTRYINDEX, ref);
}
+/// Gets a new reference to an object stored at original_ref
+///
+/// NOTE: It does not copy the value, it creates a new ref to the lua object.
+/// Leaves the stack unchanged.
+LuaRef nlua_newref(lua_State *lstate, LuaRef original_ref)
+{
+ nlua_pushref(lstate, original_ref);
+ LuaRef new_ref = nlua_ref(lstate, -1);
+ lua_pop(lstate, 1);
+
+ return new_ref;
+}
+
/// Evaluate lua string
///
/// Used for luaeval().
@@ -587,10 +875,6 @@ void executor_eval_lua(const String str, typval_T *const arg,
typval_T *const ret_tv)
FUNC_ATTR_NONNULL_ALL
{
- lua_State *const lstate = nlua_enter();
-
- garray_T str_ga;
- ga_init(&str_ga, 1, 80);
#define EVALHEADER "local _A=select(1,...) return ("
const size_t lcmd_len = sizeof(EVALHEADER) - 1 + str.size + 1;
char *lcmd;
@@ -603,35 +887,118 @@ void executor_eval_lua(const String str, typval_T *const arg,
memcpy(lcmd + sizeof(EVALHEADER) - 1, str.data, str.size);
lcmd[lcmd_len - 1] = ')';
#undef EVALHEADER
- if (luaL_loadbuffer(lstate, lcmd, lcmd_len, NLUA_EVAL_NAME)) {
- nlua_error(lstate,
- _("E5107: Error while creating lua chunk for luaeval(): %.*s"));
- if (lcmd != (char *)IObuff) {
- xfree(lcmd);
- }
- return;
- }
+ typval_exec_lua(lcmd, lcmd_len, "luaeval()", arg, 1, true, ret_tv);
+
if (lcmd != (char *)IObuff) {
xfree(lcmd);
}
+}
- if (arg->v_type == VAR_UNKNOWN) {
- lua_pushnil(lstate);
+void executor_call_lua(const char *str, size_t len, typval_T *const args,
+ int argcount, typval_T *ret_tv)
+ FUNC_ATTR_NONNULL_ALL
+{
+#define CALLHEADER "return "
+#define CALLSUFFIX "(...)"
+ const size_t lcmd_len = sizeof(CALLHEADER) - 1 + len + sizeof(CALLSUFFIX) - 1;
+ char *lcmd;
+ if (lcmd_len < IOSIZE) {
+ lcmd = (char *)IObuff;
} else {
- nlua_push_typval(lstate, arg);
+ lcmd = xmalloc(lcmd_len);
+ }
+ memcpy(lcmd, CALLHEADER, sizeof(CALLHEADER) - 1);
+ memcpy(lcmd + sizeof(CALLHEADER) - 1, str, len);
+ memcpy(lcmd + sizeof(CALLHEADER) - 1 + len, CALLSUFFIX,
+ sizeof(CALLSUFFIX) - 1);
+#undef CALLHEADER
+#undef CALLSUFFIX
+
+ typval_exec_lua(lcmd, lcmd_len, "v:lua", args, argcount, false, ret_tv);
+
+ if (lcmd != (char *)IObuff) {
+ xfree(lcmd);
+ }
+}
+
+static void typval_exec_lua(const char *lcmd, size_t lcmd_len, const char *name,
+ typval_T *const args, int argcount, bool special,
+ typval_T *ret_tv)
+{
+ if (check_restricted() || check_secure()) {
+ if (ret_tv) {
+ ret_tv->v_type = VAR_NUMBER;
+ ret_tv->vval.v_number = 0;
+ }
+ return;
+ }
+
+ lua_State *const lstate = nlua_enter();
+ if (luaL_loadbuffer(lstate, lcmd, lcmd_len, name)) {
+ nlua_error(lstate, _("E5107: Error loading lua %.*s"));
+ return;
}
- if (lua_pcall(lstate, 1, 1, 0)) {
- nlua_error(lstate,
- _("E5108: Error while calling lua chunk for luaeval(): %.*s"));
+
+ PUSH_ALL_TYPVALS(lstate, args, argcount, special);
+
+ if (lua_pcall(lstate, argcount, ret_tv ? 1 : 0, 0)) {
+ nlua_error(lstate, _("E5108: Error executing lua %.*s"));
return;
}
- nlua_pop_typval(lstate, ret_tv);
+ if (ret_tv) {
+ nlua_pop_typval(lstate, ret_tv);
+ }
+}
+
+/// Call a LuaCallable given some typvals
+///
+/// Used to call any lua callable passed from Lua into VimL
+///
+/// @param[in] lstate Lua State
+/// @param[in] lua_cb Lua Callable
+/// @param[in] argcount Count of typval arguments
+/// @param[in] argvars Typval Arguments
+/// @param[out] rettv The return value from the called function.
+int typval_exec_lua_callable(
+ lua_State *lstate,
+ LuaCallable lua_cb,
+ int argcount,
+ typval_T *argvars,
+ typval_T *rettv
+)
+{
+ int offset = 0;
+ LuaRef cb = lua_cb.func_ref;
+
+ if (cb == LUA_NOREF) {
+ // This shouldn't happen.
+ luaL_error(lstate, "Invalid function passed to VimL");
+ return ERROR_OTHER;
+ }
+
+ nlua_pushref(lstate, cb);
+
+ if (lua_cb.table_ref != LUA_NOREF) {
+ offset += 1;
+ nlua_pushref(lstate, lua_cb.table_ref);
+ }
+
+ PUSH_ALL_TYPVALS(lstate, argvars, argcount, false);
+
+ if (lua_pcall(lstate, argcount + offset, 1, 0)) {
+ nlua_print(lstate);
+ return ERROR_OTHER;
+ }
+
+ nlua_pop_typval(lstate, rettv);
+
+ return ERROR_NONE;
}
-/// Execute lua string
+/// Execute Lua string
///
-/// Used for nvim_execute_lua().
+/// Used for nvim_exec_lua().
///
/// @param[in] str String to execute.
/// @param[in] args array of ... args
@@ -666,7 +1033,7 @@ Object executor_exec_lua_api(const String str, const Array args, Error *err)
}
Object executor_exec_lua_cb(LuaRef ref, const char *name, Array args,
- bool retval)
+ bool retval, Error *err)
{
lua_State *const lstate = nlua_enter();
nlua_pushref(lstate, ref);
@@ -676,16 +1043,24 @@ Object executor_exec_lua_cb(LuaRef ref, const char *name, Array args,
}
if (lua_pcall(lstate, (int)args.size+1, retval ? 1 : 0, 0)) {
- // TODO(bfredl): callbacks:s might not always be msg-safe, for instance
- // lua callbacks for redraw events. Later on let the caller deal with the
- // error instead.
- nlua_error(lstate, _("Error executing lua callback: %.*s"));
+ // if err is passed, the caller will deal with the error.
+ if (err) {
+ size_t len;
+ const char *errstr = lua_tolstring(lstate, -1, &len);
+ api_set_error(err, kErrorTypeException,
+ "Error executing lua: %.*s", (int)len, errstr);
+ } else {
+ nlua_error(lstate, _("Error executing lua callback: %.*s"));
+ }
return NIL;
}
- Error err = ERROR_INIT;
if (retval) {
- return nlua_pop_Object(lstate, false, &err);
+ Error dummy = ERROR_INIT;
+ if (err == NULL) {
+ err = &dummy;
+ }
+ return nlua_pop_Object(lstate, false, err);
} else {
return NIL;
}
@@ -712,9 +1087,8 @@ void ex_lua(exarg_T *const eap)
xfree(code);
return;
}
- typval_T tv = { .v_type = VAR_UNKNOWN };
- executor_exec_lua((String) { .data = code, .size = len }, &tv);
- tv_clear(&tv);
+ typval_exec_lua(code, len, ":lua", NULL, 0, false, NULL);
+
xfree(code);
}
@@ -752,8 +1126,8 @@ void ex_luado(exarg_T *const eap)
#undef DOSTART
#undef DOEND
- if (luaL_loadbuffer(lstate, lcmd, lcmd_len, NLUA_EVAL_NAME)) {
- nlua_error(lstate, _("E5109: Error while creating lua chunk: %.*s"));
+ if (luaL_loadbuffer(lstate, lcmd, lcmd_len, ":luado")) {
+ nlua_error(lstate, _("E5109: Error loading lua: %.*s"));
if (lcmd_len >= IOSIZE) {
xfree(lcmd);
}
@@ -763,7 +1137,7 @@ void ex_luado(exarg_T *const eap)
xfree(lcmd);
}
if (lua_pcall(lstate, 0, 1, 0)) {
- nlua_error(lstate, _("E5110: Error while creating lua function: %.*s"));
+ nlua_error(lstate, _("E5110: Error executing lua: %.*s"));
return;
}
for (linenr_T l = eap->line1; l <= eap->line2; l++) {
@@ -774,7 +1148,7 @@ void ex_luado(exarg_T *const eap)
lua_pushstring(lstate, (const char *)ml_get_buf(curbuf, l, false));
lua_pushnumber(lstate, (lua_Number)l);
if (lua_pcall(lstate, 2, 1, 0)) {
- nlua_error(lstate, _("E5111: Error while calling lua function: %.*s"));
+ nlua_error(lstate, _("E5111: Error calling lua: %.*s"));
break;
}
if (lua_isstring(lstate, -1)) {
@@ -816,3 +1190,270 @@ void ex_luafile(exarg_T *const eap)
return;
}
}
+
+static void nlua_add_treesitter(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
+{
+ tslua_init(lstate);
+
+ lua_pushcfunction(lstate, tslua_push_parser);
+ lua_setfield(lstate, -2, "_create_ts_parser");
+
+ lua_pushcfunction(lstate, tslua_add_language);
+ lua_setfield(lstate, -2, "_ts_add_language");
+
+ lua_pushcfunction(lstate, tslua_has_language);
+ lua_setfield(lstate, -2, "_ts_has_language");
+
+ lua_pushcfunction(lstate, tslua_inspect_lang);
+ lua_setfield(lstate, -2, "_ts_inspect_language");
+
+ lua_pushcfunction(lstate, ts_lua_parse_query);
+ lua_setfield(lstate, -2, "_ts_parse_query");
+}
+
+static int nlua_regex(lua_State *lstate)
+{
+ Error err = ERROR_INIT;
+ const char *text = luaL_checkstring(lstate, 1);
+ regprog_T *prog = NULL;
+
+ TRY_WRAP({
+ try_start();
+ prog = vim_regcomp((char_u *)text, RE_AUTO | RE_MAGIC | RE_STRICT);
+ try_end(&err);
+ });
+
+ if (ERROR_SET(&err)) {
+ return luaL_error(lstate, "couldn't parse regex: %s", err.msg);
+ }
+ assert(prog);
+
+ regprog_T **p = lua_newuserdata(lstate, sizeof(regprog_T *));
+ *p = prog;
+
+ lua_getfield(lstate, LUA_REGISTRYINDEX, "nvim_regex"); // [udata, meta]
+ lua_setmetatable(lstate, -2); // [udata]
+ return 1;
+}
+
+static regprog_T **regex_check(lua_State *L)
+{
+ return luaL_checkudata(L, 1, "nvim_regex");
+}
+
+
+static int regex_gc(lua_State *lstate)
+{
+ regprog_T **prog = regex_check(lstate);
+ vim_regfree(*prog);
+ return 0;
+}
+
+static int regex_tostring(lua_State *lstate)
+{
+ lua_pushstring(lstate, "<regex>");
+ return 1;
+}
+
+static int regex_match(lua_State *lstate, regprog_T **prog, char_u *str)
+{
+ regmatch_T rm;
+ rm.regprog = *prog;
+ rm.rm_ic = false;
+ bool match = vim_regexec(&rm, str, 0);
+ *prog = rm.regprog;
+
+ if (match) {
+ lua_pushinteger(lstate, (lua_Integer)(rm.startp[0]-str));
+ lua_pushinteger(lstate, (lua_Integer)(rm.endp[0]-str));
+ return 2;
+ }
+ return 0;
+}
+
+static int regex_match_str(lua_State *lstate)
+{
+ regprog_T **prog = regex_check(lstate);
+ const char *str = luaL_checkstring(lstate, 2);
+ int nret = regex_match(lstate, prog, (char_u *)str);
+
+ if (!*prog) {
+ return luaL_error(lstate, "regex: internal error");
+ }
+
+ return nret;
+}
+
+static int regex_match_line(lua_State *lstate)
+{
+ regprog_T **prog = regex_check(lstate);
+
+ int narg = lua_gettop(lstate);
+ if (narg < 3) {
+ return luaL_error(lstate, "not enough args");
+ }
+
+ long bufnr = luaL_checkinteger(lstate, 2);
+ long rownr = luaL_checkinteger(lstate, 3);
+ long start = 0, end = -1;
+ if (narg >= 4) {
+ start = luaL_checkinteger(lstate, 4);
+ }
+ if (narg >= 5) {
+ end = luaL_checkinteger(lstate, 5);
+ if (end < 0) {
+ return luaL_error(lstate, "invalid end");
+ }
+ }
+
+ buf_T *buf = bufnr ? handle_get_buffer((int)bufnr) : curbuf;
+ if (!buf || buf->b_ml.ml_mfp == NULL) {
+ return luaL_error(lstate, "invalid buffer");
+ }
+
+ if (rownr >= buf->b_ml.ml_line_count) {
+ return luaL_error(lstate, "invalid row");
+ }
+
+ char_u *line = ml_get_buf(buf, rownr+1, false);
+ size_t len = STRLEN(line);
+
+ if (start < 0 || (size_t)start > len) {
+ return luaL_error(lstate, "invalid start");
+ }
+
+ char_u save = NUL;
+ if (end >= 0) {
+ if ((size_t)end > len || end < start) {
+ return luaL_error(lstate, "invalid end");
+ }
+ save = line[end];
+ line[end] = NUL;
+ }
+
+ int nret = regex_match(lstate, prog, line+start);
+
+ if (end >= 0) {
+ line[end] = save;
+ }
+
+ if (!*prog) {
+ return luaL_error(lstate, "regex: internal error");
+ }
+
+ return nret;
+}
+
+int nlua_CFunction_func_call(
+ int argcount,
+ typval_T *argvars,
+ typval_T *rettv,
+ void *state)
+{
+ lua_State *const lstate = nlua_enter();
+ LuaCFunctionState *funcstate = (LuaCFunctionState *)state;
+
+ return typval_exec_lua_callable(
+ lstate,
+ funcstate->lua_callable,
+ argcount,
+ argvars,
+ rettv);
+}
+/// Required functions for lua c functions as VimL callbacks
+void nlua_CFunction_func_free(void *state)
+{
+ lua_State *const lstate = nlua_enter();
+ LuaCFunctionState *funcstate = (LuaCFunctionState *)state;
+
+ nlua_unref(lstate, funcstate->lua_callable.func_ref);
+ nlua_unref(lstate, funcstate->lua_callable.table_ref);
+ xfree(funcstate);
+}
+
+bool nlua_is_table_from_lua(typval_T *const arg)
+{
+ if (arg->v_type != VAR_DICT && arg->v_type != VAR_LIST) {
+ return false;
+ }
+
+ if (arg->v_type == VAR_DICT) {
+ return arg->vval.v_dict->lua_table_ref > 0
+ && arg->vval.v_dict->lua_table_ref != LUA_NOREF;
+ } else if (arg->v_type == VAR_LIST) {
+ return arg->vval.v_list->lua_table_ref > 0
+ && arg->vval.v_list->lua_table_ref != LUA_NOREF;
+ }
+
+ return false;
+}
+
+char_u *nlua_register_table_as_callable(typval_T *const arg)
+{
+ if (!nlua_is_table_from_lua(arg)) {
+ return NULL;
+ }
+
+ LuaRef table_ref;
+ if (arg->v_type == VAR_DICT) {
+ table_ref = arg->vval.v_dict->lua_table_ref;
+ } else if (arg->v_type == VAR_LIST) {
+ table_ref = arg->vval.v_list->lua_table_ref;
+ } else {
+ return NULL;
+ }
+
+ lua_State *const lstate = nlua_enter();
+
+#ifndef NDEBUG
+ int top = lua_gettop(lstate);
+#endif
+
+ nlua_pushref(lstate, table_ref);
+ if (!lua_getmetatable(lstate, -1)) {
+ return NULL;
+ }
+
+ lua_getfield(lstate, -1, "__call");
+ if (!lua_isfunction(lstate, -1)) {
+ return NULL;
+ }
+
+ LuaRef new_table_ref = nlua_newref(lstate, table_ref);
+
+ LuaCFunctionState *state = xmalloc(sizeof(LuaCFunctionState));
+ state->lua_callable.func_ref = nlua_ref(lstate, -1);
+ state->lua_callable.table_ref = new_table_ref;
+
+ char_u *name = register_cfunc(
+ &nlua_CFunction_func_call,
+ &nlua_CFunction_func_free,
+ state);
+
+
+ lua_pop(lstate, 3);
+ assert(top == lua_gettop(lstate));
+
+ return name;
+}
+
+/// Helper function to free a list_T
+void nlua_free_typval_list(list_T *const l)
+{
+ if (l->lua_table_ref != LUA_NOREF && l->lua_table_ref > 0) {
+ lua_State *const lstate = nlua_enter();
+ nlua_unref(lstate, l->lua_table_ref);
+ l->lua_table_ref = LUA_NOREF;
+ }
+}
+
+
+/// Helper function to free a dict_T
+void nlua_free_typval_dict(dict_T *const d)
+{
+ if (d->lua_table_ref != LUA_NOREF && d->lua_table_ref > 0) {
+ lua_State *const lstate = nlua_enter();
+ nlua_unref(lstate, d->lua_table_ref);
+ d->lua_table_ref = LUA_NOREF;
+ }
+}
diff --git a/src/nvim/lua/executor.h b/src/nvim/lua/executor.h
index 8d356a5600..6599b44584 100644
--- a/src/nvim/lua/executor.h
+++ b/src/nvim/lua/executor.h
@@ -8,10 +8,14 @@
#include "nvim/func_attr.h"
#include "nvim/eval/typval.h"
#include "nvim/ex_cmds_defs.h"
+#include "nvim/lua/converter.h"
// Generated by msgpack-gen.lua
void nlua_add_api_functions(lua_State *lstate) REAL_FATTR_NONNULL_ALL;
+EXTERN LuaRef nlua_nil_ref INIT(= LUA_NOREF);
+EXTERN LuaRef nlua_empty_dict_ref INIT(= LUA_NOREF);
+
#define set_api_error(s, err) \
do { \
Error *err_ = (err); \
diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c
new file mode 100644
index 0000000000..138031237e
--- /dev/null
+++ b/src/nvim/lua/treesitter.c
@@ -0,0 +1,1006 @@
+// 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 <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <inttypes.h>
+#include <assert.h>
+
+#include <lua.h>
+#include <lualib.h>
+#include <lauxlib.h>
+
+#include "tree_sitter/api.h"
+
+#include "nvim/lua/treesitter.h"
+#include "nvim/api/private/handle.h"
+#include "nvim/memline.h"
+#include "nvim/buffer.h"
+
+typedef struct {
+ TSParser *parser;
+ TSTree *tree; // internal tree, used for editing/reparsing
+} TSLua_parser;
+
+typedef struct {
+ TSQueryCursor *cursor;
+ int predicated_match;
+} TSLua_cursor;
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "lua/treesitter.c.generated.h"
+#endif
+
+static struct luaL_Reg parser_meta[] = {
+ { "__gc", parser_gc },
+ { "__tostring", parser_tostring },
+ { "parse_buf", parser_parse_buf },
+ { "edit", parser_edit },
+ { "tree", parser_tree },
+ { "set_included_ranges", parser_set_ranges },
+ { NULL, NULL }
+};
+
+static struct luaL_Reg tree_meta[] = {
+ { "__gc", tree_gc },
+ { "__tostring", tree_tostring },
+ { "root", tree_root },
+ { NULL, NULL }
+};
+
+static struct luaL_Reg node_meta[] = {
+ { "__tostring", node_tostring },
+ { "__eq", node_eq },
+ { "__len", node_child_count },
+ { "range", node_range },
+ { "start", node_start },
+ { "end_", node_end },
+ { "type", node_type },
+ { "symbol", node_symbol },
+ { "named", node_named },
+ { "missing", node_missing },
+ { "has_error", node_has_error },
+ { "sexpr", node_sexpr },
+ { "child_count", node_child_count },
+ { "named_child_count", node_named_child_count },
+ { "child", node_child },
+ { "named_child", node_named_child },
+ { "descendant_for_range", node_descendant_for_range },
+ { "named_descendant_for_range", node_named_descendant_for_range },
+ { "parent", node_parent },
+ { "_rawquery", node_rawquery },
+ { NULL, NULL }
+};
+
+static struct luaL_Reg query_meta[] = {
+ { "__gc", query_gc },
+ { "__tostring", query_tostring },
+ { "inspect", query_inspect },
+ { NULL, NULL }
+};
+
+// cursor is not exposed, but still needs garbage collection
+static struct luaL_Reg querycursor_meta[] = {
+ { "__gc", querycursor_gc },
+ { NULL, NULL }
+};
+
+static PMap(cstr_t) *langs;
+
+static void build_meta(lua_State *L, const char *tname, const luaL_Reg *meta)
+{
+ if (luaL_newmetatable(L, tname)) { // [meta]
+ luaL_register(L, NULL, meta);
+
+ lua_pushvalue(L, -1); // [meta, meta]
+ lua_setfield(L, -2, "__index"); // [meta]
+ }
+ lua_pop(L, 1); // [] (don't use it now)
+}
+
+/// init the tslua library
+///
+/// all global state is stored in the regirstry of the lua_State
+void tslua_init(lua_State *L)
+{
+ langs = pmap_new(cstr_t)();
+
+ // type metatables
+ build_meta(L, "treesitter_parser", parser_meta);
+ build_meta(L, "treesitter_tree", tree_meta);
+ build_meta(L, "treesitter_node", node_meta);
+ build_meta(L, "treesitter_query", query_meta);
+ build_meta(L, "treesitter_querycursor", querycursor_meta);
+}
+
+int tslua_has_language(lua_State *L)
+{
+ const char *lang_name = luaL_checkstring(L, 1);
+ lua_pushboolean(L, pmap_has(cstr_t)(langs, lang_name));
+ return 1;
+}
+
+int tslua_add_language(lua_State *L)
+{
+ if (lua_gettop(L) < 2 || !lua_isstring(L, 1) || !lua_isstring(L, 2)) {
+ return luaL_error(L, "string expected");
+ }
+
+ const char *path = lua_tostring(L, 1);
+ const char *lang_name = lua_tostring(L, 2);
+
+ if (pmap_has(cstr_t)(langs, lang_name)) {
+ return 0;
+ }
+
+#define BUFSIZE 128
+ char symbol_buf[BUFSIZE];
+ snprintf(symbol_buf, BUFSIZE, "tree_sitter_%s", lang_name);
+#undef BUFSIZE
+
+ uv_lib_t lib;
+ if (uv_dlopen(path, &lib)) {
+ snprintf((char *)IObuff, IOSIZE, "Failed to load parser: uv_dlopen: %s",
+ uv_dlerror(&lib));
+ uv_dlclose(&lib);
+ lua_pushstring(L, (char *)IObuff);
+ return lua_error(L);
+ }
+
+ TSLanguage *(*lang_parser)(void);
+ if (uv_dlsym(&lib, symbol_buf, (void **)&lang_parser)) {
+ snprintf((char *)IObuff, IOSIZE, "Failed to load parser: uv_dlsym: %s",
+ uv_dlerror(&lib));
+ uv_dlclose(&lib);
+ lua_pushstring(L, (char *)IObuff);
+ return lua_error(L);
+ }
+
+ TSLanguage *lang = lang_parser();
+ if (lang == NULL) {
+ return luaL_error(L, "Failed to load parser: internal error");
+ }
+
+ pmap_put(cstr_t)(langs, xstrdup(lang_name), lang);
+
+ lua_pushboolean(L, true);
+ return 1;
+}
+
+int tslua_inspect_lang(lua_State *L)
+{
+ if (lua_gettop(L) < 1 || !lua_isstring(L, 1)) {
+ return luaL_error(L, "string expected");
+ }
+ const char *lang_name = lua_tostring(L, 1);
+
+ TSLanguage *lang = pmap_get(cstr_t)(langs, lang_name);
+ if (!lang) {
+ return luaL_error(L, "no such language: %s", lang_name);
+ }
+
+ lua_createtable(L, 0, 2); // [retval]
+
+ size_t nsymbols = (size_t)ts_language_symbol_count(lang);
+
+ lua_createtable(L, nsymbols-1, 1); // [retval, symbols]
+ for (size_t i = 0; i < nsymbols; i++) {
+ TSSymbolType t = ts_language_symbol_type(lang, i);
+ if (t == TSSymbolTypeAuxiliary) {
+ // not used by the API
+ continue;
+ }
+ lua_createtable(L, 2, 0); // [retval, symbols, elem]
+ lua_pushstring(L, ts_language_symbol_name(lang, i));
+ lua_rawseti(L, -2, 1);
+ lua_pushboolean(L, t == TSSymbolTypeRegular);
+ lua_rawseti(L, -2, 2); // [retval, symbols, elem]
+ lua_rawseti(L, -2, i); // [retval, symbols]
+ }
+
+ lua_setfield(L, -2, "symbols"); // [retval]
+
+ size_t nfields = (size_t)ts_language_field_count(lang);
+ lua_createtable(L, nfields-1, 1); // [retval, fields]
+ for (size_t i = 0; i < nfields; i++) {
+ lua_pushstring(L, ts_language_field_name_for_id(lang, i));
+ lua_rawseti(L, -2, i); // [retval, fields]
+ }
+
+ lua_setfield(L, -2, "fields"); // [retval]
+ return 1;
+}
+
+int tslua_push_parser(lua_State *L)
+{
+ // Gather language
+ if (lua_gettop(L) < 1 || !lua_isstring(L, 1)) {
+ return luaL_error(L, "string expected");
+ }
+ const char *lang_name = lua_tostring(L, 1);
+ TSLanguage *lang = pmap_get(cstr_t)(langs, lang_name);
+ if (!lang) {
+ return luaL_error(L, "no such language: %s", lang_name);
+ }
+
+ TSParser *parser = ts_parser_new();
+ ts_parser_set_language(parser, lang);
+ TSLua_parser *p = lua_newuserdata(L, sizeof(TSLua_parser)); // [udata]
+ p->parser = parser;
+ p->tree = NULL;
+
+ lua_getfield(L, LUA_REGISTRYINDEX, "treesitter_parser"); // [udata, meta]
+ lua_setmetatable(L, -2); // [udata]
+ return 1;
+}
+
+static TSLua_parser *parser_check(lua_State *L)
+{
+ return luaL_checkudata(L, 1, "treesitter_parser");
+}
+
+static int parser_gc(lua_State *L)
+{
+ TSLua_parser *p = parser_check(L);
+ if (!p) {
+ return 0;
+ }
+
+ ts_parser_delete(p->parser);
+ if (p->tree) {
+ ts_tree_delete(p->tree);
+ }
+
+ return 0;
+}
+
+static int parser_tostring(lua_State *L)
+{
+ lua_pushstring(L, "<parser>");
+ return 1;
+}
+
+static const char *input_cb(void *payload, uint32_t byte_index,
+ TSPoint position, uint32_t *bytes_read)
+{
+ buf_T *bp = payload;
+#define BUFSIZE 256
+ static char buf[BUFSIZE];
+
+ if ((linenr_T)position.row >= bp->b_ml.ml_line_count) {
+ *bytes_read = 0;
+ return "";
+ }
+ char_u *line = ml_get_buf(bp, position.row+1, false);
+ size_t len = STRLEN(line);
+
+ if (position.column > len) {
+ *bytes_read = 0;
+ } else {
+ size_t tocopy = MIN(len-position.column, BUFSIZE);
+
+ memcpy(buf, line+position.column, tocopy);
+ // Translate embedded \n to NUL
+ memchrsub(buf, '\n', '\0', tocopy);
+ *bytes_read = (uint32_t)tocopy;
+ if (tocopy < BUFSIZE) {
+ // now add the final \n. If it didn't fit, input_cb will be called again
+ // on the same line with advanced column.
+ buf[tocopy] = '\n';
+ (*bytes_read)++;
+ }
+ }
+ return buf;
+#undef BUFSIZE
+}
+
+static int parser_parse_buf(lua_State *L)
+{
+ TSLua_parser *p = parser_check(L);
+ if (!p) {
+ return 0;
+ }
+
+ long bufnr = lua_tointeger(L, 2);
+ buf_T *buf = handle_get_buffer(bufnr);
+
+ if (!buf) {
+ return luaL_error(L, "invalid buffer handle: %d", bufnr);
+ }
+
+ TSInput input = { (void *)buf, input_cb, TSInputEncodingUTF8 };
+ TSTree *new_tree = ts_parser_parse(p->parser, p->tree, input);
+
+ uint32_t n_ranges = 0;
+ TSRange *changed = p->tree ? ts_tree_get_changed_ranges(p->tree, new_tree,
+ &n_ranges) : NULL;
+ if (p->tree) {
+ ts_tree_delete(p->tree);
+ }
+ p->tree = new_tree;
+
+ tslua_push_tree(L, p->tree);
+
+ lua_createtable(L, n_ranges, 0);
+ for (size_t i = 0; i < n_ranges; i++) {
+ lua_createtable(L, 4, 0);
+ lua_pushinteger(L, changed[i].start_point.row);
+ lua_rawseti(L, -2, 1);
+ lua_pushinteger(L, changed[i].start_point.column);
+ lua_rawseti(L, -2, 2);
+ lua_pushinteger(L, changed[i].end_point.row);
+ lua_rawseti(L, -2, 3);
+ lua_pushinteger(L, changed[i].end_point.column);
+ lua_rawseti(L, -2, 4);
+
+ lua_rawseti(L, -2, i+1);
+ }
+ xfree(changed);
+ return 2;
+}
+
+static int parser_tree(lua_State *L)
+{
+ TSLua_parser *p = parser_check(L);
+ if (!p) {
+ return 0;
+ }
+
+ tslua_push_tree(L, p->tree);
+ return 1;
+}
+
+static int parser_edit(lua_State *L)
+{
+ if (lua_gettop(L) < 10) {
+ lua_pushstring(L, "not enough args to parser:edit()");
+ return lua_error(L);
+ }
+
+ TSLua_parser *p = parser_check(L);
+ if (!p) {
+ return 0;
+ }
+
+ if (!p->tree) {
+ return 0;
+ }
+
+ long start_byte = lua_tointeger(L, 2);
+ long old_end_byte = lua_tointeger(L, 3);
+ long new_end_byte = lua_tointeger(L, 4);
+ TSPoint start_point = { lua_tointeger(L, 5), lua_tointeger(L, 6) };
+ TSPoint old_end_point = { lua_tointeger(L, 7), lua_tointeger(L, 8) };
+ TSPoint new_end_point = { lua_tointeger(L, 9), lua_tointeger(L, 10) };
+
+ TSInputEdit edit = { start_byte, old_end_byte, new_end_byte,
+ start_point, old_end_point, new_end_point };
+
+ ts_tree_edit(p->tree, &edit);
+
+ return 0;
+}
+
+static int parser_set_ranges(lua_State *L)
+{
+ if (lua_gettop(L) < 2) {
+ return luaL_error(
+ L,
+ "not enough args to parser:set_included_ranges()");
+ }
+
+ TSLua_parser *p = parser_check(L);
+ if (!p || !p->tree) {
+ return 0;
+ }
+
+ if (!lua_istable(L, 2)) {
+ return luaL_error(
+ L,
+ "argument for parser:set_included_ranges() should be a table.");
+ }
+
+ size_t tbl_len = lua_objlen(L, 2);
+ TSRange *ranges = xmalloc(sizeof(TSRange) * tbl_len);
+
+
+ // [ parser, ranges ]
+ for (size_t index = 0; index < tbl_len; index++) {
+ lua_rawgeti(L, 2, index + 1); // [ parser, ranges, range ]
+
+ TSNode node;
+ if (!node_check(L, -1, &node)) {
+ xfree(ranges);
+ return luaL_error(
+ L,
+ "ranges should be tables of nodes.");
+ }
+ lua_pop(L, 1); // [ parser, ranges ]
+
+ ranges[index] = (TSRange) {
+ .start_point = ts_node_start_point(node),
+ .end_point = ts_node_end_point(node),
+ .start_byte = ts_node_start_byte(node),
+ .end_byte = ts_node_end_byte(node)
+ };
+ }
+
+ // This memcpies ranges, thus we can free it afterwards
+ ts_parser_set_included_ranges(p->parser, ranges, tbl_len);
+ xfree(ranges);
+
+ return 0;
+}
+
+
+// Tree methods
+
+/// push tree interface on lua stack.
+///
+/// This makes a copy of the tree, so ownership of the argument is unaffected.
+void tslua_push_tree(lua_State *L, TSTree *tree)
+{
+ if (tree == NULL) {
+ lua_pushnil(L);
+ return;
+ }
+ TSTree **ud = lua_newuserdata(L, sizeof(TSTree *)); // [udata]
+ *ud = ts_tree_copy(tree);
+ lua_getfield(L, LUA_REGISTRYINDEX, "treesitter_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.
+ 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)
+{
+ TSTree **ud = luaL_checkudata(L, 1, "treesitter_tree");
+ return *ud;
+}
+
+static int tree_gc(lua_State *L)
+{
+ TSTree *tree = tree_check(L);
+ if (!tree) {
+ return 0;
+ }
+
+ ts_tree_delete(tree);
+ return 0;
+}
+
+static int tree_tostring(lua_State *L)
+{
+ lua_pushstring(L, "<tree>");
+ return 1;
+}
+
+static int tree_root(lua_State *L)
+{
+ TSTree *tree = tree_check(L);
+ if (!tree) {
+ return 0;
+ }
+ TSNode root = ts_tree_root_node(tree);
+ push_node(L, root, 1);
+ return 1;
+}
+
+// Node methods
+
+/// push node interface on lua stack
+///
+/// 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)
+{
+ assert(uindex > 0 || uindex < -LUA_MINSTACK);
+ if (ts_node_is_null(node)) {
+ lua_pushnil(L); // [nil]
+ return;
+ }
+ TSNode *ud = lua_newuserdata(L, sizeof(TSNode)); // [udata]
+ *ud = node;
+ lua_getfield(L, LUA_REGISTRYINDEX, "treesitter_node"); // [udata, meta]
+ lua_setmetatable(L, -2); // [udata]
+ lua_getfenv(L, uindex); // [udata, reftable]
+ lua_setfenv(L, -2); // [udata]
+}
+
+static bool node_check(lua_State *L, int index, TSNode *res)
+{
+ TSNode *ud = luaL_checkudata(L, index, "treesitter_node");
+ if (ud) {
+ *res = *ud;
+ return true;
+ }
+ return false;
+}
+
+
+static int node_tostring(lua_State *L)
+{
+ TSNode node;
+ if (!node_check(L, 1, &node)) {
+ return 0;
+ }
+ lua_pushstring(L, "<node ");
+ lua_pushstring(L, ts_node_type(node));
+ lua_pushstring(L, ">");
+ lua_concat(L, 3);
+ return 1;
+}
+
+static int node_eq(lua_State *L)
+{
+ TSNode node;
+ if (!node_check(L, 1, &node)) {
+ return 0;
+ }
+ // This should only be called if both x and y in "x == y" has the
+ // treesitter_node metatable. So it is ok to error out otherwise.
+ TSNode *ud = luaL_checkudata(L, 2, "treesitter_node");
+ if (!ud) {
+ return 0;
+ }
+ TSNode node2 = *ud;
+ lua_pushboolean(L, ts_node_eq(node, node2));
+ return 1;
+}
+
+static int node_range(lua_State *L)
+{
+ TSNode node;
+ if (!node_check(L, 1, &node)) {
+ return 0;
+ }
+ 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);
+ return 4;
+}
+
+static int node_start(lua_State *L)
+{
+ TSNode node;
+ if (!node_check(L, 1, &node)) {
+ return 0;
+ }
+ 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);
+ return 3;
+}
+
+static int node_end(lua_State *L)
+{
+ TSNode node;
+ if (!node_check(L, 1, &node)) {
+ return 0;
+ }
+ 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);
+ return 3;
+}
+
+static int node_child_count(lua_State *L)
+{
+ TSNode node;
+ if (!node_check(L, 1, &node)) {
+ return 0;
+ }
+ uint32_t count = ts_node_child_count(node);
+ lua_pushnumber(L, count);
+ return 1;
+}
+
+static int node_named_child_count(lua_State *L)
+{
+ TSNode node;
+ if (!node_check(L, 1, &node)) {
+ return 0;
+ }
+ uint32_t count = ts_node_named_child_count(node);
+ lua_pushnumber(L, count);
+ return 1;
+}
+
+static int node_type(lua_State *L)
+{
+ TSNode node;
+ if (!node_check(L, 1, &node)) {
+ return 0;
+ }
+ lua_pushstring(L, ts_node_type(node));
+ return 1;
+}
+
+static int node_symbol(lua_State *L)
+{
+ TSNode node;
+ if (!node_check(L, 1, &node)) {
+ return 0;
+ }
+ TSSymbol symbol = ts_node_symbol(node);
+ lua_pushnumber(L, symbol);
+ return 1;
+}
+
+static int node_named(lua_State *L)
+{
+ TSNode node;
+ if (!node_check(L, 1, &node)) {
+ return 0;
+ }
+ lua_pushboolean(L, ts_node_is_named(node));
+ return 1;
+}
+
+static int node_sexpr(lua_State *L)
+{
+ TSNode node;
+ if (!node_check(L, 1, &node)) {
+ return 0;
+ }
+ char *allocated = ts_node_string(node);
+ lua_pushstring(L, allocated);
+ xfree(allocated);
+ return 1;
+}
+
+static int node_missing(lua_State *L)
+{
+ TSNode node;
+ if (!node_check(L, 1, &node)) {
+ return 0;
+ }
+ lua_pushboolean(L, ts_node_is_missing(node));
+ return 1;
+}
+
+static int node_has_error(lua_State *L)
+{
+ TSNode node;
+ if (!node_check(L, 1, &node)) {
+ return 0;
+ }
+ lua_pushboolean(L, ts_node_has_error(node));
+ return 1;
+}
+
+static int node_child(lua_State *L)
+{
+ TSNode node;
+ if (!node_check(L, 1, &node)) {
+ return 0;
+ }
+ long num = lua_tointeger(L, 2);
+ TSNode child = ts_node_child(node, (uint32_t)num);
+
+ push_node(L, child, 1);
+ return 1;
+}
+
+static int node_named_child(lua_State *L)
+{
+ TSNode node;
+ if (!node_check(L, 1, &node)) {
+ return 0;
+ }
+ long num = lua_tointeger(L, 2);
+ TSNode child = ts_node_named_child(node, (uint32_t)num);
+
+ push_node(L, child, 1);
+ return 1;
+}
+
+static int node_descendant_for_range(lua_State *L)
+{
+ TSNode node;
+ if (!node_check(L, 1, &node)) {
+ return 0;
+ }
+ TSPoint start = { (uint32_t)lua_tointeger(L, 2),
+ (uint32_t)lua_tointeger(L, 3) };
+ TSPoint end = { (uint32_t)lua_tointeger(L, 4),
+ (uint32_t)lua_tointeger(L, 5) };
+ TSNode child = ts_node_descendant_for_point_range(node, start, end);
+
+ push_node(L, child, 1);
+ return 1;
+}
+
+static int node_named_descendant_for_range(lua_State *L)
+{
+ TSNode node;
+ if (!node_check(L, 1, &node)) {
+ return 0;
+ }
+ TSPoint start = { (uint32_t)lua_tointeger(L, 2),
+ (uint32_t)lua_tointeger(L, 3) };
+ TSPoint end = { (uint32_t)lua_tointeger(L, 4),
+ (uint32_t)lua_tointeger(L, 5) };
+ TSNode child = ts_node_named_descendant_for_point_range(node, start, end);
+
+ push_node(L, child, 1);
+ return 1;
+}
+
+static int node_parent(lua_State *L)
+{
+ TSNode node;
+ if (!node_check(L, 1, &node)) {
+ return 0;
+ }
+ TSNode parent = ts_node_parent(node);
+ push_node(L, parent, 1);
+ return 1;
+}
+
+/// assumes the match table being on top of the stack
+static void set_match(lua_State *L, TSQueryMatch *match, int nodeidx)
+{
+ for (int i = 0; i < match->capture_count; i++) {
+ push_node(L, match->captures[i].node, nodeidx);
+ lua_rawseti(L, -2, match->captures[i].index+1);
+ }
+}
+
+static int query_next_match(lua_State *L)
+{
+ TSLua_cursor *ud = lua_touserdata(L, lua_upvalueindex(1));
+ TSQueryCursor *cursor = ud->cursor;
+
+ TSQuery *query = query_check(L, lua_upvalueindex(3));
+ TSQueryMatch match;
+ if (ts_query_cursor_next_match(cursor, &match)) {
+ lua_pushinteger(L, match.pattern_index+1); // [index]
+ lua_createtable(L, ts_query_capture_count(query), 2); // [index, match]
+ set_match(L, &match, lua_upvalueindex(2));
+ return 2;
+ }
+ return 0;
+}
+
+
+static int query_next_capture(lua_State *L)
+{
+ TSLua_cursor *ud = lua_touserdata(L, lua_upvalueindex(1));
+ TSQueryCursor *cursor = ud->cursor;
+
+ TSQuery *query = query_check(L, lua_upvalueindex(3));
+
+ if (ud->predicated_match > -1) {
+ lua_getfield(L, lua_upvalueindex(4), "active");
+ bool active = lua_toboolean(L, -1);
+ lua_pop(L, 1);
+ if (!active) {
+ ts_query_cursor_remove_match(cursor, ud->predicated_match);
+ }
+ ud->predicated_match = -1;
+ }
+
+ TSQueryMatch match;
+ uint32_t capture_index;
+ if (ts_query_cursor_next_capture(cursor, &match, &capture_index)) {
+ TSQueryCapture capture = match.captures[capture_index];
+
+ lua_pushinteger(L, capture.index+1); // [index]
+ push_node(L, capture.node, lua_upvalueindex(2)); // [index, node]
+
+ uint32_t n_pred;
+ ts_query_predicates_for_pattern(query, match.pattern_index, &n_pred);
+ if (n_pred > 0 && capture_index == 0) {
+ lua_pushvalue(L, lua_upvalueindex(4)); // [index, node, match]
+ set_match(L, &match, lua_upvalueindex(2));
+ lua_pushinteger(L, match.pattern_index+1);
+ lua_setfield(L, -2, "pattern");
+
+ if (match.capture_count > 1) {
+ ud->predicated_match = match.id;
+ lua_pushboolean(L, false);
+ lua_setfield(L, -2, "active");
+ }
+ return 3;
+ }
+ return 2;
+ }
+ return 0;
+}
+
+static int node_rawquery(lua_State *L)
+{
+ TSNode node;
+ if (!node_check(L, 1, &node)) {
+ return 0;
+ }
+ TSQuery *query = query_check(L, 2);
+ // TODO(bfredl): these are expensive allegedly,
+ // use a reuse list later on?
+ TSQueryCursor *cursor = ts_query_cursor_new();
+ ts_query_cursor_exec(cursor, query, node);
+
+ bool captures = lua_toboolean(L, 3);
+
+ if (lua_gettop(L) >= 4) {
+ int start = luaL_checkinteger(L, 4);
+ int end = lua_gettop(L) >= 5 ? luaL_checkinteger(L, 5) : MAXLNUM;
+ ts_query_cursor_set_point_range(cursor,
+ (TSPoint){ start, 0 }, (TSPoint){ end, 0 });
+ }
+
+ TSLua_cursor *ud = lua_newuserdata(L, sizeof(*ud)); // [udata]
+ ud->cursor = cursor;
+ ud->predicated_match = -1;
+
+ lua_getfield(L, LUA_REGISTRYINDEX, "treesitter_querycursor");
+ lua_setmetatable(L, -2); // [udata]
+ lua_pushvalue(L, 1); // [udata, node]
+
+ // include query separately, as to keep a ref to it for gc
+ lua_pushvalue(L, 2); // [udata, node, query]
+
+ if (captures) {
+ // placeholder for match state
+ lua_createtable(L, ts_query_capture_count(query), 2); // [u, n, q, match]
+ lua_pushcclosure(L, query_next_capture, 4); // [closure]
+ } else {
+ lua_pushcclosure(L, query_next_match, 3); // [closure]
+ }
+
+ return 1;
+}
+
+static int querycursor_gc(lua_State *L)
+{
+ TSLua_cursor *ud = luaL_checkudata(L, 1, "treesitter_querycursor");
+ ts_query_cursor_delete(ud->cursor);
+ return 0;
+}
+
+// Query methods
+
+int ts_lua_parse_query(lua_State *L)
+{
+ if (lua_gettop(L) < 2 || !lua_isstring(L, 1) || !lua_isstring(L, 2)) {
+ return luaL_error(L, "string expected");
+ }
+
+ const char *lang_name = lua_tostring(L, 1);
+ TSLanguage *lang = pmap_get(cstr_t)(langs, lang_name);
+ if (!lang) {
+ return luaL_error(L, "no such language: %s", lang_name);
+ }
+
+ size_t len;
+ const char *src = lua_tolstring(L, 2, &len);
+
+ uint32_t error_offset;
+ TSQueryError error_type;
+ TSQuery *query = ts_query_new(lang, src, len, &error_offset, &error_type);
+
+ if (!query) {
+ return luaL_error(L, "query: %s at position %d",
+ query_err_string(error_type), (int)error_offset);
+ }
+
+ TSQuery **ud = lua_newuserdata(L, sizeof(TSQuery *)); // [udata]
+ *ud = query;
+ lua_getfield(L, LUA_REGISTRYINDEX, "treesitter_query"); // [udata, meta]
+ lua_setmetatable(L, -2); // [udata]
+ return 1;
+}
+
+
+static const char *query_err_string(TSQueryError err) {
+ switch (err) {
+ case TSQueryErrorSyntax: return "invalid syntax";
+ case TSQueryErrorNodeType: return "invalid node type";
+ case TSQueryErrorField: return "invalid field";
+ case TSQueryErrorCapture: return "invalid capture";
+ default: return "error";
+ }
+}
+
+static TSQuery *query_check(lua_State *L, int index)
+{
+ TSQuery **ud = luaL_checkudata(L, index, "treesitter_query");
+ return *ud;
+}
+
+static int query_gc(lua_State *L)
+{
+ TSQuery *query = query_check(L, 1);
+ if (!query) {
+ return 0;
+ }
+
+ ts_query_delete(query);
+ return 0;
+}
+
+static int query_tostring(lua_State *L)
+{
+ lua_pushstring(L, "<query>");
+ return 1;
+}
+
+static int query_inspect(lua_State *L)
+{
+ TSQuery *query = query_check(L, 1);
+ if (!query) {
+ return 0;
+ }
+
+ uint32_t n_pat = ts_query_pattern_count(query);
+ lua_createtable(L, 0, 2); // [retval]
+ lua_createtable(L, n_pat, 1); // [retval, patterns]
+ for (size_t i = 0; i < n_pat; i++) {
+ uint32_t len;
+ const TSQueryPredicateStep *step = ts_query_predicates_for_pattern(query,
+ i, &len);
+ if (len == 0) {
+ continue;
+ }
+ lua_createtable(L, len/4, 1); // [retval, patterns, pat]
+ lua_createtable(L, 3, 0); // [retval, patterns, pat, pred]
+ int nextpred = 1;
+ int nextitem = 1;
+ for (size_t k = 0; k < len; k++) {
+ if (step[k].type == TSQueryPredicateStepTypeDone) {
+ lua_rawseti(L, -2, nextpred++); // [retval, patterns, pat]
+ lua_createtable(L, 3, 0); // [retval, patterns, pat, pred]
+ nextitem = 1;
+ continue;
+ }
+
+ if (step[k].type == TSQueryPredicateStepTypeString) {
+ uint32_t strlen;
+ const char *str = ts_query_string_value_for_id(query, step[k].value_id,
+ &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]
+ } else {
+ abort();
+ }
+ lua_rawseti(L, -2, nextitem++); // [retval, patterns, pat, pred]
+ }
+ // last predicate should have ended with TypeDone
+ lua_pop(L, 1); // [retval, patters, pat]
+ lua_rawseti(L, -2, i+1); // [retval, patterns]
+ }
+ lua_setfield(L, -2, "patterns"); // [retval]
+
+ uint32_t n_captures = ts_query_capture_count(query);
+ lua_createtable(L, n_captures, 0); // [retval, captures]
+ for (size_t i = 0; i < n_captures; i++) {
+ uint32_t strlen;
+ const char *str = ts_query_capture_name_for_id(query, i, &strlen);
+ lua_pushlstring(L, str, strlen); // [retval, captures, capture]
+ lua_rawseti(L, -2, i+1);
+ }
+ lua_setfield(L, -2, "captures"); // [retval]
+
+ return 1;
+}
diff --git a/src/nvim/lua/treesitter.h b/src/nvim/lua/treesitter.h
new file mode 100644
index 0000000000..812166f67b
--- /dev/null
+++ b/src/nvim/lua/treesitter.h
@@ -0,0 +1,14 @@
+#ifndef NVIM_LUA_TREESITTER_H
+#define NVIM_LUA_TREESITTER_H
+
+#include <lua.h>
+#include <lualib.h>
+#include <lauxlib.h>
+
+#include "tree_sitter/api.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "lua/treesitter.h.generated.h"
+#endif
+
+#endif // NVIM_LUA_TREESITTER_H
diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua
index a03e97490d..820b237c4f 100644
--- a/src/nvim/lua/vim.lua
+++ b/src/nvim/lua/vim.lua
@@ -33,35 +33,35 @@
-- - https://github.com/bakpakin/Fennel (pretty print, repl)
-- - https://github.com/howl-editor/howl/tree/master/lib/howl/util
+local vim = vim
+assert(vim)
-- Internal-only until comments in #8107 are addressed.
-- Returns:
-- {errcode}, {output}
-local function _system(cmd)
- local out = vim.api.nvim_call_function('system', { cmd })
- local err = vim.api.nvim_get_vvar('shell_error')
+function vim._system(cmd)
+ local out = vim.fn.system(cmd)
+ local err = vim.v.shell_error
return err, out
end
-- Gets process info from the `ps` command.
-- Used by nvim_get_proc() as a fallback.
-local function _os_proc_info(pid)
+function vim._os_proc_info(pid)
if pid == nil or pid <= 0 or type(pid) ~= 'number' then
error('invalid pid')
end
local cmd = { 'ps', '-p', pid, '-o', 'comm=', }
- local err, name = _system(cmd)
- if 1 == err and string.gsub(name, '%s*', '') == '' then
+ local err, name = vim._system(cmd)
+ if 1 == err and vim.trim(name) == '' then
return {} -- Process not found.
elseif 0 ~= err then
- local args_str = vim.api.nvim_call_function('string', { cmd })
- error('command failed: '..args_str)
+ error('command failed: '..vim.fn.string(cmd))
end
- local _, ppid = _system({ 'ps', '-p', pid, '-o', 'ppid=', })
+ local _, ppid = vim._system({ 'ps', '-p', pid, '-o', 'ppid=', })
-- Remove trailing whitespace.
- name = string.gsub(string.gsub(name, '%s+$', ''), '^.*/', '')
- ppid = string.gsub(ppid, '%s+$', '')
- ppid = tonumber(ppid) == nil and -1 or tonumber(ppid)
+ name = vim.trim(name):gsub('^.*/', '')
+ ppid = tonumber(ppid) or -1
return {
name = name,
pid = pid,
@@ -71,20 +71,19 @@ end
-- Gets process children from the `pgrep` command.
-- Used by nvim_get_proc_children() as a fallback.
-local function _os_proc_children(ppid)
+function vim._os_proc_children(ppid)
if ppid == nil or ppid <= 0 or type(ppid) ~= 'number' then
error('invalid ppid')
end
local cmd = { 'pgrep', '-P', ppid, }
- local err, rv = _system(cmd)
- if 1 == err and string.gsub(rv, '%s*', '') == '' then
+ local err, rv = vim._system(cmd)
+ if 1 == err and vim.trim(rv) == '' then
return {} -- Process not found.
elseif 0 ~= err then
- local args_str = vim.api.nvim_call_function('string', { cmd })
- error('command failed: '..args_str)
+ error('command failed: '..vim.fn.string(cmd))
end
local children = {}
- for s in string.gmatch(rv, '%S+') do
+ for s in rv:gmatch('%S+') do
local i = tonumber(s)
if i ~= nil then
table.insert(children, i)
@@ -98,7 +97,7 @@ end
-- Last inserted paths. Used to clear out items from package.[c]path when they
-- are no longer in &runtimepath.
local last_nvim_paths = {}
-local function _update_package_paths()
+function vim._update_package_paths()
local cur_nvim_paths = {}
local rtps = vim.api.nvim_list_runtime_paths()
local sep = package.config:sub(1, 1)
@@ -162,22 +161,35 @@ local function inspect(object, options) -- luacheck: no unused
error(object, options) -- Stub for gen_vimdoc.py
end
---- Paste handler, invoked by |nvim_paste()| when a conforming UI
---- (such as the |TUI|) pastes text into the editor.
----
---@see |paste|
----
---@param lines |readfile()|-style list of lines to paste. |channel-lines|
---@param phase -1: "non-streaming" paste: the call contains all lines.
---- If paste is "streamed", `phase` indicates the stream state:
---- - 1: starts the paste (exactly once)
---- - 2: continues the paste (zero or more times)
---- - 3: ends the paste (exactly once)
---@returns false if client should cancel the paste.
-local function paste(lines, phase) end -- luacheck: no unused
-paste = (function()
+do
local tdots, tick, got_line1 = 0, 0, false
- return function(lines, phase)
+
+ --- 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:
+ --- <pre>
+ --- 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)
+ --- </pre>
+ ---
+ --@see |paste|
+ ---
+ --@param lines |readfile()|-style list of lines to paste. |channel-lines|
+ --@param phase -1: "non-streaming" paste: the call contains all lines.
+ --- If paste is "streamed", `phase` indicates the stream state:
+ --- - 1: starts the paste (exactly once)
+ --- - 2: continues the paste (zero or more times)
+ --- - 3: ends the paste (exactly once)
+ --@returns false if client should cancel the paste.
+ function vim.paste(lines, phase)
local call = vim.api.nvim_call_function
local now = vim.loop.now()
local mode = call('mode', {}):sub(1,1)
@@ -189,14 +201,29 @@ paste = (function()
if mode == 'c' and not got_line1 then -- cmdline-mode: paste only 1 line.
got_line1 = (#lines > 1)
vim.api.nvim_set_option('paste', true) -- For nvim_input().
- local line1, _ = string.gsub(lines[1], '[\r\n\012\027]', ' ') -- Scrub.
+ local line1 = lines[1]:gsub('<', '<lt>'):gsub('[\r\n\012\027]', ' ') -- Scrub.
vim.api.nvim_input(line1)
vim.api.nvim_set_option('paste', false)
- elseif mode ~= 'c' then -- Else: discard remaining cmdline-mode chunks.
- if phase < 2 and mode ~= 'i' and mode ~= 'R' and mode ~= 't' then
+ elseif mode ~= 'c' then
+ if phase < 2 and mode:find('^[vV\22sS\19]') then
+ vim.api.nvim_command([[exe "normal! \<Del>"]])
+ vim.api.nvim_put(lines, 'c', false, true)
+ elseif phase < 2 and not mode:find('^[iRt]') then
vim.api.nvim_put(lines, 'c', true, true)
-- XXX: Normal-mode: workaround bad cursor-placement after first chunk.
vim.api.nvim_command('normal! a')
+ elseif phase < 2 and mode == 'R' then
+ local nchars = 0
+ for _, line in ipairs(lines) do
+ nchars = nchars + line:len()
+ end
+ local row, col = unpack(vim.api.nvim_win_get_cursor(0))
+ local bufline = vim.api.nvim_buf_get_lines(0, row-1, row, true)[1]
+ local firstline = lines[1]
+ firstline = bufline:sub(1, col)..firstline
+ lines[1] = firstline
+ lines[#lines] = lines[#lines]..bufline:sub(col + nchars + 1, bufline:len())
+ vim.api.nvim_buf_set_lines(0, row-1, row, false, lines)
else
vim.api.nvim_put(lines, 'c', false, true)
end
@@ -214,42 +241,252 @@ paste = (function()
end
return true -- Paste will not continue if not returning `true`.
end
-end)()
+end
--- Defers callback `cb` until the Nvim API is safe to call.
---
---@see |lua-loop-callbacks|
---@see |vim.schedule()|
---@see |vim.in_fast_event()|
-local function schedule_wrap(cb)
+function vim.schedule_wrap(cb)
return (function (...)
local args = {...}
vim.schedule(function() cb(unpack(args)) end)
end)
end
+--- <Docs described in |vim.empty_dict()| >
+--@private
+function vim.empty_dict()
+ return setmetatable({}, vim._empty_dict_mt)
+end
+
+-- vim.fn.{func}(...)
+vim.fn = setmetatable({}, {
+ __index = function(t, key)
+ local function _fn(...)
+ return vim.call(key, ...)
+ end
+ t[key] = _fn
+ return _fn
+ end
+})
+
+vim.funcref = function(viml_func_name)
+ return vim.fn[viml_func_name]
+end
+
+-- These are for loading runtime modules lazily since they aren't available in
+-- the nvim binary as specified in executor.c
local function __index(t, key)
if key == 'inspect' then
t.inspect = require('vim.inspect')
return t.inspect
- elseif require('vim.shared')[key] ~= nil then
- -- Expose all `vim.shared` functions on the `vim` module.
- t[key] = require('vim.shared')[key]
+ elseif key == 'treesitter' then
+ t.treesitter = require('vim.treesitter')
+ return t.treesitter
+ elseif require('vim.uri')[key] ~= nil then
+ -- Expose all `vim.uri` functions on the `vim` module.
+ t[key] = require('vim.uri')[key]
return t[key]
+ elseif key == 'lsp' then
+ t.lsp = require('vim.lsp')
+ return t.lsp
+ elseif key == 'highlight' then
+ t.highlight = require('vim.highlight')
+ return t.highlight
end
end
-local module = {
- _update_package_paths = _update_package_paths,
- _os_proc_children = _os_proc_children,
- _os_proc_info = _os_proc_info,
- _system = _system,
- paste = paste,
- schedule_wrap = schedule_wrap,
-}
-
-setmetatable(module, {
+setmetatable(vim, {
__index = __index
})
+-- An easier alias for commands.
+vim.cmd = vim.api.nvim_command
+
+-- These are the vim.env/v/g/o/bo/wo variable magic accessors.
+do
+ local a = vim.api
+ local validate = vim.validate
+ local function make_meta_accessor(get, set, del)
+ validate {
+ get = {get, 'f'};
+ set = {set, 'f'};
+ del = {del, 'f', true};
+ }
+ local mt = {}
+ if del then
+ function mt:__newindex(k, v)
+ if v == nil then
+ return del(k)
+ end
+ return set(k, v)
+ end
+ else
+ function mt:__newindex(k, v)
+ return set(k, v)
+ end
+ end
+ function mt:__index(k)
+ return get(k)
+ end
+ return setmetatable({}, mt)
+ end
+ local function pcall_ret(status, ...)
+ if status then return ... end
+ end
+ local function nil_wrap(fn)
+ return function(...)
+ return pcall_ret(pcall(fn, ...))
+ end
+ end
+
+ vim.b = make_meta_accessor(
+ nil_wrap(function(v) return a.nvim_buf_get_var(0, v) end),
+ function(v, k) return a.nvim_buf_set_var(0, v, k) end,
+ function(v) return a.nvim_buf_del_var(0, v) end
+ )
+ vim.w = make_meta_accessor(
+ nil_wrap(function(v) return a.nvim_win_get_var(0, v) end),
+ function(v, k) return a.nvim_win_set_var(0, v, k) end,
+ function(v) return a.nvim_win_del_var(0, v) end
+ )
+ vim.t = make_meta_accessor(
+ nil_wrap(function(v) return a.nvim_tabpage_get_var(0, v) end),
+ function(v, k) return a.nvim_tabpage_set_var(0, v, k) end,
+ function(v) return a.nvim_tabpage_del_var(0, v) end
+ )
+ vim.g = make_meta_accessor(nil_wrap(a.nvim_get_var), a.nvim_set_var, a.nvim_del_var)
+ vim.v = make_meta_accessor(nil_wrap(a.nvim_get_vvar), a.nvim_set_vvar)
+ vim.o = make_meta_accessor(a.nvim_get_option, a.nvim_set_option)
+
+ local function getenv(k)
+ local v = vim.fn.getenv(k)
+ if v == vim.NIL then
+ return nil
+ end
+ return v
+ end
+ vim.env = make_meta_accessor(getenv, vim.fn.setenv)
+ -- TODO(ashkan) if/when these are available from an API, generate them
+ -- instead of hardcoding.
+ local window_options = {
+ arab = true; arabic = true; breakindent = true; breakindentopt = true;
+ bri = true; briopt = true; cc = true; cocu = true;
+ cole = true; colorcolumn = true; concealcursor = true; conceallevel = true;
+ crb = true; cuc = true; cul = true; cursorbind = true;
+ cursorcolumn = true; cursorline = true; diff = true; fcs = true;
+ fdc = true; fde = true; fdi = true; fdl = true;
+ fdm = true; fdn = true; fdt = true; fen = true;
+ fillchars = true; fml = true; fmr = true; foldcolumn = true;
+ foldenable = true; foldexpr = true; foldignore = true; foldlevel = true;
+ foldmarker = true; foldmethod = true; foldminlines = true; foldnestmax = true;
+ foldtext = true; lbr = true; lcs = true; linebreak = true;
+ list = true; listchars = true; nu = true; number = true;
+ numberwidth = true; nuw = true; previewwindow = true; pvw = true;
+ relativenumber = true; rightleft = true; rightleftcmd = true; rl = true;
+ rlc = true; rnu = true; scb = true; scl = true;
+ scr = true; scroll = true; scrollbind = true; signcolumn = true;
+ spell = true; statusline = true; stl = true; wfh = true;
+ wfw = true; winbl = true; winblend = true; winfixheight = true;
+ winfixwidth = true; winhighlight = true; winhl = true; wrap = true;
+ }
+ local function new_buf_opt_accessor(bufnr)
+ local function get(k)
+ if window_options[k] then
+ return a.nvim_err_writeln(k.." is a window option, not a buffer option")
+ end
+ if bufnr == nil and type(k) == "number" then
+ return new_buf_opt_accessor(k)
+ end
+ return a.nvim_buf_get_option(bufnr or 0, k)
+ end
+ local function set(k, v)
+ if window_options[k] then
+ return a.nvim_err_writeln(k.." is a window option, not a buffer option")
+ end
+ return a.nvim_buf_set_option(bufnr or 0, k, v)
+ end
+ return make_meta_accessor(get, set)
+ end
+ vim.bo = new_buf_opt_accessor(nil)
+ local function new_win_opt_accessor(winnr)
+ local function get(k)
+ if winnr == nil and type(k) == "number" then
+ return new_win_opt_accessor(k)
+ end
+ return a.nvim_win_get_option(winnr or 0, k)
+ end
+ local function set(k, v) return a.nvim_win_set_option(winnr or 0, k, v) end
+ return make_meta_accessor(get, set)
+ end
+ vim.wo = new_win_opt_accessor(nil)
+end
+
+--- Get a table of lines with start, end columns for a region marked by two points
+---
+--@param bufnr number of buffer
+--@param pos1 (line, column) tuple marking beginning of region
+--@param pos2 (line, column) tuple marking end of region
+--@param regtype type of selection (:help setreg)
+--@param inclusive boolean indicating whether the selection is end-inclusive
+--@return region lua table of the form {linenr = {startcol,endcol}}
+function vim.region(bufnr, pos1, pos2, regtype, inclusive)
+ if not vim.api.nvim_buf_is_loaded(bufnr) then
+ vim.fn.bufload(bufnr)
+ end
+
+ -- 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]
+ pos1[2] = vim.str_utfindex(bufline, pos1[2])
+ end
+
+ local region = {}
+ for l = pos1[1], pos2[1] do
+ local c1, c2
+ if regtype:byte() == 22 then -- block selection: take width from regtype
+ 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]
+ if c1 < #bufline then
+ c1 = vim.str_byteindex(bufline, c1)
+ end
+ if c2 < #bufline then
+ c2 = vim.str_byteindex(bufline, c2)
+ end
+ else
+ c1 = (l == pos1[1]) and (pos1[2]) or 0
+ c2 = (l == pos2[1]) and (pos2[2] + (inclusive and 1 or 0)) or -1
+ end
+ table.insert(region, l, {c1, c2})
+ end
+ return region
+end
+
+--- Defers calling `fn` until `timeout` ms passes.
+---
+--- Use to do a one-shot timer that calls `fn`
+--- Note: The {fn} is |schedule_wrap|ped automatically, so API functions are
+--- safe to call.
+--@param fn Callback to call once `timeout` expires
+--@param timeout Number of milliseconds to wait before calling `fn`
+--@return timer luv timer object
+function vim.defer_fn(fn, timeout)
+ vim.validate { fn = { fn, 'c', true}; }
+ local timer = vim.loop.new_timer()
+ timer:start(timeout, 0, vim.schedule_wrap(function()
+ timer:stop()
+ timer:close()
+
+ fn()
+ end))
+
+ return timer
+end
+
return module
diff --git a/src/nvim/macros.h b/src/nvim/macros.h
index d11507fa6a..3df7fa768d 100644
--- a/src/nvim/macros.h
+++ b/src/nvim/macros.h
@@ -36,38 +36,34 @@
#define BUFEMPTY() (curbuf->b_ml.ml_line_count == 1 && *ml_get((linenr_T)1) == \
NUL)
-/*
- * toupper() and tolower() that use the current locale.
- * Careful: Only call TOUPPER_LOC() and TOLOWER_LOC() with a character in the
- * range 0 - 255. toupper()/tolower() on some systems can't handle others.
- * Note: It is often better to use mb_tolower() and mb_toupper(), because many
- * toupper() and tolower() implementations only work for ASCII.
- */
+// toupper() and tolower() that use the current locale.
+// Careful: Only call TOUPPER_LOC() and TOLOWER_LOC() with a character in the
+// range 0 - 255. toupper()/tolower() on some systems can't handle others.
+// Note: It is often better to use mb_tolower() and mb_toupper(), because many
+// toupper() and tolower() implementations only work for ASCII.
#define TOUPPER_LOC toupper
#define TOLOWER_LOC tolower
-/* toupper() and tolower() for ASCII only and ignore the current locale. */
+// toupper() and tolower() for ASCII only and ignore the current locale.
# define TOUPPER_ASC(c) (((c) < 'a' || (c) > 'z') ? (c) : (c) - ('a' - 'A'))
# define TOLOWER_ASC(c) (((c) < 'A' || (c) > 'Z') ? (c) : (c) + ('a' - 'A'))
-/* Like isalpha() but reject non-ASCII characters. Can't be used with a
- * special key (negative value). */
+// Like isalpha() but reject non-ASCII characters. Can't be used with a
+// special key (negative value).
# define ASCII_ISLOWER(c) ((unsigned)(c) >= 'a' && (unsigned)(c) <= 'z')
# define ASCII_ISUPPER(c) ((unsigned)(c) >= 'A' && (unsigned)(c) <= 'Z')
# define ASCII_ISALPHA(c) (ASCII_ISUPPER(c) || ASCII_ISLOWER(c))
# define ASCII_ISALNUM(c) (ASCII_ISALPHA(c) || ascii_isdigit(c))
-/* Returns empty string if it is NULL. */
+// Returns empty string if it is NULL.
#define EMPTY_IF_NULL(x) ((x) ? (x) : (char_u *)"")
-/*
- * Adjust chars in a language according to 'langmap' option.
- * NOTE that there is no noticeable overhead if 'langmap' is not set.
- * When set the overhead for characters < 256 is small.
- * Don't apply 'langmap' if the character comes from the Stuff buffer or from a
- * mapping and the langnoremap option was set.
- * The do-while is just to ignore a ';' after the macro.
- */
+// Adjust chars in a language according to 'langmap' option.
+// NOTE that there is no noticeable overhead if 'langmap' is not set.
+// When set the overhead for characters < 256 is small.
+// Don't apply 'langmap' if the character comes from the Stuff buffer or from a
+// mapping and the langnoremap option was set.
+// The do-while is just to ignore a ';' after the macro.
# define LANGMAP_ADJUST(c, condition) \
do { \
if (*p_langmap \
@@ -83,12 +79,12 @@
} \
} while (0)
-#define WRITEBIN "wb" /* no CR-LF translation */
+#define WRITEBIN "wb" // no CR-LF translation
#define READBIN "rb"
#define APPENDBIN "ab"
-/* mch_open_rw(): invoke os_open() with third argument for user R/W. */
-#if defined(UNIX) /* open in rw------- mode */
+// mch_open_rw(): invoke os_open() with third argument for user R/W.
+#if defined(UNIX) // open in rw------- mode
# define mch_open_rw(n, f) os_open((n), (f), (mode_t)0600)
#elif defined(WIN32)
# define mch_open_rw(n, f) os_open((n), (f), S_IREAD | S_IWRITE)
@@ -100,7 +96,7 @@
# define UTF_COMPOSINGLIKE(p1, p2) utf_composinglike((p1), (p2))
-/* Whether to draw the vertical bar on the right side of the cell. */
+// Whether to draw the vertical bar on the right side of the cell.
# define CURSOR_BAR_RIGHT (curwin->w_p_rl && (!(State & CMDLINE) || cmdmsg_rl))
// MB_PTR_ADV(): advance a pointer to the next character, taking care of
@@ -110,8 +106,6 @@
// MB_COPY_CHAR(f, t): copy one char from "f" to "t" and advance the pointers.
// PTR2CHAR(): get character from pointer.
-// Get the length of the character p points to, including composing chars.
-# define MB_PTR2LEN(p) mb_ptr2len(p)
// Advance multi-byte pointer, skip over composing chars.
# define MB_PTR_ADV(p) (p += mb_ptr2len((char_u *)p))
// Advance multi-byte pointer, do not skip over composing chars.
diff --git a/src/nvim/main.c b/src/nvim/main.c
index be1f08bb46..f79fb57eae 100644
--- a/src/nvim/main.c
+++ b/src/nvim/main.c
@@ -10,6 +10,7 @@
#include <msgpack.h>
#include "nvim/ascii.h"
+#include "nvim/channel.h"
#include "nvim/vim.h"
#include "nvim/main.h"
#include "nvim/aucmd.h"
@@ -27,6 +28,7 @@
#include "nvim/highlight.h"
#include "nvim/iconv.h"
#include "nvim/if_cscope.h"
+#include "nvim/lua/executor.h"
#ifdef HAVE_LOCALE_H
# include <locale.h>
#endif
@@ -63,6 +65,9 @@
#include "nvim/os/os.h"
#include "nvim/os/time.h"
#include "nvim/os/fileio.h"
+#ifdef WIN32
+# include "nvim/os/os_win_console.h"
+#endif
#include "nvim/event/loop.h"
#include "nvim/os/signal.h"
#include "nvim/event/process.h"
@@ -79,44 +84,11 @@
#endif
#include "nvim/api/vim.h"
-// Maximum number of commands from + or -c arguments.
-#define MAX_ARG_CMDS 10
-
// values for "window_layout"
#define WIN_HOR 1 // "-o" horizontally split windows
#define WIN_VER 2 // "-O" vertically split windows
#define WIN_TABS 3 // "-p" windows on tab pages
-// Struct for various parameters passed between main() and other functions.
-typedef struct {
- int argc;
- char **argv;
-
- char *use_vimrc; // vimrc from -u argument
-
- int n_commands; // no. of commands from + or -c
- char *commands[MAX_ARG_CMDS]; // commands from + or -c arg
- char_u cmds_tofree[MAX_ARG_CMDS]; // commands that need free()
- int n_pre_commands; // no. of commands from --cmd
- char *pre_commands[MAX_ARG_CMDS]; // commands from --cmd argument
-
- int edit_type; // type of editing to do
- char_u *tagname; // tag from -t argument
- char_u *use_ef; // 'errorfile' from -q argument
-
- bool input_isatty; // stdin is a terminal
- bool output_isatty; // stdout is a terminal
- bool err_isatty; // stderr is a terminal
- int no_swap_file; // "-n" argument used
- int use_debug_break_level;
- int window_count; // number of windows to use
- int window_layout; // 0, WIN_HOR, WIN_VER or WIN_TABS
-
- int diff_mode; // start with 'diff' set
-
- char *listen_addr; // --listen {address}
-} mparm_T;
-
// Values for edit_type.
#define EDIT_NONE 0 // no edit type yet
#define EDIT_FILE 1 // file name argument[s] given, use argument list
@@ -143,7 +115,6 @@ static const char *err_extra_cmd =
void event_init(void)
{
- log_init();
loop_init(&main_loop, NULL);
resize_events = multiqueue_new_child(main_loop.events);
@@ -184,7 +155,7 @@ bool event_teardown(void)
/// Performs early initialization.
///
/// Needed for unit tests. Must be called after `time_init()`.
-void early_init(void)
+void early_init(mparm_T *paramp)
{
env_init();
fs_init();
@@ -204,7 +175,7 @@ void early_init(void)
// Allocate the first window and buffer.
// Can't do anything without it, exit when it fails.
if (!win_alloc_first()) {
- mch_exit(0);
+ os_exit(0);
}
init_yank(); // init yank buffers
@@ -218,7 +189,8 @@ void early_init(void)
// msg_outtrans_len_attr().
// First find out the home directory, needed to expand "~" in options.
init_homedir(); // find real value of $HOME
- set_init_1();
+ set_init_1(paramp != NULL ? paramp->clean : false);
+ log_init();
TIME_MSG("inits 1");
set_lang_var(); // set v:lang and v:ctype
@@ -260,9 +232,19 @@ int main(int argc, char **argv)
init_startuptime(&params);
+ // Need to find "--clean" before actually parsing arguments.
+ for (int i = 1; i < params.argc; i++) {
+ if (STRICMP(params.argv[i], "--clean") == 0) {
+ params.clean = true;
+ break;
+ }
+ }
+
event_init();
- early_init();
+ early_init(&params);
+
+ set_argv_var(argv, argc); // set v:argv
// Check if we have an interactive window.
check_and_set_isatty(&params);
@@ -284,13 +266,19 @@ int main(int argc, char **argv)
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)
params.window_count = 0; /* open up to 3 windows */
- /* Don't redraw until much later. */
- ++RedrawingDisabled;
+ // Don't redraw until much later.
+ RedrawingDisabled++;
setbuf(stdout, NULL);
@@ -325,6 +313,26 @@ int main(int argc, char **argv)
input_start(STDIN_FILENO);
}
+ // Wait for UIs to set up Nvim or show early messages
+ // and prompts (--cmd, swapfile dialog, …).
+ bool use_remote_ui = (embedded_mode && !headless_mode);
+ bool use_builtin_ui = (!headless_mode && !embedded_mode && !silent_mode);
+ if (use_remote_ui || use_builtin_ui) {
+ TIME_MSG("waiting for UI");
+ if (use_remote_ui) {
+ remote_ui_wait_for_attach();
+ } else {
+ ui_builtin_start();
+ }
+ TIME_MSG("done waiting for UI");
+
+ // prepare screen now, so external UIs can display messages
+ starting = NO_BUFFERS;
+ screenclear();
+ TIME_MSG("initialized screen early for UI");
+ }
+
+
// open terminals when opening files that start with term://
#define PROTO "term://"
do_cmdline_cmd("augroup nvim_terminal");
@@ -335,8 +343,8 @@ int main(int argc, char **argv)
"matchstr(expand(\"<amatch>\"), "
"'\\c\\m" PROTO "\\%(.\\{-}//\\%(\\d\\+:\\)\\?\\)\\?\\zs.*'), "
// capture the working directory
- "{'cwd': get(matchlist(expand(\"<amatch>\"), "
- "'\\c\\m" PROTO "\\(.\\{-}\\)//'), 1, '')})"
+ "{'cwd': expand(get(matchlist(expand(\"<amatch>\"), "
+ "'\\c\\m" PROTO "\\(.\\{-}\\)//'), 1, ''))})"
"|endif");
do_cmdline_cmd("augroup END");
#undef PROTO
@@ -347,25 +355,6 @@ int main(int argc, char **argv)
p_lpl = false;
}
- // Wait for UIs to set up Nvim or show early messages
- // and prompts (--cmd, swapfile dialog, …).
- bool use_remote_ui = (embedded_mode && !headless_mode);
- bool use_builtin_ui = (!headless_mode && !embedded_mode && !silent_mode);
- if (use_remote_ui || use_builtin_ui) {
- TIME_MSG("waiting for UI");
- if (use_remote_ui) {
- remote_ui_wait_for_attach();
- } else {
- ui_builtin_start();
- }
- TIME_MSG("done waiting for UI");
-
- // prepare screen now, so external UIs can display messages
- starting = NO_BUFFERS;
- screenclear();
- TIME_MSG("initialized screen early for UI");
- }
-
// Execute --cmd arguments.
exe_pre_commands(&params);
@@ -380,23 +369,17 @@ int main(int argc, char **argv)
syn_maybe_on();
}
- /*
- * Read all the plugin files.
- * Only when compiled with +eval, since most plugins need it.
- */
+ // Read all the plugin files.
load_plugins();
// Decide about window layout for diff mode after reading vimrc.
set_window_layout(&params);
- /*
- * Recovery mode without a file name: List swap files.
- * This uses the 'dir' option, therefore it must be after the
- * initializations.
- */
+ // Recovery mode without a file name: List swap files.
+ // Uses the 'dir' option, therefore it must be after the initializations.
if (recoverymode && fname == NULL) {
- recover_names(NULL, TRUE, 0, NULL);
- mch_exit(0);
+ recover_names(NULL, true, 0, NULL);
+ os_exit(0);
}
// Set some option defaults after reading vimrc files.
@@ -427,17 +410,15 @@ int main(int argc, char **argv)
set_vim_var_list(VV_OLDFILES, tv_list_alloc(0));
}
- /*
- * "-q errorfile": Load the error file now.
- * If the error file can't be read, exit before doing anything else.
- */
+ // "-q errorfile": Load the error file now.
+ // If the error file can't be read, exit before doing anything else.
handle_quickfix(&params);
- /*
- * Start putting things on the screen.
- * Scroll screen down before drawing over it
- * Clear screen now, so file message will not be cleared.
- */
+ //
+ // Start putting things on the screen.
+ // Scroll screen down before drawing over it
+ // Clear screen now, so file message will not be cleared.
+ //
starting = NO_BUFFERS;
no_wait_return = false;
if (!exmode_active) {
@@ -469,27 +450,26 @@ int main(int argc, char **argv)
no_wait_return = true;
- /*
- * Create the requested number of windows and edit buffers in them.
- * Also does recovery if "recoverymode" set.
- */
+ //
+ // Create the requested number of windows and edit buffers in them.
+ // Also does recovery if "recoverymode" set.
+ //
create_windows(&params);
TIME_MSG("opening buffers");
- /* clear v:swapcommand */
+ // Clear v:swapcommand
set_vim_var_string(VV_SWAPCOMMAND, NULL, -1);
- /* Ex starts at last line of the file */
- if (exmode_active)
+ // Ex starts at last line of the file.
+ if (exmode_active) {
curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count;
+ }
apply_autocmds(EVENT_BUFENTER, NULL, NULL, FALSE, curbuf);
TIME_MSG("BufEnter autocommands");
setpcmark();
- /*
- * When started with "-q errorfile" jump to first error now.
- */
+ // When started with "-q errorfile" jump to first error now.
if (params.edit_type == EDIT_QF) {
qf_jump(NULL, 0, 0, FALSE);
TIME_MSG("jump to first error");
@@ -501,26 +481,23 @@ int main(int argc, char **argv)
xfree(cwd);
if (params.diff_mode) {
- /* set options in each window for "nvim -d". */
+ // set options in each window for "nvim -d".
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
diff_win_options(wp, TRUE);
}
}
- /*
- * Shorten any of the filenames, but only when absolute.
- */
- shorten_fnames(FALSE);
+ // Shorten any of the filenames, but only when absolute.
+ shorten_fnames(false);
- /*
- * Need to jump to the tag before executing the '-c command'.
- * Makes "vim -c '/return' -t main" work.
- */
+ // Need to jump to the tag before executing the '-c command'.
+ // Makes "vim -c '/return' -t main" work.
handle_tag(params.tagname);
- /* Execute any "+", "-c" and "-S" arguments. */
- if (params.n_commands > 0)
+ // Execute any "+", "-c" and "-S" arguments.
+ if (params.n_commands > 0) {
exe_commands(&params);
+ }
starting = 0;
@@ -531,9 +508,10 @@ int main(int argc, char **argv)
// 'autochdir' has been postponed.
do_autochdir();
- /* start in insert mode */
- if (p_im)
- need_start_insertmode = TRUE;
+ // start in insert mode
+ if (p_im) {
+ need_start_insertmode = true;
+ }
set_vim_var_nr(VV_VIM_DID_ENTER, 1L);
apply_autocmds(EVENT_VIMENTER, NULL, NULL, false, curbuf);
@@ -549,18 +527,19 @@ int main(int argc, char **argv)
// main loop.
set_reg_var(get_default_register_name());
- /* When a startup script or session file setup for diff'ing and
- * scrollbind, sync the scrollbind now. */
+ // When a startup script or session file setup for diff'ing and
+ // scrollbind, sync the scrollbind now.
if (curwin->w_p_diff && curwin->w_p_scb) {
update_topline();
check_scrollbind((linenr_T)0, 0L);
TIME_MSG("diff scrollbinding");
}
- /* If ":startinsert" command used, stuff a dummy command to be able to
- * call normal_cmd(), which will then start Insert mode. */
- if (restart_edit != 0)
+ // If ":startinsert" command used, stuff a dummy command to be able to
+ // call normal_cmd(), which will then start Insert mode.
+ if (restart_edit != 0) {
stuffcharReadbuff(K_NOP);
+ }
// WORKAROUND(mhi): #3023
if (cb_flags & CB_UNNAMEDMASK) {
@@ -570,9 +549,7 @@ int main(int argc, char **argv)
TIME_MSG("before starting main loop");
ILOG("starting main loop");
- /*
- * Call the main command loop. This never returns.
- */
+ // Main loop: never returns.
normal_enter(false, false);
#if defined(WIN32) && !defined(MAKE_LIB)
@@ -581,6 +558,31 @@ int main(int argc, char **argv)
return 0;
}
+void os_exit(int r)
+ FUNC_ATTR_NORETURN
+{
+ exiting = true;
+
+ ui_flush();
+ ui_call_stop();
+ ml_close_all(true); // remove all memfiles
+
+ if (!event_teardown() && r == 0) {
+ r = 1; // Exit with error if main_loop did not teardown gracefully.
+ }
+ if (input_global_fd() >= 0) {
+ stream_set_blocking(input_global_fd(), true); // normalize stream (#2598)
+ }
+
+ ILOG("Nvim exit: %d", r);
+
+#ifdef EXITFREE
+ free_all_mem();
+#endif
+
+ exit(r);
+}
+
/// Exit properly
void getout(int exitval)
FUNC_ATTR_NORETURN
@@ -601,7 +603,7 @@ void getout(int exitval)
/* Optionally print hashtable efficiency. */
hash_debug_results();
- if (get_vim_var_nr(VV_DYING) <= 1) {
+ if (v_dying <= 1) {
const tabpage_T *next_tp;
// Trigger BufWinLeave for all windows, but only once per buffer.
@@ -650,8 +652,9 @@ void getout(int exitval)
shada_write_file(NULL, false);
}
- if (get_vim_var_nr(VV_DYING) <= 1)
- apply_autocmds(EVENT_VIMLEAVE, NULL, NULL, FALSE, curbuf);
+ if (v_dying <= 1) {
+ apply_autocmds(EVENT_VIMLEAVE, NULL, NULL, false, curbuf);
+ }
profile_dump();
@@ -675,7 +678,7 @@ void getout(int exitval)
garbage_collect(false);
}
- mch_exit(exitval);
+ os_exit(exitval);
}
/// Gets the integer value of a numeric command line argument if given,
@@ -795,10 +798,10 @@ static void command_line_scan(mparm_T *parmp)
// "--cmd <cmd>" execute cmd before vimrc
if (STRICMP(argv[0] + argv_idx, "help") == 0) {
usage();
- mch_exit(0);
+ os_exit(0);
} else if (STRICMP(argv[0] + argv_idx, "version") == 0) {
version();
- mch_exit(0);
+ os_exit(0);
} else if (STRICMP(argv[0] + argv_idx, "api-info") == 0) {
FileDescriptor fp;
const int fof_ret = file_open_fd(&fp, STDOUT_FILENO,
@@ -821,7 +824,7 @@ static void command_line_scan(mparm_T *parmp)
if (ff_ret < 0) {
msgpack_file_write_error(ff_ret);
}
- mch_exit(0);
+ os_exit(0);
} else if (STRICMP(argv[0] + argv_idx, "headless") == 0) {
headless_mode = true;
} else if (STRICMP(argv[0] + argv_idx, "embed") == 0) {
@@ -841,6 +844,7 @@ static void command_line_scan(mparm_T *parmp)
argv_idx += 11;
} else if (STRNICMP(argv[0] + argv_idx, "clean", 5) == 0) {
parmp->use_vimrc = "NONE";
+ parmp->clean = true;
set_option_value("shadafile", 0L, "NONE", 0);
} else {
if (argv[0][argv_idx])
@@ -887,7 +891,7 @@ static void command_line_scan(mparm_T *parmp)
case '?': // "-?" give help message (for MS-Windows)
case 'h': { // "-h" give help message
usage();
- mch_exit(0);
+ os_exit(0);
}
case 'H': { // "-H" start in Hebrew mode: rl + hkmap set.
p_hkmap = true;
@@ -983,7 +987,7 @@ static void command_line_scan(mparm_T *parmp)
}
case 'v': {
version();
- mch_exit(0);
+ os_exit(0);
}
case 'V': { // "-V{N}" Verbose level
// default is 10: a little bit verbose
@@ -1111,20 +1115,14 @@ scripterror:
_("Attempt to open script file again: \"%s %s\"\n"),
argv[-1], argv[0]);
mch_errmsg((const char *)IObuff);
- mch_exit(2);
+ os_exit(2);
}
int error;
if (strequal(argv[0], "-")) {
const int stdin_dup_fd = os_dup(STDIN_FILENO);
#ifdef WIN32
// Replace the original stdin with the console input handle.
- close(STDIN_FILENO);
- const HANDLE conin_handle =
- CreateFile("CONIN$", GENERIC_READ | GENERIC_WRITE,
- FILE_SHARE_READ, (LPSECURITY_ATTRIBUTES)NULL,
- OPEN_EXISTING, 0, (HANDLE)NULL);
- const int conin_fd = _open_osfhandle(conin_handle, _O_RDONLY);
- assert(conin_fd == STDIN_FILENO);
+ os_replace_stdin_to_conin();
#endif
FileDescriptor *const stdin_dup = file_open_fd_new(
&error, stdin_dup_fd, kFileReadOnly|kFileNonBlocking);
@@ -1136,7 +1134,7 @@ scripterror:
_("Cannot open for reading: \"%s\": %s\n"),
argv[0], os_strerror(error));
mch_errmsg((const char *)IObuff);
- mch_exit(2);
+ os_exit(2);
}
save_typebuf();
break;
@@ -1174,7 +1172,7 @@ scripterror:
mch_errmsg(_("Cannot open for script output: \""));
mch_errmsg(argv[0]);
mch_errmsg("\"\n");
- mch_exit(2);
+ os_exit(2);
}
break;
}
@@ -1261,9 +1259,8 @@ static void init_params(mparm_T *paramp, int argc, char **argv)
/// Initialize global startuptime file if "--startuptime" passed as an argument.
static void init_startuptime(mparm_T *paramp)
{
- for (int i = 1; i < paramp->argc; i++) {
- if (STRICMP(paramp->argv[i], "--startuptime") == 0
- && i + 1 < paramp->argc) {
+ 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_start("--- NVIM STARTING ---");
break;
@@ -1381,7 +1378,7 @@ static void handle_quickfix(mparm_T *paramp)
vim_snprintf((char *)IObuff, IOSIZE, "cfile %s", p_ef);
if (qf_init(NULL, p_ef, p_efm, true, IObuff, p_menc) < 0) {
msg_putchar('\n');
- mch_exit(3);
+ os_exit(3);
}
TIME_MSG("reading errorfile");
}
@@ -1460,12 +1457,13 @@ static void create_windows(mparm_T *parmp)
} else
parmp->window_count = 1;
- if (recoverymode) { /* do recover */
- msg_scroll = TRUE; /* scroll message up */
- ml_recover();
- if (curbuf->b_ml.ml_mfp == NULL) /* failed */
+ if (recoverymode) { // do recover
+ msg_scroll = true; // scroll message up
+ ml_recover(true);
+ if (curbuf->b_ml.ml_mfp == NULL) { // failed
getout(1);
- do_modelines(0); /* do modelines */
+ }
+ do_modelines(0); // do modelines
} else {
// Open a buffer for windows that don't have one yet.
// Commands in the vimrc might have loaded a file or split the window.
@@ -1513,7 +1511,7 @@ static void create_windows(mparm_T *parmp)
/* We can't close the window, it would disturb what
* happens next. Clear the file name and set the arg
* index to -1 to delete it later. */
- setfname(curbuf, NULL, NULL, FALSE);
+ setfname(curbuf, NULL, NULL, false);
curwin->w_arg_idx = -1;
swap_exists_action = SEA_NONE;
} else
@@ -1778,7 +1776,8 @@ static bool do_user_initialization(void)
if (do_source(user_vimrc, true, DOSO_VIMRC) != FAIL) {
do_exrc = p_exrc;
if (do_exrc) {
- do_exrc = (path_full_compare((char_u *)VIMRC_FILE, user_vimrc, false)
+ do_exrc = (path_full_compare((char_u *)VIMRC_FILE, user_vimrc,
+ false, true)
!= kEqualFiles);
}
xfree(user_vimrc);
@@ -1805,7 +1804,7 @@ static bool do_user_initialization(void)
do_exrc = p_exrc;
if (do_exrc) {
do_exrc = (path_full_compare((char_u *)VIMRC_FILE, (char_u *)vimrc,
- false) != kEqualFiles);
+ false, true) != kEqualFiles);
}
xfree(vimrc);
xfree(config_dirs);
@@ -1942,7 +1941,7 @@ static void mainerr(const char *errstr, const char *str)
mch_errmsg(prgname);
mch_errmsg(" -h\"\n");
- mch_exit(1);
+ os_exit(1);
}
/// Prints version information for "nvim -v" or "nvim --version".
diff --git a/src/nvim/main.h b/src/nvim/main.h
index 86d25fe657..61252f2bce 100644
--- a/src/nvim/main.h
+++ b/src/nvim/main.h
@@ -4,8 +4,42 @@
#include "nvim/normal.h"
#include "nvim/event/loop.h"
+// Maximum number of commands from + or -c arguments.
+#define MAX_ARG_CMDS 10
+
extern Loop main_loop;
+// Struct for various parameters passed between main() and other functions.
+typedef struct {
+ int argc;
+ char **argv;
+
+ char *use_vimrc; // vimrc from -u argument
+ bool clean; // --clean argument
+
+ int n_commands; // no. of commands from + or -c
+ char *commands[MAX_ARG_CMDS]; // commands from + or -c arg
+ char_u cmds_tofree[MAX_ARG_CMDS]; // commands that need free()
+ int n_pre_commands; // no. of commands from --cmd
+ char *pre_commands[MAX_ARG_CMDS]; // commands from --cmd argument
+
+ int edit_type; // type of editing to do
+ char_u *tagname; // tag from -t argument
+ char_u *use_ef; // 'errorfile' from -q argument
+
+ bool input_isatty; // stdin is a terminal
+ bool output_isatty; // stdout is a terminal
+ bool err_isatty; // stderr is a terminal
+ int no_swap_file; // "-n" argument used
+ int use_debug_break_level;
+ int window_count; // number of windows to use
+ int window_layout; // 0, WIN_HOR, WIN_VER or WIN_TABS
+
+ int diff_mode; // start with 'diff' set
+
+ char *listen_addr; // --listen {address}
+} mparm_T;
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "main.h.generated.h"
#endif
diff --git a/src/nvim/map.c b/src/nvim/map.c
index cdade5ee71..cba39f24b3 100644
--- a/src/nvim/map.c
+++ b/src/nvim/map.c
@@ -44,7 +44,8 @@
#define INITIALIZER(T, U) T##_##U##_initializer
#define INITIALIZER_DECLARE(T, U, ...) const U INITIALIZER(T, U) = __VA_ARGS__
-#define DEFAULT_INITIALIZER {0}
+#define DEFAULT_INITIALIZER { 0 }
+#define SSIZE_INITIALIZER { -1 }
#define MAP_IMPL(T, U, ...) \
INITIALIZER_DECLARE(T, U, __VA_ARGS__); \
@@ -178,10 +179,16 @@ MAP_IMPL(int, int, DEFAULT_INITIALIZER)
MAP_IMPL(cstr_t, ptr_t, DEFAULT_INITIALIZER)
MAP_IMPL(ptr_t, ptr_t, DEFAULT_INITIALIZER)
MAP_IMPL(uint64_t, ptr_t, DEFAULT_INITIALIZER)
+MAP_IMPL(uint64_t, ssize_t, SSIZE_INITIALIZER)
+MAP_IMPL(uint64_t, uint64_t, DEFAULT_INITIALIZER)
+#define EXTMARK_NS_INITIALIZER { 0, 0 }
+MAP_IMPL(uint64_t, ExtmarkNs, EXTMARK_NS_INITIALIZER)
+#define KVEC_INITIALIZER { .size = 0, .capacity = 0, .items = NULL }
+#define EXTMARK_ITEM_INITIALIZER { 0, 0, 0, KVEC_INITIALIZER }
+MAP_IMPL(uint64_t, ExtmarkItem, EXTMARK_ITEM_INITIALIZER)
MAP_IMPL(handle_T, ptr_t, DEFAULT_INITIALIZER)
#define MSGPACK_HANDLER_INITIALIZER { .fn = NULL, .fast = false }
MAP_IMPL(String, MsgpackRpcRequestHandler, MSGPACK_HANDLER_INITIALIZER)
-#define KVEC_INITIALIZER { .size = 0, .capacity = 0, .items = NULL }
MAP_IMPL(HlEntry, int, DEFAULT_INITIALIZER)
MAP_IMPL(String, handle_T, 0)
diff --git a/src/nvim/map.h b/src/nvim/map.h
index 75ab64cca4..0ad7865bf0 100644
--- a/src/nvim/map.h
+++ b/src/nvim/map.h
@@ -4,9 +4,9 @@
#include <stdbool.h>
#include "nvim/map_defs.h"
+#include "nvim/extmark_defs.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/dispatch.h"
-#include "nvim/bufhl_defs.h"
#include "nvim/highlight_defs.h"
#if defined(__NetBSD__)
@@ -38,6 +38,18 @@ MAP_DECLS(int, int)
MAP_DECLS(cstr_t, ptr_t)
MAP_DECLS(ptr_t, ptr_t)
MAP_DECLS(uint64_t, ptr_t)
+MAP_DECLS(uint64_t, ssize_t)
+MAP_DECLS(uint64_t, uint64_t)
+
+// NB: this is the only way to define a struct both containing and contained
+// in a map...
+typedef struct ExtmarkNs { // For namespacing extmarks
+ Map(uint64_t, uint64_t) *map; // For fast lookup
+ uint64_t free_id; // For automatically assigning id's
+} ExtmarkNs;
+
+MAP_DECLS(uint64_t, ExtmarkNs)
+MAP_DECLS(uint64_t, ExtmarkItem)
MAP_DECLS(handle_T, ptr_t)
MAP_DECLS(String, MsgpackRpcRequestHandler)
MAP_DECLS(HlEntry, int)
@@ -53,6 +65,8 @@ MAP_DECLS(String, handle_T)
#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_new(T) map_new(T, ptr_t)
#define pmap_free(T) map_free(T, ptr_t)
#define pmap_get(T) map_get(T, ptr_t)
diff --git a/src/nvim/mark.c b/src/nvim/mark.c
index e103d3cb55..1ecfae57ed 100644
--- a/src/nvim/mark.c
+++ b/src/nvim/mark.c
@@ -20,6 +20,7 @@
#include "nvim/ex_cmds.h"
#include "nvim/fileio.h"
#include "nvim/fold.h"
+#include "nvim/extmark.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
@@ -178,6 +179,16 @@ void setpcmark(void)
curwin->w_pcmark.lnum = 1;
}
+ if (jop_flags & JOP_STACK) {
+ // jumpoptions=stack: if we're somewhere in the middle of the jumplist
+ // discard everything after the current index.
+ if (curwin->w_jumplistidx < curwin->w_jumplistlen - 1) {
+ // Discard the rest of the jumplist by cutting the length down to
+ // contain nothing beyond the current index.
+ curwin->w_jumplistlen = curwin->w_jumplistidx + 1;
+ }
+ }
+
/* If jumplist is full: remove oldest entry */
if (++curwin->w_jumplistlen > JUMPLISTSIZE) {
curwin->w_jumplistlen = JUMPLISTSIZE;
@@ -296,17 +307,17 @@ pos_T *movechangelist(int count)
* - NULL if there is no mark called 'c'.
* - -1 if mark is in other file and jumped there (only if changefile is TRUE)
*/
-pos_T *getmark_buf(buf_T *buf, int c, int changefile)
+pos_T *getmark_buf(buf_T *buf, int c, bool changefile)
{
return getmark_buf_fnum(buf, c, changefile, NULL);
}
-pos_T *getmark(int c, int changefile)
+pos_T *getmark(int c, bool changefile)
{
return getmark_buf_fnum(curbuf, c, changefile, NULL);
}
-pos_T *getmark_buf_fnum(buf_T *buf, int c, int changefile, int *fnum)
+pos_T *getmark_buf_fnum(buf_T *buf, int c, bool changefile, int *fnum)
{
pos_T *posp;
pos_T *startp, *endp;
@@ -616,11 +627,12 @@ static char_u *mark_line(pos_T *mp, int lead_len)
/*
* print the marks
*/
-void do_marks(exarg_T *eap)
+void ex_marks(exarg_T *eap)
{
char_u *arg = eap->arg;
int i;
char_u *name;
+ pos_T *posp, *startp, *endp;
if (arg != NULL && *arg == NUL)
arg = NULL;
@@ -646,8 +658,18 @@ void do_marks(exarg_T *eap)
show_one_mark(']', arg, &curbuf->b_op_end, NULL, true);
show_one_mark('^', arg, &curbuf->b_last_insert.mark, NULL, true);
show_one_mark('.', arg, &curbuf->b_last_change.mark, NULL, true);
- show_one_mark('<', arg, &curbuf->b_visual.vi_start, NULL, true);
- show_one_mark('>', arg, &curbuf->b_visual.vi_end, NULL, true);
+
+ // Show the marks as where they will jump to.
+ startp = &curbuf->b_visual.vi_start;
+ endp = &curbuf->b_visual.vi_end;
+ if ((lt(*startp, *endp) || endp->lnum == 0) && startp->lnum != 0) {
+ posp = startp;
+ } else {
+ posp = endp;
+ }
+ show_one_mark('<', arg, posp, NULL, true);
+ show_one_mark('>', arg, posp == startp ? endp : startp, NULL, true);
+
show_one_mark(-1, arg, NULL, NULL, false);
}
@@ -905,9 +927,9 @@ void mark_adjust(linenr_T line1,
linenr_T line2,
long amount,
long amount_after,
- bool end_temp)
+ ExtmarkOp op)
{
- mark_adjust_internal(line1, line2, amount, amount_after, true, end_temp);
+ mark_adjust_internal(line1, line2, amount, amount_after, true, op);
}
// mark_adjust_nofold() does the same as mark_adjust() but without adjusting
@@ -916,14 +938,16 @@ void mark_adjust(linenr_T line1,
// calling foldMarkAdjust() with arguments line1, line2, amount, amount_after,
// for an example of why this may be necessary, see do_move().
void mark_adjust_nofold(linenr_T line1, linenr_T line2, long amount,
- long amount_after, bool end_temp)
+ long amount_after,
+ ExtmarkOp op)
{
- mark_adjust_internal(line1, line2, amount, amount_after, false, end_temp);
+ mark_adjust_internal(line1, line2, amount, amount_after, false, op);
}
static void mark_adjust_internal(linenr_T line1, linenr_T line2,
long amount, long amount_after,
- bool adjust_folds, bool end_temp)
+ bool adjust_folds,
+ ExtmarkOp op)
{
int i;
int fnum = curbuf->b_fnum;
@@ -978,7 +1002,9 @@ static void mark_adjust_internal(linenr_T line1, linenr_T line2,
}
sign_mark_adjust(line1, line2, amount, amount_after);
- bufhl_mark_adjust(curbuf, line1, line2, amount, amount_after, end_temp);
+ if (op != kExtmarkNOOP) {
+ extmark_adjust(curbuf, line1, line2, amount, amount_after, op);
+ }
}
/* previous context mark */
@@ -1191,7 +1217,20 @@ void cleanup_jumplist(win_T *wp, bool checktail)
break;
}
}
- if (i >= wp->w_jumplistlen) { // no duplicate
+
+ bool mustfree;
+ if (i >= wp->w_jumplistlen) { // not duplicate
+ mustfree = false;
+ } else if (i > from + 1) { // non-adjacent duplicate
+ // jumpoptions=stack: remove duplicates only when adjacent.
+ mustfree = !(jop_flags & JOP_STACK);
+ } else { // adjacent duplicate
+ mustfree = true;
+ }
+
+ if (mustfree) {
+ xfree(wp->w_jumplist[from].fname);
+ } else {
if (to != from) {
// Not using wp->w_jumplist[to++] = wp->w_jumplist[from] because
// this way valgrind complains about overlapping source and destination
@@ -1199,8 +1238,6 @@ void cleanup_jumplist(win_T *wp, bool checktail)
wp->w_jumplist[to] = wp->w_jumplist[from];
}
to++;
- } else {
- xfree(wp->w_jumplist[from].fname);
}
}
if (wp->w_jumplistidx == wp->w_jumplistlen) {
diff --git a/src/nvim/mark.h b/src/nvim/mark.h
index ed4e47907b..b3d9b5d95a 100644
--- a/src/nvim/mark.h
+++ b/src/nvim/mark.h
@@ -6,6 +6,7 @@
#include "nvim/buffer_defs.h"
#include "nvim/func_attr.h"
#include "nvim/mark_defs.h"
+#include "nvim/extmark_defs.h"
#include "nvim/memory.h"
#include "nvim/pos.h"
#include "nvim/os/time.h"
diff --git a/src/nvim/marktree.c b/src/nvim/marktree.c
new file mode 100644
index 0000000000..6dd452b5af
--- /dev/null
+++ b/src/nvim/marktree.c
@@ -0,0 +1,1197 @@
+// 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
+// marker tree data structure of the Atom editor, regarding efficient updates
+// to text changes.
+//
+// Marks are inserted using marktree_put. Text changes are processed using
+// marktree_splice. All read and delete operations use the iterator.
+// use marktree_itr_get to put an iterator at a given position or
+// marktree_lookup to lookup a mark by its id (iterator optional in this case).
+// 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):
+//
+// Copyright 1997-1999, 2001, John-Mark Gurney.
+// 2008-2009, Attractive Chaos <attractor@live.co.uk>
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+//
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+// SUCH DAMAGE.
+//
+// Changes done by by the neovim project follow the Apache v2 license available
+// at the repo root.
+
+#include <assert.h>
+
+#include "nvim/marktree.h"
+#include "nvim/lib/kvec.h"
+#include "nvim/garray.h"
+
+#define T MT_BRANCH_FACTOR
+#define ILEN (sizeof(mtnode_t)+(2 * T) * sizeof(void *))
+#define key_t SKRAPET
+
+#define RIGHT_GRAVITY (((uint64_t)1) << 63)
+#define ANTIGRAVITY(id) ((id)&(RIGHT_GRAVITY-1))
+#define IS_RIGHT(id) ((id)&RIGHT_GRAVITY)
+
+#define PAIRED MARKTREE_PAIRED_FLAG
+#define END_FLAG MARKTREE_END_FLAG
+#define ID_INCR (((uint64_t)1) << 2)
+
+#define PROP_MASK (RIGHT_GRAVITY|PAIRED|END_FLAG)
+
+#define rawkey(itr) (itr->node->key[itr->i])
+
+static bool pos_leq(mtpos_t a, mtpos_t b)
+{
+ return a.row < b.row || (a.row == b.row && a.col <= b.col);
+}
+
+static void relative(mtpos_t base, mtpos_t *val)
+{
+ assert(pos_leq(base, *val));
+ if (val->row == base.row) {
+ val->row = 0;
+ val->col -= base.col;
+ } else {
+ val->row -= base.row;
+ }
+}
+
+static void unrelative(mtpos_t base, mtpos_t *val)
+{
+ if (val->row == 0) {
+ val->row = base.row;
+ val->col += base.col;
+ } else {
+ val->row += base.row;
+ }
+}
+
+static void compose(mtpos_t *base, mtpos_t val)
+{
+ if (val.row == 0) {
+ base->col += val.col;
+ } else {
+ base->row += val.row;
+ base->col = val.col;
+ }
+}
+
+#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)
+{
+ int cmp = mt_generic_cmp(a.pos.row, b.pos.row);
+ if (cmp != 0) {
+ return cmp;
+ }
+ cmp = mt_generic_cmp(a.pos.col, b.pos.col);
+ 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.id, b.id);
+}
+
+static inline int marktree_getp_aux(const mtnode_t *x, mtkey_t k, int *r)
+{
+ int tr, *rr, begin = 0, end = x->n;
+ if (x->n == 0) {
+ return -1;
+ }
+ rr = r? r : &tr;
+ while (begin < end) {
+ int mid = (begin + end) >> 1;
+ if (key_cmp(x->key[mid], k) < 0) {
+ begin = mid + 1;
+ } else {
+ end = mid;
+ }
+ }
+ if (begin == x->n) { *rr = 1; return x->n - 1; }
+ if ((*rr = key_cmp(k, x->key[begin])) < 0) {
+ begin--;
+ }
+ return begin;
+}
+
+static inline void refkey(MarkTree *b, mtnode_t *x, int i)
+{
+ pmap_put(uint64_t)(b->id2node, ANTIGRAVITY(x->key[i].id), x);
+}
+
+// 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)
+{
+ mtnode_t *y = x->ptr[i];
+ mtnode_t *z;
+ z = (mtnode_t *)xcalloc(1, y->level ? ILEN : sizeof(mtnode_t));
+ b->n_nodes++;
+ z->level = y->level;
+ z->n = T - 1;
+ memcpy(z->key, &y->key[T], sizeof(mtkey_t) * (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);
+ for (int j = 0; j < T; j++) {
+ z->ptr[j]->parent = z;
+ }
+ }
+ y->n = T - 1;
+ memmove(&x->ptr[i + 2], &x->ptr[i + 1],
+ sizeof(mtnode_t *) * (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));
+
+ // move key to internal layer:
+ x->key[i] = y->key[T - 1];
+ refkey(b, x, i);
+ x->n++;
+
+ for (int j = 0; j < T-1; j++) {
+ relative(x->key[i].pos, &z->key[j].pos);
+ }
+ if (i > 0) {
+ unrelative(x->key[i-1].pos, &x->key[i].pos);
+ }
+}
+
+// x must not be a full node (even if there might be internal space)
+static inline void marktree_putp_aux(MarkTree *b, mtnode_t *x, mtkey_t k)
+{
+ int i;
+ 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));
+ }
+ x->key[i + 1] = k;
+ refkey(b, x, i+1);
+ x->n++;
+ } else {
+ i = marktree_getp_aux(x, k, 0) + 1;
+ if (x->ptr[i]->n == 2 * T - 1) {
+ split_node(b, x, i);
+ if (key_cmp(k, x->key[i]) > 0) {
+ i++;
+ }
+ }
+ if (i > 0) {
+ relative(x->key[i-1].pos, &k.pos);
+ }
+ marktree_putp_aux(b, x->ptr[i], k);
+ }
+}
+
+uint64_t marktree_put(MarkTree *b, int row, int col, bool right_gravity)
+{
+ uint64_t id = (b->next_id+=ID_INCR);
+ uint64_t keyid = id;
+ if (right_gravity) {
+ // order all right gravity keys after the left ones, for effortless
+ // insertion (but not deletion!)
+ keyid |= RIGHT_GRAVITY;
+ }
+ marktree_put_key(b, row, col, keyid);
+ return id;
+}
+
+uint64_t marktree_put_pair(MarkTree *b,
+ int start_row, int start_col, bool start_right,
+ int end_row, int end_col, bool end_right)
+{
+ uint64_t id = (b->next_id+=ID_INCR)|PAIRED;
+ uint64_t start_id = id|(start_right?RIGHT_GRAVITY:0);
+ uint64_t end_id = id|END_FLAG|(end_right?RIGHT_GRAVITY:0);
+ marktree_put_key(b, start_row, start_col, start_id);
+ marktree_put_key(b, end_row, end_col, end_id);
+ return id;
+}
+
+void marktree_put_key(MarkTree *b, int row, int col, uint64_t id)
+{
+ mtkey_t k = { .pos = { .row = row, .col = col }, .id = id };
+
+ if (!b->root) {
+ b->root = (mtnode_t *)xcalloc(1, ILEN);
+ b->id2node = pmap_new(uint64_t)();
+ b->n_nodes++;
+ }
+ mtnode_t *r, *s;
+ b->n_keys++;
+ r = b->root;
+ if (r->n == 2 * T - 1) {
+ b->n_nodes++;
+ s = (mtnode_t *)xcalloc(1, ILEN);
+ b->root = s; s->level = r->level+1; s->n = 0;
+ s->ptr[0] = r;
+ r->parent = s;
+ split_node(b, s, 0);
+ r = s;
+ }
+ marktree_putp_aux(b, r, k);
+}
+
+/// INITIATING DELETION PROTOCOL:
+///
+/// 1. Construct a valid iterator to the node to delete (argument)
+/// 2. If an "internal" key. Iterate one step to the left or right,
+/// which gives an internal key "auxiliary key".
+/// 3. Now delete this internal key (intended or auxiliary).
+/// The leaf node X might become undersized.
+/// 4. If step two was done: now replace the key that _should_ be
+/// deleted with the auxiliary key. Adjust relative
+/// 5. Now "repair" the tree as needed. We always start at a leaf node X.
+/// - if the node is big enough, terminate
+/// - if we can steal from the left, steal
+/// - if we can steal from the right, steal
+/// - otherwise merge this node with a neighbour. This might make our
+/// parent undersized. So repeat 5 for the parent.
+/// 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.
+///
+/// @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)
+{
+ int adjustment = 0;
+
+ mtnode_t *cur = itr->node;
+ int curi = itr->i;
+ uint64_t id = cur->key[curi].id;
+ // fprintf(stderr, "\nDELET %lu\n", id);
+
+ if (itr->node->level) {
+ if (rev) {
+ abort();
+ } else {
+ // fprintf(stderr, "INTERNAL %d\n", cur->level);
+ // steal previous node
+ marktree_itr_prev(b, itr);
+ adjustment = -1;
+ }
+ }
+
+ // 3.
+ mtnode_t *x = itr->node;
+ assert(x->level == 0);
+ mtkey_t 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));
+ }
+ x->n--;
+
+ // 4.
+ if (adjustment) {
+ if (adjustment == 1) {
+ abort();
+ } else { // adjustment == -1
+ int ilvl = itr->lvl-1;
+ mtnode_t *lnode = x;
+ do {
+ mtnode_t *p = lnode->parent;
+ if (ilvl < 0) {
+ abort();
+ }
+ int i = itr->s[ilvl].i;
+ assert(p->ptr[i] == lnode);
+ if (i > 0) {
+ unrelative(p->key[i-1].pos, &intkey.pos);
+ }
+ lnode = p;
+ ilvl--;
+ } while (lnode != cur);
+
+ mtkey_t deleted = cur->key[curi];
+ cur->key[curi] = intkey;
+ refkey(b, cur, curi);
+ relative(intkey.pos, &deleted.pos);
+ mtnode_t *y = cur->ptr[curi+1];
+ if (deleted.pos.row || deleted.pos.col) {
+ while (y) {
+ for (int k = 0; k < y->n; k++) {
+ unrelative(deleted.pos, &y->key[k].pos);
+ }
+ y = y->level ? y->ptr[0] : NULL;
+ }
+ }
+ }
+ }
+
+ b->n_keys--;
+ pmap_del(uint64_t)(b->id2node, ANTIGRAVITY(id));
+
+ // 5.
+ bool itr_dirty = false;
+ int rlvl = itr->lvl-1;
+ int *lasti = &itr->i;
+ while (x != b->root) {
+ assert(rlvl >= 0);
+ mtnode_t *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 && 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);
+ break;
+ } else if (pi < p->n && p->ptr[pi+1]->n > T-1) {
+ // steal one key from right neighbour
+ pivot_left(b, 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->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
+ }
+ lasti = &itr->s[rlvl].i;
+ rlvl--;
+ x = p;
+ }
+
+ // 6.
+ if (b->root->n == 0) {
+ if (itr->lvl > 0) {
+ memmove(itr->s, itr->s+1, (size_t)(itr->lvl-1) * sizeof(*itr->s));
+ itr->lvl--;
+ }
+ if (b->root->level) {
+ mtnode_t *oldroot = b->root;
+ b->root = b->root->ptr[0];
+ b->root->parent = NULL;
+ xfree(oldroot);
+ } else {
+ // no items, nothing for iterator to point to
+ // not strictly needed, should handle delete right-most mark anyway
+ itr->node = NULL;
+ }
+ }
+
+ if (itr->node && itr_dirty) {
+ marktree_itr_fix_pos(b, itr);
+ }
+
+ // BONUS STEP: fix the iterator, so that it points to the key afterwards
+ // TODO(bfredl): with "rev" should point before
+ if (adjustment == 1) {
+ abort();
+ } else if (adjustment == -1) {
+ // tricky: we stand at the deleted space in the previous leaf node.
+ // But the inner key is now the previous key we stole, so we need
+ // to skip that one as well.
+ marktree_itr_next(b, itr);
+ marktree_itr_next(b, itr);
+ } else {
+ if (itr->node && itr->i >= itr->node->n) {
+ // we deleted the last key of a leaf node
+ // go to the inner key after that.
+ assert(itr->node->level == 0);
+ marktree_itr_next(b, itr);
+ }
+ }
+}
+
+static mtnode_t *merge_node(MarkTree *b, mtnode_t *p, int i)
+{
+ mtnode_t *x = p->ptr[i], *y = p->ptr[i+1];
+
+ x->key[x->n] = p->key[i];
+ refkey(b, x, x->n);
+ if (i > 0) {
+ 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));
+ 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;
+ }
+ }
+ x->n += y->n+1;
+ memmove(&p->key[i], &p->key[i + 1], (size_t)(p->n - i - 1) * sizeof(mtkey_t));
+ memmove(&p->ptr[i + 1], &p->ptr[i + 2],
+ (size_t)(p->n - i - 1) * sizeof(mtkey_t *));
+ p->n--;
+ xfree(y);
+ b->n_nodes--;
+ return x;
+}
+
+// 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)
+{
+ mtnode_t *x = p->ptr[i], *y = p->ptr[i+1];
+ memmove(&y->key[1], y->key, (size_t)y->n * sizeof(mtkey_t));
+ if (y->level) {
+ memmove(&y->ptr[1], y->ptr, (size_t)(y->n + 1) * sizeof(mtnode_t *));
+ }
+ y->key[0] = p->key[i];
+ refkey(b, y, 0);
+ p->key[i] = x->key[x->n - 1];
+ refkey(b, p, i);
+ if (x->level) {
+ y->ptr[0] = x->ptr[x->n];
+ y->ptr[0]->parent = y;
+ }
+ x->n--;
+ y->n++;
+ if (i > 0) {
+ unrelative(p->key[i-1].pos, &p->key[i].pos);
+ }
+ relative(p->key[i].pos, &y->key[0].pos);
+ for (int k = 1; k < y->n; k++) {
+ unrelative(y->key[0].pos, &y->key[k].pos);
+ }
+}
+
+static void pivot_left(MarkTree *b, mtnode_t *p, int i)
+{
+ mtnode_t *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.
+ for (int k = 1; k < y->n; k++) {
+ relative(y->key[0].pos, &y->key[k].pos);
+ }
+ unrelative(p->key[i].pos, &y->key[0].pos);
+ if (i > 0) {
+ relative(p->key[i-1].pos, &p->key[i].pos);
+ }
+
+ x->key[x->n] = p->key[i];
+ refkey(b, x, x->n);
+ p->key[i] = y->key[0];
+ refkey(b, p, i);
+ if (x->level) {
+ x->ptr[x->n+1] = y->ptr[0];
+ x->ptr[x->n+1]->parent = x;
+ }
+ memmove(y->key, &y->key[1], (size_t)(y->n-1) * sizeof(mtkey_t));
+ if (y->level) {
+ memmove(y->ptr, &y->ptr[1], (size_t)y->n * sizeof(mtnode_t *));
+ }
+ x->n++;
+ y->n--;
+}
+
+/// frees all mem, resets tree to valid empty state
+void marktree_clear(MarkTree *b)
+{
+ if (b->root) {
+ marktree_free_node(b->root);
+ b->root = NULL;
+ }
+ if (b->id2node) {
+ pmap_free(uint64_t)(b->id2node);
+ b->id2node = NULL;
+ }
+ b->n_keys = 0;
+ b->n_nodes = 0;
+}
+
+void marktree_free_node(mtnode_t *x)
+{
+ if (x->level) {
+ for (int i = 0; i < x->n+1; i++) {
+ marktree_free_node(x->ptr[i]);
+ }
+ }
+ xfree(x);
+}
+
+/// NB: caller must check not pair!
+uint64_t marktree_revise(MarkTree *b, MarkTreeIter *itr)
+{
+ uint64_t old_id = rawkey(itr).id;
+ pmap_del(uint64_t)(b->id2node, ANTIGRAVITY(old_id));
+ uint64_t new_id = (b->next_id += ID_INCR);
+ rawkey(itr).id = new_id + (RIGHT_GRAVITY&old_id);
+ refkey(b, itr->node, itr->i);
+ return new_id;
+}
+
+void marktree_move(MarkTree *b, MarkTreeIter *itr, int row, int col)
+{
+ uint64_t old_id = rawkey(itr).id;
+ // TODO(bfredl): optimize when moving a mark within a leaf without moving it
+ // across neighbours!
+ marktree_del_itr(b, itr, false);
+ marktree_put_key(b, row, col, old_id);
+ itr->node = NULL; // itr might become invalid by put
+}
+
+// itr functions
+
+// TODO(bfredl): static inline?
+bool marktree_itr_get(MarkTree *b, int row, int col, MarkTreeIter *itr)
+{
+ return marktree_itr_get_ext(b, (mtpos_t){ 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)
+{
+ mtkey_t k = { .pos = p, .id = gravity ? RIGHT_GRAVITY : 0 };
+ if (last && !gravity) {
+ k.id = UINT64_MAX;
+ }
+ if (b->n_keys == 0) {
+ itr->node = NULL;
+ return false;
+ }
+ itr->pos = (mtpos_t){ 0, 0 };
+ itr->node = b->root;
+ itr->lvl = 0;
+ if (oldbase) {
+ oldbase[itr->lvl] = itr->pos;
+ }
+ while (true) {
+ itr->i = marktree_getp_aux(itr->node, k, 0)+1;
+
+ if (itr->node->level == 0) {
+ break;
+ }
+
+ itr->s[itr->lvl].i = itr->i;
+ 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);
+ }
+ itr->node = itr->node->ptr[itr->i];
+ itr->lvl++;
+ if (oldbase) {
+ oldbase[itr->lvl] = itr->pos;
+ }
+ }
+
+ if (last) {
+ return marktree_itr_prev(b, itr);
+ } else if (itr->i >= itr->node->n) {
+ return marktree_itr_next(b, itr);
+ }
+ return true;
+}
+
+bool marktree_itr_first(MarkTree *b, MarkTreeIter *itr)
+{
+ itr->node = b->root;
+ if (b->n_keys == 0) {
+ return false;
+ }
+
+ itr->i = 0;
+ itr->lvl = 0;
+ itr->pos = (mtpos_t){ 0, 0 };
+ while (itr->node->level > 0) {
+ itr->s[itr->lvl].i = 0;
+ itr->s[itr->lvl].oldcol = 0;
+ itr->lvl++;
+ itr->node = itr->node->ptr[0];
+ }
+ return true;
+}
+
+// gives the first key that is greater or equal to p
+int marktree_itr_last(MarkTree *b, MarkTreeIter *itr)
+{
+ if (b->n_keys == 0) {
+ itr->node = NULL;
+ return false;
+ }
+ itr->pos = (mtpos_t){ 0, 0 };
+ itr->node = b->root;
+ itr->lvl = 0;
+ while (true) {
+ itr->i = itr->node->n;
+
+ if (itr->node->level == 0) {
+ break;
+ }
+
+ itr->s[itr->lvl].i = itr->i;
+ itr->s[itr->lvl].oldcol = itr->pos.col;
+
+ assert(itr->i > 0);
+ compose(&itr->pos, itr->node->key[itr->i-1].pos);
+
+ itr->node = itr->node->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);
+}
+
+static bool marktree_itr_next_skip(MarkTree *b, MarkTreeIter *itr, bool skip,
+ mtpos_t oldbase[])
+{
+ if (!itr->node) {
+ return false;
+ }
+ itr->i++;
+ if (itr->node->level == 0 || skip) {
+ if (itr->i < itr->node->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) {
+ 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.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) {
+ // 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);
+ }
+ 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;
+ itr->lvl++;
+ }
+ }
+ return true;
+}
+
+bool marktree_itr_prev(MarkTree *b, MarkTreeIter *itr)
+{
+ if (!itr->node) {
+ return false;
+ }
+ if (itr->node->level == 0) {
+ itr->i--;
+ if (itr->i >= 0) {
+ // 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 a non-internal key
+ while (itr->i < 0) {
+ itr->node = itr->node->parent;
+ if (itr->node == 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.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) {
+ // 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);
+ }
+ 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;
+ itr->lvl++;
+ }
+ itr->i--;
+ }
+ 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;
+}
+
+
+mtpos_t marktree_itr_pos(MarkTreeIter *itr)
+{
+ mtpos_t pos = rawkey(itr).pos;
+ unrelative(itr->pos, &pos);
+ return pos;
+}
+
+mtmark_t marktree_itr_current(MarkTreeIter *itr)
+{
+ if (itr->node) {
+ uint64_t keyid = rawkey(itr).id;
+ mtpos_t pos = marktree_itr_pos(itr);
+ mtmark_t mark = { .row = pos.row,
+ .col = pos.col,
+ .id = ANTIGRAVITY(keyid),
+ .right_gravity = keyid & RIGHT_GRAVITY };
+ return mark;
+ }
+ return (mtmark_t){ -1, -1, 0, false };
+}
+
+static void swap_id(uint64_t *id1, uint64_t *id2)
+{
+ uint64_t temp = *id1;
+ *id1 = *id2;
+ *id2 = temp;
+}
+
+bool marktree_splice(MarkTree *b,
+ int 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 = { (int)old_extent_line, old_extent_col };
+ mtpos_t new_extent = { (int)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 };
+
+ mtpos_t oldbase[MT_MAX_DEPTH];
+
+ marktree_itr_get_ext(b, start, itr, false, true, oldbase);
+ if (!itr->node) {
+ // den e FÄRDIG
+ return false;
+ }
+ mtpos_t delta = { new_extent.row - old_extent.row,
+ new_extent.col-old_extent.col };
+
+ if (may_delete) {
+ mtpos_t ipos = marktree_itr_pos(itr);
+ if (!pos_leq(old_extent, ipos)
+ || (old_extent.row == ipos.row && old_extent.col == ipos.col
+ && !IS_RIGHT(rawkey(itr).id))) {
+ marktree_itr_get_ext(b, old_extent, enditr, true, true, NULL);
+ assert(enditr->node);
+ // "assert" (itr <= enditr)
+ } else {
+ may_delete = false;
+ }
+ }
+
+ bool past_right = false;
+ bool moved = false;
+
+ // 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;
+ relative(itr->pos, &loc_start);
+
+ relative(oldbase[itr->lvl], &loc_old);
+
+continue_same_node:
+ // NB: strictly should be less than the right gravity of loc_old, but
+ // the iter comparison below will already break on that.
+ if (!pos_leq(rawkey(itr).pos, loc_old)) {
+ break;
+ }
+
+ if (IS_RIGHT(rawkey(itr).id)) {
+ while (rawkey(itr).id != rawkey(enditr).id
+ && IS_RIGHT(rawkey(enditr).id)) {
+ marktree_itr_prev(b, enditr);
+ }
+ if (!IS_RIGHT(rawkey(enditr).id)) {
+ swap_id(&rawkey(itr).id, &rawkey(enditr).id);
+ refkey(b, itr->node, itr->i);
+ refkey(b, enditr->node, enditr->i);
+ } else {
+ past_right = true; // NOLINT
+ break;
+ }
+ }
+
+ if (rawkey(itr).id == rawkey(enditr).id) {
+ // actually, will be past_right after this key
+ past_right = true;
+ }
+
+ moved = true;
+ if (itr->node->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);
+ } else {
+ rawkey(itr).pos = loc_start;
+ if (itr->i < itr->node->n-1) {
+ itr->i++;
+ if (!past_right) {
+ goto continue_same_node;
+ }
+ } else {
+ marktree_itr_next(b, itr);
+ }
+ }
+ }
+ while (itr->node) {
+ mtpos_t loc_new = new_extent;
+ relative(itr->pos, &loc_new);
+ mtpos_t limit = old_extent;
+
+ relative(oldbase[itr->lvl], &limit);
+
+past_continue_same_node:
+
+ if (pos_leq(limit, rawkey(itr).pos)) {
+ break;
+ }
+
+ mtpos_t oldpos = rawkey(itr).pos;
+ rawkey(itr).pos = loc_new;
+ moved = true;
+ if (itr->node->level) {
+ oldbase[itr->lvl+1] = oldpos;
+ unrelative(oldbase[itr->lvl], &oldbase[itr->lvl+1]);
+
+ marktree_itr_next_skip(b, itr, false, oldbase);
+ } else {
+ if (itr->i < itr->node->n-1) {
+ itr->i++;
+ goto past_continue_same_node;
+ } else {
+ marktree_itr_next(b, itr);
+ }
+ }
+ }
+ }
+
+
+ while (itr->node) {
+ unrelative(oldbase[itr->lvl], &rawkey(itr).pos);
+ int realrow = rawkey(itr).pos.row;
+ assert(realrow >= old_extent.row);
+ bool done = false;
+ if (realrow == old_extent.row) {
+ if (delta.col) {
+ rawkey(itr).pos.col += delta.col;
+ moved = true;
+ }
+ } else {
+ if (same_line) {
+ // optimization: column only adjustment can skip remaining rows
+ done = true;
+ }
+ }
+ if (delta.row) {
+ rawkey(itr).pos.row += delta.row;
+ moved = true;
+ }
+ relative(itr->pos, &rawkey(itr).pos);
+ if (done) {
+ break;
+ }
+ marktree_itr_next_skip(b, itr, true, NULL);
+ }
+ 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;
+ 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) {
+ mtpos_t pos = marktree_itr_pos(itr);
+ if (!pos_leq(pos, end) || (pos.row == end.row && pos.col == end.col
+ && rawkey(itr).id & RIGHT_GRAVITY)) {
+ break;
+ }
+ relative(start, &pos);
+ kv_push(saved, ((mtkey_t){ .pos = pos, .id = rawkey(itr).id }));
+ marktree_del_itr(b, itr, false);
+ }
+
+ marktree_splice(b, start.row, start.col, size.row, size.col, 0, 0);
+ mtpos_t 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);
+ unrelative(new, &item.pos);
+ marktree_put_key(b, item.pos.row, item.pos.col, item.id);
+ }
+ kv_destroy(saved);
+}
+
+/// @param itr OPTIONAL. set itr to pos.
+mtpos_t marktree_lookup(MarkTree *b, uint64_t id, MarkTreeIter *itr)
+{
+ mtnode_t *n = pmap_get(uint64_t)(b->id2node, id);
+ if (n == NULL) {
+ if (itr) {
+ itr->node = NULL;
+ }
+ return (mtpos_t){ -1, -1 };
+ }
+ int i = 0;
+ for (i = 0; i < n->n; i++) {
+ if (ANTIGRAVITY(n->key[i].id) == id) {
+ goto found;
+ }
+ }
+ abort();
+found: {}
+ mtpos_t pos = n->key[i].pos;
+ if (itr) {
+ itr->i = i;
+ itr->node = 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:
+ if (itr) {
+ itr->s[b->root->level-p->level].i = i;
+ }
+ if (i > 0) {
+ unrelative(p->key[i-1].pos, &pos);
+ }
+ n = p;
+ }
+ if (itr) {
+ marktree_itr_fix_pos(b, itr);
+ }
+ return pos;
+}
+
+static void marktree_itr_fix_pos(MarkTree *b, MarkTreeIter *itr)
+{
+ itr->pos = (mtpos_t){ 0, 0 };
+ mtnode_t *x = b->root;
+ for (int lvl = 0; lvl < itr->lvl; lvl++) {
+ itr->s[lvl].oldcol = itr->pos.col;
+ int i = itr->s[lvl].i;
+ if (i > 0) {
+ compose(&itr->pos, x->key[i-1].pos);
+ }
+ assert(x->level);
+ x = x->ptr[i];
+ }
+ assert(x == itr->node);
+}
+
+void marktree_check(MarkTree *b)
+{
+#ifndef NDEBUG
+ if (b->root == NULL) {
+ assert(b->n_keys == 0);
+ assert(b->n_nodes == 0);
+ assert(b->id2node == NULL || map_size(b->id2node) == 0);
+ return;
+ }
+
+ mtpos_t dummy;
+ bool last_right = false;
+ size_t nkeys = check_node(b, b->root, &dummy, &last_right);
+ assert(b->n_keys == nkeys);
+ assert(b->n_keys == map_size(b->id2node));
+#else
+ // Do nothing, as assertions are required
+ (void)b;
+#endif
+}
+
+#ifndef NDEBUG
+static size_t check_node(MarkTree *b, mtnode_t *x,
+ mtpos_t *last, bool *last_right)
+{
+ assert(x->n <= 2 * T - 1);
+ // TODO(bfredl): too strict if checking "in repair" post-delete tree.
+ assert(x->n >= (x != b->root ? T-1 : 0));
+ size_t n_keys = (size_t)x->n;
+
+ for (int i = 0; i < x->n; i++) {
+ if (x->level) {
+ n_keys += check_node(b, x->ptr[i], last, last_right);
+ } else {
+ *last = (mtpos_t) { 0, 0 };
+ }
+ if (i > 0) {
+ unrelative(x->key[i-1].pos, last);
+ }
+ if (x->level) {
+ }
+ assert(pos_leq(*last, x->key[i].pos));
+ if (last->row == x->key[i].pos.row && last->col == x->key[i].pos.col) {
+ assert(!*last_right || IS_RIGHT(x->key[i].id));
+ }
+ *last_right = IS_RIGHT(x->key[i].id);
+ assert(x->key[i].pos.col >= 0);
+ assert(pmap_get(uint64_t)(b->id2node, ANTIGRAVITY(x->key[i].id)) == x);
+ }
+
+ if (x->level) {
+ n_keys += 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]->level == x->level-1);
+ // PARANOIA: check no double node ref
+ for (int j = 0; j < i; j++) {
+ assert(x->ptr[i] != x->ptr[j]);
+ }
+ }
+ } else {
+ *last = x->key[x->n-1].pos;
+ }
+ return n_keys;
+}
+#endif
+
+char *mt_inspect_rec(MarkTree *b)
+{
+ 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;
+}
+
+void mt_inspect_node(MarkTree *b, garray_T *ga, mtnode_t *n, mtpos_t off)
+{
+ static char buf[1024];
+#define GA_PUT(x) ga_concat(ga, (char_u *)(x))
+ GA_PUT("[");
+ if (n->level) {
+ mt_inspect_node(b, ga, n->ptr[0], off);
+ }
+ for (int i = 0; i < n->n; i++) {
+ mtpos_t p = n->key[i].pos;
+ unrelative(off, &p);
+ snprintf((char *)buf, sizeof(buf), "%d/%d", p.row, p.col);
+ GA_PUT(buf);
+ if (n->level) {
+ mt_inspect_node(b, ga, n->ptr[i+1], p);
+ } else {
+ GA_PUT(",");
+ }
+ }
+ GA_PUT("]");
+#undef GA_PUT
+}
+
diff --git a/src/nvim/marktree.h b/src/nvim/marktree.h
new file mode 100644
index 0000000000..0c73e75b2e
--- /dev/null
+++ b/src/nvim/marktree.h
@@ -0,0 +1,76 @@
+#ifndef NVIM_MARKTREE_H
+#define NVIM_MARKTREE_H
+
+#include <stdint.h>
+#include "nvim/map.h"
+#include "nvim/garray.h"
+
+#define MT_MAX_DEPTH 20
+#define MT_BRANCH_FACTOR 10
+
+typedef struct {
+ int32_t row;
+ int32_t col;
+} mtpos_t;
+
+typedef struct {
+ int32_t row;
+ int32_t col;
+ uint64_t id;
+ bool right_gravity;
+} mtmark_t;
+
+typedef struct mtnode_s mtnode_t;
+typedef struct {
+ int oldcol;
+ int i;
+} iterstate_t;
+
+typedef struct {
+ mtpos_t pos;
+ int lvl;
+ mtnode_t *node;
+ int i;
+ iterstate_t s[MT_MAX_DEPTH];
+} MarkTreeIter;
+
+
+// Internal storage
+//
+// NB: actual marks have id > 0, so we can use (row,col,0) pseudo-key for
+// "space before (row,col)"
+typedef struct {
+ mtpos_t pos;
+ uint64_t id;
+} mtkey_t;
+
+struct mtnode_s {
+ int32_t n;
+ int32_t level;
+ // TODO(bfredl): we could consider having a only-sometimes-valid
+ // index into parent for faster "chached" lookup.
+ mtnode_t *parent;
+ mtkey_t key[2 * MT_BRANCH_FACTOR - 1];
+ mtnode_t *ptr[];
+};
+
+// TODO(bfredl): the iterator is pretty much everpresent, make it part of the
+// tree struct itself?
+typedef struct {
+ mtnode_t *root;
+ size_t n_keys, n_nodes;
+ uint64_t next_id;
+ // TODO(bfredl): the pointer to node could be part of the larger
+ // Map(uint64_t, ExtmarkItem) essentially;
+ PMap(uint64_t) *id2node;
+} MarkTree;
+
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "marktree.h.generated.h"
+#endif
+
+#define MARKTREE_PAIRED_FLAG (((uint64_t)1) << 1)
+#define MARKTREE_END_FLAG (((uint64_t)1) << 0)
+
+#endif // NVIM_MARKTREE_H
diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c
index 85e6697bfb..e67be60aa6 100644
--- a/src/nvim/mbyte.c
+++ b/src/nvim/mbyte.c
@@ -265,68 +265,70 @@ static struct
{ const char *name; int canon; }
enc_alias_table[] =
{
- {"ansi", IDX_LATIN_1},
- {"iso-8859-1", IDX_LATIN_1},
- {"latin2", IDX_ISO_2},
- {"latin3", IDX_ISO_3},
- {"latin4", IDX_ISO_4},
- {"cyrillic", IDX_ISO_5},
- {"arabic", IDX_ISO_6},
- {"greek", IDX_ISO_7},
- {"hebrew", IDX_ISO_8},
- {"latin5", IDX_ISO_9},
- {"turkish", IDX_ISO_9}, /* ? */
- {"latin6", IDX_ISO_10},
- {"nordic", IDX_ISO_10}, /* ? */
- {"thai", IDX_ISO_11}, /* ? */
- {"latin7", IDX_ISO_13},
- {"latin8", IDX_ISO_14},
- {"latin9", IDX_ISO_15},
- {"utf8", IDX_UTF8},
- {"unicode", IDX_UCS2},
- {"ucs2", IDX_UCS2},
- {"ucs2be", IDX_UCS2},
- {"ucs-2be", IDX_UCS2},
- {"ucs2le", IDX_UCS2LE},
- {"utf16", IDX_UTF16},
- {"utf16be", IDX_UTF16},
- {"utf-16be", IDX_UTF16},
- {"utf16le", IDX_UTF16LE},
- {"ucs4", IDX_UCS4},
- {"ucs4be", IDX_UCS4},
- {"ucs-4be", IDX_UCS4},
- {"ucs4le", IDX_UCS4LE},
- {"utf32", IDX_UCS4},
- {"utf-32", IDX_UCS4},
- {"utf32be", IDX_UCS4},
- {"utf-32be", IDX_UCS4},
- {"utf32le", IDX_UCS4LE},
- {"utf-32le", IDX_UCS4LE},
- {"932", IDX_CP932},
- {"949", IDX_CP949},
- {"936", IDX_CP936},
- {"gbk", IDX_CP936},
- {"950", IDX_CP950},
- {"eucjp", IDX_EUC_JP},
- {"unix-jis", IDX_EUC_JP},
- {"ujis", IDX_EUC_JP},
- {"shift-jis", IDX_SJIS},
- {"pck", IDX_SJIS}, /* Sun: PCK */
- {"euckr", IDX_EUC_KR},
- {"5601", IDX_EUC_KR}, /* Sun: KS C 5601 */
- {"euccn", IDX_EUC_CN},
- {"gb2312", IDX_EUC_CN},
- {"euctw", IDX_EUC_TW},
- {"japan", IDX_EUC_JP},
- {"korea", IDX_EUC_KR},
- {"prc", IDX_EUC_CN},
- {"chinese", IDX_EUC_CN},
- {"taiwan", IDX_EUC_TW},
- {"cp950", IDX_BIG5},
- {"950", IDX_BIG5},
- {"mac", IDX_MACROMAN},
- {"mac-roman", IDX_MACROMAN},
- {NULL, 0}
+ { "ansi", IDX_LATIN_1 },
+ { "iso-8859-1", IDX_LATIN_1 },
+ { "latin2", IDX_ISO_2 },
+ { "latin3", IDX_ISO_3 },
+ { "latin4", IDX_ISO_4 },
+ { "cyrillic", IDX_ISO_5 },
+ { "arabic", IDX_ISO_6 },
+ { "greek", IDX_ISO_7 },
+ { "hebrew", IDX_ISO_8 },
+ { "latin5", IDX_ISO_9 },
+ { "turkish", IDX_ISO_9 }, // ?
+ { "latin6", IDX_ISO_10 },
+ { "nordic", IDX_ISO_10 }, // ?
+ { "thai", IDX_ISO_11 }, // ?
+ { "latin7", IDX_ISO_13 },
+ { "latin8", IDX_ISO_14 },
+ { "latin9", IDX_ISO_15 },
+ { "utf8", IDX_UTF8 },
+ { "unicode", IDX_UCS2 },
+ { "ucs2", IDX_UCS2 },
+ { "ucs2be", IDX_UCS2 },
+ { "ucs-2be", IDX_UCS2 },
+ { "ucs2le", IDX_UCS2LE },
+ { "utf16", IDX_UTF16 },
+ { "utf16be", IDX_UTF16 },
+ { "utf-16be", IDX_UTF16 },
+ { "utf16le", IDX_UTF16LE },
+ { "ucs4", IDX_UCS4 },
+ { "ucs4be", IDX_UCS4 },
+ { "ucs-4be", IDX_UCS4 },
+ { "ucs4le", IDX_UCS4LE },
+ { "utf32", IDX_UCS4 },
+ { "utf-32", IDX_UCS4 },
+ { "utf32be", IDX_UCS4 },
+ { "utf-32be", IDX_UCS4 },
+ { "utf32le", IDX_UCS4LE },
+ { "utf-32le", IDX_UCS4LE },
+ { "932", IDX_CP932 },
+ { "949", IDX_CP949 },
+ { "936", IDX_CP936 },
+ { "gbk", IDX_CP936 },
+ { "950", IDX_CP950 },
+ { "eucjp", IDX_EUC_JP },
+ { "unix-jis", IDX_EUC_JP },
+ { "ujis", IDX_EUC_JP },
+ { "shift-jis", IDX_SJIS },
+ { "pck", IDX_SJIS }, // Sun: PCK
+ { "euckr", IDX_EUC_KR },
+ { "5601", IDX_EUC_KR }, // Sun: KS C 5601
+ { "euccn", IDX_EUC_CN },
+ { "gb2312", IDX_EUC_CN },
+ { "euctw", IDX_EUC_TW },
+ { "japan", IDX_EUC_JP },
+ { "korea", IDX_EUC_KR },
+ { "prc", IDX_EUC_CN },
+ { "zh-cn", IDX_EUC_CN },
+ { "chinese", IDX_EUC_CN },
+ { "zh-tw", IDX_EUC_TW },
+ { "taiwan", IDX_EUC_TW },
+ { "cp950", IDX_BIG5 },
+ { "950", IDX_BIG5 },
+ { "mac", IDX_MACROMAN },
+ { "mac-roman", IDX_MACROMAN },
+ { NULL, 0 }
};
/*
diff --git a/src/nvim/memline.c b/src/nvim/memline.c
index 15dd2767a2..d5788d96b3 100644
--- a/src/nvim/memline.c
+++ b/src/nvim/memline.c
@@ -523,9 +523,9 @@ void ml_open_file(buf_T *buf)
}
}
- if (mfp->mf_fname == NULL) { /* Failed! */
- need_wait_return = TRUE; /* call wait_return later */
- ++no_wait_return;
+ if (*p_dir != NUL && mfp->mf_fname == NULL) {
+ need_wait_return = true; // call wait_return later
+ no_wait_return++;
(void)EMSG2(_(
"E303: Unable to open swap file for \"%s\", recovery impossible"),
buf_spname(buf) != NULL ? buf_spname(buf) : buf->b_fname);
@@ -540,7 +540,7 @@ void ml_open_file(buf_T *buf)
/// file, or reading into an existing buffer, create a swap file now.
///
/// @param newfile reading file into new buffer
-void check_need_swap(int newfile)
+void check_need_swap(bool newfile)
{
int old_msg_silent = msg_silent; // might be reset by an E325 message
msg_silent = 0; // If swap dialog prompts for input, user needs to see it!
@@ -738,10 +738,10 @@ static void add_b0_fenc(ZERO_BL *b0p, buf_T *buf)
}
-/*
- * Try to recover curbuf from the .swp file.
- */
-void ml_recover(void)
+/// Try to recover curbuf from the .swp file.
+/// @param checkext If true, check the extension and detect whether it is a
+/// swap file.
+void ml_recover(bool checkext)
{
buf_T *buf = NULL;
memfile_T *mfp = NULL;
@@ -785,7 +785,7 @@ void ml_recover(void)
if (fname == NULL) /* When there is no file name */
fname = (char_u *)"";
len = (int)STRLEN(fname);
- if (len >= 4
+ if (checkext && len >= 4
&& STRNICMP(fname + len - 4, ".s", 2) == 0
&& vim_strchr((char_u *)"abcdefghijklmnopqrstuvw",
TOLOWER_ASC(fname[len - 2])) != NULL
@@ -937,8 +937,9 @@ void ml_recover(void)
*/
if (directly) {
expand_env(b0p->b0_fname, NameBuff, MAXPATHL);
- if (setfname(curbuf, NameBuff, NULL, TRUE) == FAIL)
+ if (setfname(curbuf, NameBuff, NULL, true) == FAIL) {
goto theend;
+ }
}
home_replace(NULL, mfp->mf_fname, NameBuff, MAXPATHL, TRUE);
@@ -1375,7 +1376,9 @@ recover_names (
if (curbuf->b_ml.ml_mfp != NULL
&& (p = curbuf->b_ml.ml_mfp->mf_fname) != NULL) {
for (int i = 0; i < num_files; i++) {
- if (path_full_compare(p, files[i], true) & kEqualFiles) {
+ // Do not expand wildcards, on Windows would try to expand
+ // "%tmp%" in "%tmp%file"
+ if (path_full_compare(p, files[i], true, false) & kEqualFiles) {
// Remove the name from files[i]. Move further entries
// down. When the array becomes empty free it here, since
// FreeWild() won't be called below.
@@ -1435,7 +1438,7 @@ recover_names (
* Append the full path to name with path separators made into percent
* signs, to dir. An unnamed buffer is handled as "" (<currentdir>/"")
*/
-static char *make_percent_swname(const char *dir, char *name)
+char *make_percent_swname(const char *dir, char *name)
FUNC_ATTR_NONNULL_ARG(1)
{
char *d = NULL;
@@ -1501,16 +1504,15 @@ static time_t swapfile_info(char_u *fname)
int fd;
struct block0 b0;
time_t x = (time_t)0;
- char *p;
#ifdef UNIX
char uname[B0_UNAME_SIZE];
#endif
- /* print the swap file date */
+ // print the swap file date
FileInfo file_info;
if (os_fileinfo((char *)fname, &file_info)) {
#ifdef UNIX
- /* print name of owner of the file */
+ // print name of owner of the file
if (os_get_uname(file_info.stat.st_uid, uname, B0_UNAME_SIZE) == OK) {
MSG_PUTS(_(" owned by: "));
msg_outtrans((char_u *)uname);
@@ -1519,11 +1521,8 @@ static time_t swapfile_info(char_u *fname)
#endif
MSG_PUTS(_(" dated: "));
x = file_info.stat.st_mtim.tv_sec;
- p = ctime(&x); // includes '\n'
- if (p == NULL)
- MSG_PUTS("(invalid)\n");
- else
- MSG_PUTS(p);
+ char ctime_buf[50];
+ MSG_PUTS(os_ctime_r(&x, ctime_buf, sizeof(ctime_buf)));
}
/*
@@ -1799,9 +1798,10 @@ char_u *ml_get(linenr_T lnum)
/*
* Return pointer to position "pos".
*/
-char_u *ml_get_pos(pos_T *pos)
+char_u *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, false) + pos->col;
}
/*
@@ -1859,7 +1859,10 @@ errorret:
// Avoid giving this message for a recursive call, may happen
// when the GUI redraws part of the text.
recursive++;
- IEMSGN(_("E316: ml_get: cannot find line %" PRId64), lnum);
+ get_trans_bufname(buf);
+ shorten_dir(NameBuff);
+ iemsgf(_("E316: ml_get: cannot find line %" PRId64 " in buffer %d %s"),
+ lnum, buf->b_fnum, NameBuff);
recursive--;
}
goto errorret;
@@ -1873,8 +1876,10 @@ errorret:
buf->b_ml.ml_line_lnum = lnum;
buf->b_ml.ml_flags &= ~ML_LINE_DIRTY;
}
- if (will_change)
+ if (will_change) {
buf->b_ml.ml_flags |= (ML_LOCKED_DIRTY | ML_LOCKED_POS);
+ ml_add_deleted_len_buf(buf, buf->b_ml.ml_line_ptr, -1);
+ }
return buf->b_ml.ml_line_ptr;
}
@@ -1927,6 +1932,7 @@ int ml_append_buf(
colnr_T len, // length of new line, including NUL, or 0
bool newfile // flag, see above
)
+ FUNC_ATTR_NONNULL_ARG(1)
{
if (buf->b_ml.ml_mfp == NULL)
return FAIL;
@@ -2386,21 +2392,32 @@ static int ml_append_int(
void ml_add_deleted_len(char_u *ptr, ssize_t len)
{
+ ml_add_deleted_len_buf(curbuf, ptr, len);
+}
+
+void ml_add_deleted_len_buf(buf_T *buf, char_u *ptr, ssize_t len)
+{
if (inhibit_delete_count) {
return;
}
if (len == -1) {
len = STRLEN(ptr);
}
- curbuf->deleted_bytes += len+1;
- if (curbuf->update_need_codepoints) {
- mb_utflen(ptr, len, &curbuf->deleted_codepoints,
- &curbuf->deleted_codeunits);
- curbuf->deleted_codepoints++; // NL char
- curbuf->deleted_codeunits++;
+ buf->deleted_bytes += len+1;
+ if (buf->update_need_codepoints) {
+ mb_utflen(ptr, len, &buf->deleted_codepoints,
+ &buf->deleted_codeunits);
+ buf->deleted_codepoints++; // NL char
+ buf->deleted_codeunits++;
}
}
+
+int ml_replace(linenr_T lnum, char_u *line, bool copy)
+{
+ return ml_replace_buf(curbuf, lnum, line, copy);
+}
+
/*
* Replace line lnum, with buffering, in current buffer.
*
@@ -2412,36 +2429,37 @@ void ml_add_deleted_len(char_u *ptr, ssize_t len)
*
* return FAIL for failure, OK otherwise
*/
-int ml_replace(linenr_T lnum, char_u *line, bool copy)
+int ml_replace_buf(buf_T *buf, linenr_T lnum, char_u *line, bool copy)
{
if (line == NULL) /* just checking... */
return FAIL;
- /* When starting up, we might still need to create the memfile */
- if (curbuf->b_ml.ml_mfp == NULL && open_buffer(FALSE, NULL, 0) == FAIL)
+ // When starting up, we might still need to create the memfile
+ if (buf->b_ml.ml_mfp == NULL && open_buffer(false, NULL, 0) == FAIL) {
return FAIL;
+ }
bool readlen = true;
if (copy) {
line = vim_strsave(line);
}
- if (curbuf->b_ml.ml_line_lnum != lnum) { // other line buffered
- ml_flush_line(curbuf); // flush it
- } else if (curbuf->b_ml.ml_flags & ML_LINE_DIRTY) { // same line allocated
- ml_add_deleted_len(curbuf->b_ml.ml_line_ptr, -1);
+ if (buf->b_ml.ml_line_lnum != lnum) { // other line buffered
+ ml_flush_line(buf); // flush it
+ } else if (buf->b_ml.ml_flags & ML_LINE_DIRTY) { // same line allocated
+ ml_add_deleted_len_buf(buf, buf->b_ml.ml_line_ptr, -1);
readlen = false; // already added the length
- xfree(curbuf->b_ml.ml_line_ptr); // free it
+ xfree(buf->b_ml.ml_line_ptr); // free it
}
- if (readlen && kv_size(curbuf->update_callbacks)) {
- ml_add_deleted_len(ml_get_buf(curbuf, lnum, false), -1);
+ if (readlen && kv_size(buf->update_callbacks)) {
+ ml_add_deleted_len_buf(buf, ml_get_buf(buf, lnum, false), -1);
}
- curbuf->b_ml.ml_line_ptr = line;
- curbuf->b_ml.ml_line_lnum = lnum;
- curbuf->b_ml.ml_flags = (curbuf->b_ml.ml_flags | ML_LINE_DIRTY) & ~ML_EMPTY;
+ buf->b_ml.ml_line_ptr = line;
+ buf->b_ml.ml_line_lnum = lnum;
+ buf->b_ml.ml_flags = (buf->b_ml.ml_flags | ML_LINE_DIRTY) & ~ML_EMPTY;
return OK;
}
@@ -2523,7 +2541,7 @@ static int ml_delete_int(buf_T *buf, linenr_T lnum, bool message)
// Line should always have an NL char internally (represented as NUL),
// even if 'noeol' is set.
assert(line_size >= 1);
- ml_add_deleted_len((char_u *)dp + line_start, line_size-1);
+ ml_add_deleted_len_buf(buf, (char_u *)dp + line_start, line_size-1);
/*
* special case: If there is only one line in the data block it becomes empty.
@@ -3259,15 +3277,13 @@ attention_message (
)
{
assert(buf->b_fname != NULL);
- time_t x, sx;
- char *p;
++no_wait_return;
(void)EMSG(_("E325: ATTENTION"));
MSG_PUTS(_("\nFound a swap file by the name \""));
msg_home_replace(fname);
MSG_PUTS("\"\n");
- sx = swapfile_info(fname);
+ const time_t swap_mtime = swapfile_info(fname);
MSG_PUTS(_("While opening file \""));
msg_outtrans(buf->b_fname);
MSG_PUTS("\"\n");
@@ -3276,14 +3292,12 @@ attention_message (
MSG_PUTS(_(" CANNOT BE FOUND"));
} else {
MSG_PUTS(_(" dated: "));
- x = file_info.stat.st_mtim.tv_sec;
- p = ctime(&x); // includes '\n'
- if (p == NULL)
- MSG_PUTS("(invalid)\n");
- else
- MSG_PUTS(p);
- if (sx != 0 && x > sx)
+ time_t x = file_info.stat.st_mtim.tv_sec;
+ char ctime_buf[50];
+ MSG_PUTS(os_ctime_r(&x, ctime_buf, sizeof(ctime_buf)));
+ if (swap_mtime != 0 && x > swap_mtime) {
MSG_PUTS(_(" NEWER than swap file!\n"));
+ }
}
/* Some of these messages are long to allow translation to
* other languages. */
@@ -3989,8 +4003,8 @@ long ml_find_line_or_offset(buf_T *buf, linenr_T lnum, long *offp, bool no_ff)
int ffdos = !no_ff && (get_fileformat(buf) == EOL_DOS);
int extra = 0;
- /* take care of cached line first */
- ml_flush_line(curbuf);
+ // take care of cached line first
+ ml_flush_line(buf);
if (buf->b_ml.ml_usedchunks == -1
|| buf->b_ml.ml_chunksize == NULL
diff --git a/src/nvim/memory.c b/src/nvim/memory.c
index 64aae71433..9bc6b23ce3 100644
--- a/src/nvim/memory.c
+++ b/src/nvim/memory.c
@@ -693,6 +693,8 @@ void free_all_mem(void)
clear_hl_tables(false);
list_free_log();
+
+ check_quickfix_busy();
}
#endif
diff --git a/src/nvim/menu.c b/src/nvim/menu.c
index 18c4f1fff1..b060464383 100644
--- a/src/nvim/menu.c
+++ b/src/nvim/menu.c
@@ -27,6 +27,8 @@
#include "nvim/strings.h"
#include "nvim/ui.h"
#include "nvim/eval/typval.h"
+#include "nvim/screen.h"
+#include "nvim/window.h"
#define MENUDEPTH 10 /* maximum depth of menus */
@@ -46,6 +48,24 @@ static char_u e_notsubmenu[] = N_(
static char_u e_othermode[] = N_("E328: Menu only exists in another mode");
static char_u e_nomenu[] = N_("E329: No menu \"%s\"");
+// Return true if "name" is a window toolbar menu name.
+static bool menu_is_winbar(const char_u *const name)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
+{
+ return (STRNCMP(name, "WinBar", 6) == 0);
+}
+
+int winbar_height(const win_T *const wp)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
+{
+ return wp->w_winbar != NULL && wp->w_winbar->children != NULL ? 1 : 0;
+}
+
+static vimmenu_T **get_root_menu(const char_u *const name)
+ FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
+{
+ return menu_is_winbar(name) ? &curwin->w_winbar : &root_menu;
+}
/// Do the :menu command and relatives.
/// @param eap Ex command arguments
@@ -170,6 +190,12 @@ ex_menu(exarg_T *eap)
goto theend;
}
+ vimmenu_T **root_menu_ptr = get_root_menu(menu_path);
+ if (root_menu_ptr == &curwin->w_winbar) {
+ // Assume the window toolbar menu will change.
+ redraw_later(NOT_VALID);
+ }
+
if (enable != kNone) {
// Change sensitivity of the menu.
// For the PopUp menu, remove a menu for each mode separately.
@@ -182,11 +208,11 @@ ex_menu(exarg_T *eap)
for (i = 0; i < MENU_INDEX_TIP; ++i)
if (modes & (1 << i)) {
p = popup_mode_name(menu_path, i);
- menu_enable_recurse(root_menu, p, MENU_ALL_MODES, enable);
+ menu_enable_recurse(*root_menu_ptr, p, MENU_ALL_MODES, enable);
xfree(p);
}
}
- menu_enable_recurse(root_menu, menu_path, modes, enable);
+ menu_enable_recurse(*root_menu_ptr, menu_path, modes, enable);
} else if (unmenu) {
/*
* Delete menu(s).
@@ -201,13 +227,13 @@ ex_menu(exarg_T *eap)
for (i = 0; i < MENU_INDEX_TIP; ++i)
if (modes & (1 << i)) {
p = popup_mode_name(menu_path, i);
- remove_menu(&root_menu, p, MENU_ALL_MODES, TRUE);
+ remove_menu(root_menu_ptr, p, MENU_ALL_MODES, true);
xfree(p);
}
}
- /* Careful: remove_menu() changes menu_path */
- remove_menu(&root_menu, menu_path, modes, FALSE);
+ // Careful: remove_menu() changes menu_path
+ remove_menu(root_menu_ptr, menu_path, modes, false);
} else {
/*
* Add menu(s).
@@ -244,6 +270,19 @@ ex_menu(exarg_T *eap)
xfree(map_buf);
}
+ if (root_menu_ptr == &curwin->w_winbar) {
+ const int h = winbar_height(curwin);
+
+ if (h != curwin->w_winbar_height) {
+ if (h == 0) {
+ curwin->w_height++;
+ } else if (curwin->w_height > 0) {
+ curwin->w_height--;
+ }
+ curwin->w_winbar_height = h;
+ }
+ }
+
ui_call_update_menu();
theend:
@@ -266,7 +305,6 @@ add_menu_path(
{
char_u *path_name;
int modes = menuarg->modes;
- vimmenu_T **menup;
vimmenu_T *menu = NULL;
vimmenu_T *parent;
vimmenu_T **lower_pri;
@@ -285,7 +323,8 @@ add_menu_path(
/* Make a copy so we can stuff around with it, since it could be const */
path_name = vim_strsave(menu_path);
- menup = &root_menu;
+ vimmenu_T **root_menu_ptr = get_root_menu(menu_path);
+ vimmenu_T **menup = root_menu_ptr;
parent = NULL;
name = path_name;
while (*name) {
@@ -468,14 +507,16 @@ erret:
/* Delete any empty submenu we added before discovering the error. Repeat
* for higher levels. */
while (parent != NULL && parent->children == NULL) {
- if (parent->parent == NULL)
- menup = &root_menu;
- else
+ if (parent->parent == NULL) {
+ menup = root_menu_ptr;
+ } else {
menup = &parent->parent->children;
- for (; *menup != NULL && *menup != parent; menup = &((*menup)->next))
- ;
- if (*menup == NULL) /* safety check */
+ }
+ for (; *menup != NULL && *menup != parent; menup = &((*menup)->next)) {
+ }
+ if (*menup == NULL) { // safety check
break;
+ }
parent = parent->parent;
free_menu(menup);
}
@@ -620,6 +661,14 @@ remove_menu (
return OK;
}
+// Remove the WinBar menu from window "wp".
+void remove_winbar(win_T *wp)
+ FUNC_ATTR_NONNULL_ALL
+{
+ remove_menu(&wp->w_winbar, (char_u *)"", MENU_ALL_MODES, true);
+ xfree(wp->w_winbar_items);
+}
+
/*
* Free the given menu structure and remove it from the linked list.
*/
@@ -740,7 +789,7 @@ static dict_T *menu_get_recursive(const vimmenu_T *menu, int modes)
/// @return false if could not find path_name
bool menu_get(char_u *const path_name, int modes, list_T *list)
{
- vimmenu_T *menu = find_menu(root_menu, path_name, modes);
+ vimmenu_T *menu = find_menu(*get_root_menu(path_name), path_name, modes);
if (!menu) {
return false;
}
@@ -802,10 +851,8 @@ static vimmenu_T *find_menu(vimmenu_T *menu, char_u *name, int modes)
/// Show the mapping associated with a menu item or hierarchy in a sub-menu.
static int show_menus(char_u *const path_name, int modes)
{
- vimmenu_T *menu;
-
// First, find the (sub)menu with the given name
- menu = find_menu(root_menu, path_name, modes);
+ vimmenu_T *menu = find_menu(*get_root_menu(path_name), path_name, modes);
if (!menu) {
return FAIL;
}
@@ -868,7 +915,7 @@ static void show_menus_recursive(vimmenu_T *menu, int modes, int depth)
if (*menu->strings[bit] == NUL) {
msg_puts_attr("<Nop>", HL_ATTR(HLF_8));
} else {
- msg_outtrans_special(menu->strings[bit], false);
+ msg_outtrans_special(menu->strings[bit], false, 0);
}
}
} else {
@@ -890,6 +937,7 @@ static void show_menus_recursive(vimmenu_T *menu, int modes, int depth)
* Used when expanding menu names.
*/
static vimmenu_T *expand_menu = NULL;
+static vimmenu_T *expand_menu_alt = NULL;
static int expand_modes = 0x0;
static int expand_emenu; /* TRUE for ":emenu" command */
@@ -940,14 +988,15 @@ char_u *set_context_in_menu_cmd(expand_T *xp, char_u *cmd, char_u *arg, int forc
// ":popup" only uses menues, not entries
expand_menus = !((*cmd == 't' && cmd[1] == 'e') || *cmd == 'p');
expand_emenu = (*cmd == 'e');
- if (expand_menus && ascii_iswhite(*p))
- return NULL; /* TODO: check for next command? */
- if (*p == NUL) { /* Complete the menu name */
- /*
- * With :unmenu, you only want to match menus for the appropriate mode.
- * With :menu though you might want to add a menu with the same name as
- * one in another mode, so match menus from other modes too.
- */
+ if (expand_menus && ascii_iswhite(*p)) {
+ return NULL; // TODO(vim): check for next command?
+ }
+ if (*p == NUL) { // Complete the menu name
+ bool try_alt_menu = true;
+
+ // With :unmenu, you only want to match menus for the appropriate mode.
+ // With :menu though you might want to add a menu with the same name as
+ // one in another mode, so match menus from other modes too.
expand_modes = get_menu_cmd_modes(cmd, forceit, NULL, &unmenu);
if (!unmenu)
expand_modes = MENU_ALL_MODES;
@@ -976,6 +1025,10 @@ char_u *set_context_in_menu_cmd(expand_T *xp, char_u *cmd, char_u *arg, int forc
break;
}
menu = menu->next;
+ if (menu == NULL && try_alt_menu) {
+ menu = curwin->w_winbar;
+ try_alt_menu = false;
+ }
}
if (menu == NULL) {
/* No menu found with the name we were looking for */
@@ -984,14 +1037,21 @@ char_u *set_context_in_menu_cmd(expand_T *xp, char_u *cmd, char_u *arg, int forc
}
name = p;
menu = menu->children;
+ try_alt_menu = false;
}
xfree(path_name);
xp->xp_context = expand_menus ? EXPAND_MENUNAMES : EXPAND_MENUS;
xp->xp_pattern = after_dot;
expand_menu = menu;
- } else /* We're in the mapping part */
+ if (expand_menu == root_menu) {
+ expand_menu_alt = curwin->w_winbar;
+ } else {
+ expand_menu_alt = NULL;
+ }
+ } else { // We're in the mapping part
xp->xp_context = EXPAND_NOTHING;
+ }
return NULL;
}
@@ -1002,19 +1062,26 @@ char_u *set_context_in_menu_cmd(expand_T *xp, char_u *cmd, char_u *arg, int forc
char_u *get_menu_name(expand_T *xp, int idx)
{
static vimmenu_T *menu = NULL;
+ static bool did_alt_menu = false;
char_u *str;
static int should_advance = FALSE;
if (idx == 0) { /* first call: start at first item */
menu = expand_menu;
- should_advance = FALSE;
+ did_alt_menu = false;
+ should_advance = false;
}
/* Skip PopUp[nvoci]. */
while (menu != NULL && (menu_is_hidden(menu->dname)
|| menu_is_separator(menu->dname)
- || menu->children == NULL))
+ || menu->children == NULL)) {
menu = menu->next;
+ if (menu == NULL && !did_alt_menu) {
+ menu = expand_menu_alt;
+ did_alt_menu = true;
+ }
+ }
if (menu == NULL) /* at end of linked list */
return NULL;
@@ -1030,9 +1097,14 @@ char_u *get_menu_name(expand_T *xp, int idx)
else
str = (char_u *)"";
- if (should_advance)
- /* Advance to next menu entry. */
+ if (should_advance) {
+ // Advance to next menu entry.
menu = menu->next;
+ if (menu == NULL && !did_alt_menu) {
+ menu = expand_menu_alt;
+ did_alt_menu = true;
+ }
+ }
should_advance = !should_advance;
@@ -1046,6 +1118,7 @@ char_u *get_menu_name(expand_T *xp, int idx)
char_u *get_menu_names(expand_T *xp, int idx)
{
static vimmenu_T *menu = NULL;
+ static bool did_alt_menu = false;
#define TBUFFER_LEN 256
static char_u tbuffer[TBUFFER_LEN]; /*hack*/
char_u *str;
@@ -1053,16 +1126,21 @@ char_u *get_menu_names(expand_T *xp, int idx)
if (idx == 0) { /* first call: start at first item */
menu = expand_menu;
- should_advance = FALSE;
+ did_alt_menu = false;
+ should_advance = false;
}
/* Skip Browse-style entries, popup menus and separators. */
while (menu != NULL
- && ( menu_is_hidden(menu->dname)
- || (expand_emenu && menu_is_separator(menu->dname))
- || menu->dname[STRLEN(menu->dname) - 1] == '.'
- ))
+ && (menu_is_hidden(menu->dname)
+ || (expand_emenu && menu_is_separator(menu->dname))
+ || menu->dname[STRLEN(menu->dname) - 1] == '.')) {
menu = menu->next;
+ if (menu == NULL && !did_alt_menu) {
+ menu = expand_menu_alt;
+ did_alt_menu = true;
+ }
+ }
if (menu == NULL) /* at end of linked list */
return NULL;
@@ -1092,9 +1170,14 @@ char_u *get_menu_names(expand_T *xp, int idx)
} else
str = (char_u *)"";
- if (should_advance)
- /* Advance to next menu entry. */
+ if (should_advance) {
+ // Advance to next menu entry.
menu = menu->next;
+ if (menu == NULL && !did_alt_menu) {
+ menu = expand_menu_alt;
+ did_alt_menu = true;
+ }
+ }
should_advance = !should_advance;
@@ -1279,29 +1362,27 @@ static char_u *menu_text(const char_u *str, int *mnemonic, char_u **actext)
return text;
}
-/*
- * Return TRUE if "name" can be a menu in the MenuBar.
- */
-int menu_is_menubar(char_u *name)
+// Return true if "name" can be a menu in the MenuBar.
+bool menu_is_menubar(const char_u *const name)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
{
return !menu_is_popup(name)
&& !menu_is_toolbar(name)
+ && !menu_is_winbar(name)
&& *name != MNU_HIDDEN_CHAR;
}
-/*
- * Return TRUE if "name" is a popup menu name.
- */
-int menu_is_popup(char_u *name)
+// Return true if "name" is a popup menu name.
+bool menu_is_popup(const char_u *const name)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
{
return STRNCMP(name, "PopUp", 5) == 0;
}
-/*
- * Return TRUE if "name" is a toolbar menu name.
- */
-int menu_is_toolbar(char_u *name)
+// Return true if "name" is a toolbar menu name.
+bool menu_is_toolbar(const char_u *const name)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
{
return STRNCMP(name, "ToolBar", 7) == 0;
}
@@ -1325,53 +1406,15 @@ static int menu_is_hidden(char_u *name)
|| (menu_is_popup(name) && name[5] != NUL);
}
-/*
- * Given a menu descriptor, e.g. "File.New", find it in the menu hierarchy and
- * execute it.
- */
-void ex_emenu(exarg_T *eap)
+// Execute "menu". Use by ":emenu" and the window toolbar.
+// "eap" is NULL for the window toolbar.
+static void execute_menu(const exarg_T *eap, vimmenu_T *menu)
+ FUNC_ATTR_NONNULL_ARG(2)
{
- vimmenu_T *menu;
- char_u *name;
- char_u *saved_name;
- char_u *p;
- int idx;
- char_u *mode;
-
- saved_name = vim_strsave(eap->arg);
-
- menu = root_menu;
- name = saved_name;
- while (*name) {
- /* Find in the menu hierarchy */
- p = menu_name_skip(name);
+ int idx = -1;
+ char_u *mode;
- while (menu != NULL) {
- if (menu_name_equal(name, menu)) {
- if (*p == NUL && menu->children != NULL) {
- EMSG(_("E333: Menu path must lead to a menu item"));
- menu = NULL;
- } else if (*p != NUL && menu->children == NULL) {
- EMSG(_(e_notsubmenu));
- menu = NULL;
- }
- break;
- }
- menu = menu->next;
- }
- if (menu == NULL || *p == NUL)
- break;
- menu = menu->children;
- name = p;
- }
- xfree(saved_name);
- if (menu == NULL) {
- EMSG2(_("E334: Menu not found: %s"), eap->arg);
- return;
- }
-
- /* Found the menu, so execute.
- * Use the Insert mode entry when returning to Insert mode. */
+ // Use the Insert mode entry when returning to Insert mode.
if (((State & INSERT) || restart_edit) && !current_sctx.sc_sid) {
mode = (char_u *)"Insert";
idx = MENU_INDEX_INSERT;
@@ -1384,7 +1427,7 @@ void ex_emenu(exarg_T *eap)
* is. Just execute the visual binding for the menu. */
mode = (char_u *)"Visual";
idx = MENU_INDEX_VISUAL;
- } else if (eap->addr_count) {
+ } else if (eap != NULL && eap->addr_count) {
pos_T tpos;
mode = (char_u *)"Visual";
@@ -1422,9 +1465,13 @@ void ex_emenu(exarg_T *eap)
/* Adjust the cursor to make sure it is in the correct pos
* for exclusive mode */
- if (*p_sel == 'e' && gchar_cursor() != NUL)
- ++curwin->w_cursor.col;
- } else {
+ if (*p_sel == 'e' && gchar_cursor() != NUL) {
+ curwin->w_cursor.col++;
+ }
+ }
+
+ // For the WinBar menu always use the Normal mode menu.
+ if (idx == -1 || eap == NULL) {
mode = (char_u *)"Normal";
idx = MENU_INDEX_NORMAL;
}
@@ -1432,19 +1479,114 @@ void ex_emenu(exarg_T *eap)
assert(idx != MENU_INDEX_INVALID);
if (menu->strings[idx] != NULL) {
// When executing a script or function execute the commands right now.
+ // Also for the window toolbar
// Otherwise put them in the typeahead buffer.
- if (current_sctx.sc_sid != 0) {
- exec_normal_cmd(menu->strings[idx], menu->noremap[idx],
- menu->silent[idx]);
+ if (eap == NULL || current_sctx.sc_sid != 0) {
+ save_state_T save_state;
+
+ ex_normal_busy++;
+ if (save_current_state(&save_state)) {
+ exec_normal_cmd(menu->strings[idx], menu->noremap[idx],
+ menu->silent[idx]);
+ }
+ restore_current_state(&save_state);
+ ex_normal_busy--;
} else {
ins_typebuf(menu->strings[idx], menu->noremap[idx], 0, true,
menu->silent[idx]);
}
- } else {
+ } else if (eap != NULL) {
EMSG2(_("E335: Menu not defined for %s mode"), mode);
}
}
+// Given a menu descriptor, e.g. "File.New", find it in the menu hierarchy and
+// execute it.
+void ex_emenu(exarg_T *eap)
+{
+ char_u *saved_name = vim_strsave(eap->arg);
+ vimmenu_T *menu = *get_root_menu(saved_name);
+ char_u *name = saved_name;
+ while (*name) {
+ // Find in the menu hierarchy
+ char_u *p = menu_name_skip(name);
+
+ while (menu != NULL) {
+ if (menu_name_equal(name, menu)) {
+ if (*p == NUL && menu->children != NULL) {
+ EMSG(_("E333: Menu path must lead to a menu item"));
+ menu = NULL;
+ } else if (*p != NUL && menu->children == NULL) {
+ EMSG(_(e_notsubmenu));
+ menu = NULL;
+ }
+ break;
+ }
+ menu = menu->next;
+ }
+ if (menu == NULL || *p == NUL) {
+ break;
+ }
+ menu = menu->children;
+ name = p;
+ }
+ xfree(saved_name);
+ if (menu == NULL) {
+ EMSG2(_("E334: Menu not found: %s"), eap->arg);
+ return;
+ }
+
+ // Found the menu, so execute.
+ execute_menu(eap, menu);
+}
+
+// Handle a click in the window toolbar of "wp" at column "col".
+void winbar_click(win_T *wp, int col)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (wp->w_winbar_items == NULL) {
+ return;
+ }
+ for (int idx = 0; wp->w_winbar_items[idx].wb_menu != NULL; idx++) {
+ winbar_item_T *item = &wp->w_winbar_items[idx];
+
+ if (col >= item->wb_startcol && col <= item->wb_endcol) {
+ win_T *save_curwin = NULL;
+ const pos_T save_visual = VIsual;
+ const int save_visual_active = VIsual_active;
+ const int save_visual_select = VIsual_select;
+ const int save_visual_reselect = VIsual_reselect;
+ const int save_visual_mode = VIsual_mode;
+
+ if (wp != curwin) {
+ // Clicking in the window toolbar of a not-current window.
+ // Make that window the current one and save Visual mode.
+ save_curwin = curwin;
+ VIsual_active = false;
+ curwin = wp;
+ curbuf = curwin->w_buffer;
+ check_cursor();
+ }
+
+ // Note: the command might close the current window.
+ execute_menu(NULL, item->wb_menu);
+
+ if (save_curwin != NULL && win_valid(save_curwin)) {
+ curwin = save_curwin;
+ curbuf = curwin->w_buffer;
+ VIsual = save_visual;
+ VIsual_active = save_visual_active;
+ VIsual_select = save_visual_select;
+ VIsual_reselect = save_visual_reselect;
+ VIsual_mode = save_visual_mode;
+ }
+ if (!win_valid(wp)) {
+ break;
+ }
+ }
+ }
+}
+
/*
* Translation of menu names. Just a simple lookup table.
*/
diff --git a/src/nvim/menu.h b/src/nvim/menu.h
index 5ff979f2bf..642d9aafac 100644
--- a/src/nvim/menu.h
+++ b/src/nvim/menu.h
@@ -6,18 +6,6 @@
#include "nvim/types.h" // for char_u and expand_T
#include "nvim/ex_cmds_defs.h" // for exarg_T
-/// Indices into vimmenu_T->strings[] and vimmenu_T->noremap[] for each mode
-/// \addtogroup MENU_INDEX
-/// @{
-#define MENU_INDEX_INVALID -1
-#define MENU_INDEX_NORMAL 0
-#define MENU_INDEX_VISUAL 1
-#define MENU_INDEX_SELECT 2
-#define MENU_INDEX_OP_PENDING 3
-#define MENU_INDEX_INSERT 4
-#define MENU_INDEX_CMDLINE 5
-#define MENU_INDEX_TIP 6
-#define MENU_MODES 7
/// @}
/// note MENU_INDEX_TIP is not a 'real' mode
@@ -37,28 +25,6 @@
/// Start a menu name with this to not include it on the main menu bar
#define MNU_HIDDEN_CHAR ']'
-typedef struct VimMenu vimmenu_T;
-
-struct VimMenu {
- int modes; ///< Which modes is this menu visible for
- int enabled; ///< for which modes the menu is enabled
- char_u *name; ///< Name of menu, possibly translated
- char_u *dname; ///< Displayed Name ("name" without '&')
- char_u *en_name; ///< "name" untranslated, NULL when
- ///< was not translated
- char_u *en_dname; ///< NULL when "dname" untranslated
- int mnemonic; ///< mnemonic key (after '&')
- char_u *actext; ///< accelerator text (after TAB)
- long priority; ///< Menu order priority
- char_u *strings[MENU_MODES]; ///< Mapped string for each mode
- int noremap[MENU_MODES]; ///< A \ref REMAP_VALUES flag for each mode
- bool silent[MENU_MODES]; ///< A silent flag for each mode
- vimmenu_T *children; ///< Children of sub-menu
- vimmenu_T *parent; ///< Parent of menu
- vimmenu_T *next; ///< Next item in menu
-};
-
-
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "menu.h.generated.h"
#endif
diff --git a/src/nvim/message.c b/src/nvim/message.c
index 30e906cd5f..8999365d32 100644
--- a/src/nvim/message.c
+++ b/src/nvim/message.c
@@ -228,20 +228,25 @@ int msg_attr(const char *s, const int attr)
}
/// similar to msg_outtrans_attr, but support newlines and tabs.
-void msg_multiline_attr(const char *s, int attr, bool check_int)
+void msg_multiline_attr(const char *s, int attr,
+ bool check_int, bool *need_clear)
FUNC_ATTR_NONNULL_ALL
{
const char *next_spec = s;
- while (next_spec != NULL && (!check_int || !got_int)) {
+ while (next_spec != NULL) {
+ if (check_int && got_int) {
+ return;
+ }
next_spec = strpbrk(s, "\t\n\r");
if (next_spec != NULL) {
// Printing all char that are before the char found by strpbrk
- msg_outtrans_len_attr((char_u *)s, next_spec - s, attr);
+ msg_outtrans_len_attr((const char_u *)s, next_spec - s, attr);
- if (*next_spec != TAB) {
+ if (*next_spec != TAB && *need_clear) {
msg_clr_eos();
+ *need_clear = false;
}
msg_putchar_attr((uint8_t)(*next_spec), attr);
s = next_spec + 1;
@@ -253,6 +258,7 @@ void msg_multiline_attr(const char *s, int attr, bool check_int)
if (*s != NUL) {
msg_outtrans_attr((char_u *)s, attr);
}
+ return;
}
@@ -300,23 +306,21 @@ bool msg_attr_keep(char_u *s, int attr, bool keep, bool multiline)
add_msg_hist((const char *)s, -1, attr, multiline);
}
- /* When displaying keep_msg, don't let msg_start() free it, caller must do
- * that. */
- if (s == keep_msg)
- keep_msg = NULL;
-
/* Truncate the message if needed. */
msg_start();
buf = msg_strtrunc(s, FALSE);
if (buf != NULL)
s = buf;
+ bool need_clear = true;
if (multiline) {
- msg_multiline_attr((char *)s, attr, false);
+ msg_multiline_attr((char *)s, attr, false, &need_clear);
} else {
msg_outtrans_attr(s, attr);
}
- msg_clr_eos();
+ if (need_clear) {
+ msg_clr_eos();
+ }
retval = msg_end();
if (keep && retval && vim_strsize(s) < (int)(Rows - cmdline_row - 1)
@@ -1375,7 +1379,7 @@ static void msg_home_replace_attr(char_u *fname, int attr)
/*
* Output 'len' characters in 'str' (including NULs) with translation
- * if 'len' is -1, output upto a NUL character.
+ * if 'len' is -1, output up to a NUL character.
* Use attributes 'attr'.
* Return the number of characters it takes on the screen.
*/
@@ -1384,12 +1388,12 @@ int msg_outtrans(char_u *str)
return msg_outtrans_attr(str, 0);
}
-int msg_outtrans_attr(char_u *str, int attr)
+int msg_outtrans_attr(const char_u *str, int attr)
{
return msg_outtrans_len_attr(str, (int)STRLEN(str), attr);
}
-int msg_outtrans_len(char_u *str, int len)
+int msg_outtrans_len(const char_u *str, int len)
{
return msg_outtrans_len_attr(str, len, 0);
}
@@ -1402,7 +1406,7 @@ char_u *msg_outtrans_one(char_u *p, int attr)
{
int l;
- if (has_mbyte && (l = (*mb_ptr2len)(p)) > 1) {
+ if ((l = utfc_ptr2len(p)) > 1) {
msg_outtrans_len_attr(p, l, attr);
return p + l;
}
@@ -1410,7 +1414,7 @@ char_u *msg_outtrans_one(char_u *p, int attr)
return p + 1;
}
-int msg_outtrans_len_attr(char_u *msgstr, int len, int attr)
+int msg_outtrans_len_attr(const char_u *msgstr, int len, int attr)
{
int retval = 0;
const char *str = (const char *)msgstr;
@@ -1498,7 +1502,7 @@ void msg_make(char_u *arg)
}
}
-/// Output the string 'str' upto a NUL character.
+/// Output the string 'str' up to a NUL character.
/// Return the number of characters it takes on the screen.
///
/// If K_SPECIAL is encountered, then it is taken in conjunction with the
@@ -1512,7 +1516,8 @@ void msg_make(char_u *arg)
/// the character/string -- webb
int msg_outtrans_special(
const char_u *strstart,
- int from ///< true for LHS of a mapping
+ bool from, ///< true for LHS of a mapping
+ int maxlen ///< screen columns, 0 for unlimeted
)
{
if (strstart == NULL) {
@@ -1532,6 +1537,9 @@ int msg_outtrans_special(
string = str2special((const char **)&str, from, false);
}
const int len = vim_strsize((char_u *)string);
+ if (maxlen > 0 && retval + len >= maxlen) {
+ break;
+ }
// Highlight special keys
msg_puts_attr(string, (len > 1
&& (*mb_ptr2len)((char_u *)string) <= 1
@@ -2561,10 +2569,15 @@ static int do_more_prompt(int typed_char)
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--"
+ // should be displayed.
+ bool no_need_more = headless_mode && !embedded_mode;
+
// We get called recursively when a timer callback outputs a message. In
// that case don't show another prompt. Also when at the hit-Enter prompt
// and nothing was typed.
- if (entered || (State == HITRETURN && typed_char == 0)) {
+ if (no_need_more || entered || (State == HITRETURN && typed_char == 0)) {
return false;
}
entered = true;
diff --git a/src/nvim/misc1.c b/src/nvim/misc1.c
index ab3520dd73..6dafbafb3e 100644
--- a/src/nvim/misc1.c
+++ b/src/nvim/misc1.c
@@ -30,7 +30,6 @@
#include "nvim/indent_c.h"
#include "nvim/buffer_updates.h"
#include "nvim/main.h"
-#include "nvim/mark.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
@@ -74,7 +73,8 @@ static garray_T ga_users = GA_EMPTY_INIT_VALUE;
* If "include_space" is set, include trailing whitespace while calculating the
* length.
*/
-int get_leader_len(char_u *line, char_u **flags, int backward, int include_space)
+int get_leader_len(char_u *line, char_u **flags,
+ bool backward, bool include_space)
{
int i, j;
int result;
@@ -278,7 +278,7 @@ int get_last_leader_offset(char_u *line, char_u **flags)
// whitespace. Otherwise we would think we are inside a
// comment if the middle part appears somewhere in the middle
// of the line. E.g. for C the "*" appears often.
- for (j = 0; ascii_iswhite(line[j]) && j <= i; j++) {
+ for (j = 0; j <= i && ascii_iswhite(line[j]); j++) {
}
if (j < i) {
continue;
@@ -484,25 +484,39 @@ int plines_win_col(win_T *wp, linenr_T lnum, long column)
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) {
- // Check if there are any really folded lines, but also included lines
- // that are maybe folded.
- linenr_T x = foldedCount(wp, first, NULL);
- if (x > 0) {
- ++count; /* count 1 for "+-- folded" line */
- first += x;
- } else {
- if (first == wp->w_topline) {
- count += plines_win_nofill(wp, first, true) + wp->w_topfill;
- } else {
- count += plines_win(wp, first, true);
- }
- first++;
- }
+ linenr_T next = first;
+ count += plines_win_full(wp, first, &next, NULL, false);
+ first = next + 1;
}
return count;
}
@@ -1015,6 +1029,15 @@ void fast_breakcheck(void)
}
}
+// Like line_breakcheck() but check 100 times less often.
+void veryfast_breakcheck(void)
+{
+ if (++breakcheck_count >= BREAKCHECK_SKIP * 100) {
+ breakcheck_count = 0;
+ os_breakcheck();
+ }
+}
+
/// os_call_shell() wrapper. Handles 'verbose', :profile, and v:shell_error.
/// Invalidates cached tags.
///
@@ -1148,3 +1171,26 @@ int goto_im(void)
{
return p_im && stuff_empty() && typebuf_typed();
}
+
+/// Put the timestamp of an undo header in "buf[buflen]" in a nice format.
+void add_time(char_u *buf, size_t buflen, time_t tt)
+{
+ struct tm curtime;
+
+ if (time(NULL) - tt >= 100) {
+ os_localtime_r(&tt, &curtime);
+ if (time(NULL) - tt < (60L * 60L * 12L)) {
+ // within 12 hours
+ (void)strftime((char *)buf, buflen, "%H:%M:%S", &curtime);
+ } else {
+ // longer ago
+ (void)strftime((char *)buf, buflen, "%Y/%m/%d %H:%M:%S", &curtime);
+ }
+ } else {
+ int64_t seconds = time(NULL) - tt;
+ vim_snprintf((char *)buf, buflen,
+ NGETTEXT("%" PRId64 " second ago",
+ "%" PRId64 " seconds ago", (uint32_t)seconds),
+ seconds);
+ }
+}
diff --git a/src/nvim/mouse.c b/src/nvim/mouse.c
index d0aa0653cb..fcd9ee4f75 100644
--- a/src/nvim/mouse.c
+++ b/src/nvim/mouse.c
@@ -60,6 +60,7 @@ int jump_to_mouse(int flags,
{
static int on_status_line = 0; // #lines below bottom of window
static int on_sep_line = 0; // on separator right of window
+ static bool in_winbar = false;
static int prev_row = -1;
static int prev_col = -1;
static win_T *dragwin = NULL; // window being dragged
@@ -73,6 +74,7 @@ int jump_to_mouse(int flags,
int col = mouse_col;
int grid = mouse_grid;
int mouse_char;
+ int fdc = 0;
mouse_past_bottom = false;
mouse_past_eol = false;
@@ -92,10 +94,24 @@ int jump_to_mouse(int flags,
retnomove:
// before moving the cursor for a left click which is NOT in a status
// line, stop Visual mode
- if (on_status_line)
+ if (on_status_line) {
return IN_STATUS_LINE;
- if (on_sep_line)
+ }
+ if (on_sep_line) {
return IN_SEP_LINE;
+ }
+ if (in_winbar) {
+ // A quick second click may arrive as a double-click, but we use it
+ // as a second click in the WinBar.
+ if ((mod_mask & MOD_MASK_MULTI_CLICK) && !(flags & MOUSE_RELEASED)) {
+ wp = mouse_find_win(&grid, &row, &col);
+ if (wp == NULL) {
+ return IN_UNKNOWN;
+ }
+ winbar_click(wp, col);
+ }
+ return IN_OTHER_WIN | MOUSE_WINBAR;
+ }
if (flags & MOUSE_MAY_STOP_VIS) {
end_visual_mode();
redraw_curbuf_later(INVERTED); // delete the inversion
@@ -109,12 +125,12 @@ retnomove:
if (flags & MOUSE_SETPOS)
goto retnomove; // ugly goto...
- // Remember the character under the mouse, it might be a '-' or '+' in the
- // fold column. NB: only works for ASCII chars!
+ // Remember the character under the mouse, might be one of foldclose or
+ // foldopen fillchars in the fold column.
if (row >= 0 && row < Rows && col >= 0 && col <= Columns
&& default_grid.chars != NULL) {
- mouse_char = default_grid.chars[default_grid.line_offset[row]
- + (unsigned)col][0];
+ mouse_char = utf_ptr2char(default_grid.chars[default_grid.line_offset[row]
+ + (unsigned)col]);
} else {
mouse_char = ' ';
}
@@ -131,7 +147,18 @@ retnomove:
if (wp == NULL) {
return IN_UNKNOWN;
}
+ fdc = win_fdccol_count(wp);
dragwin = NULL;
+
+ if (row == -1) {
+ // A click in the window toolbar does not enter another window or
+ // change Visual highlighting.
+ winbar_click(wp, col);
+ in_winbar = true;
+ return IN_OTHER_WIN | MOUSE_WINBAR;
+ }
+ in_winbar = false;
+
// winpos and height may change in win_enter()!
if (grid == DEFAULT_GRID_HANDLE && row >= wp->w_height) {
// In (or below) status line
@@ -165,9 +192,8 @@ retnomove:
|| (!on_status_line
&& !on_sep_line
&& (wp->w_p_rl
- ? col < wp->w_width_inner - wp->w_p_fdc
- : col >= wp->w_p_fdc + (cmdwin_type == 0 && wp == curwin
- ? 0 : 1))
+ ? col < wp->w_width_inner - fdc
+ : col >= fdc + (cmdwin_type == 0 && wp == curwin ? 0 : 1))
&& (flags & MOUSE_MAY_STOP_VIS)))) {
end_visual_mode();
redraw_curbuf_later(INVERTED); // delete the inversion
@@ -222,6 +248,9 @@ retnomove:
did_drag |= count;
}
return IN_SEP_LINE; // Cursor didn't move
+ } else if (in_winbar) {
+ // After a click on the window toolbar don't start Visual mode.
+ return IN_OTHER_WIN | MOUSE_WINBAR;
} else {
// keep_window_focus must be true
// before moving the cursor for a left click, stop Visual mode
@@ -305,8 +334,8 @@ retnomove:
}
// Check for position outside of the fold column.
- if (curwin->w_p_rl ? col < curwin->w_width_inner - curwin->w_p_fdc :
- col >= curwin->w_p_fdc + (cmdwin_type == 0 ? 0 : 1)) {
+ if (curwin->w_p_rl ? col < curwin->w_width_inner - fdc :
+ col >= fdc + (cmdwin_type == 0 ? 0 : 1)) {
mouse_char = ' ';
}
@@ -350,7 +379,7 @@ retnomove:
count |= CURSOR_MOVED; // Cursor has moved
}
- if (mouse_char == '+') {
+ if (mouse_char == curwin->w_p_fcs_chars.foldclosed) {
count |= MOUSE_FOLD_OPEN;
} else if (mouse_char != ' ') {
count |= MOUSE_FOLD_CLOSE;
@@ -470,6 +499,7 @@ win_T *mouse_find_win(int *gridp, int *rowp, int *colp)
// exist.
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
if (wp == fp->fr_win) {
+ *rowp -= wp->w_winbar_height;
return wp;
}
}
@@ -479,7 +509,7 @@ win_T *mouse_find_win(int *gridp, int *rowp, int *colp)
static win_T *mouse_find_grid_win(int *gridp, int *rowp, int *colp)
{
if (*gridp == msg_grid.handle) {
- rowp += msg_grid_pos;
+ // rowp += msg_grid_pos; // PVS: dead store #11612
*gridp = DEFAULT_GRID_HANDLE;
} else if (*gridp > 1) {
win_T *wp = get_win_by_grid_handle(*gridp);
@@ -508,31 +538,30 @@ static win_T *mouse_find_grid_win(int *gridp, int *rowp, int *colp)
return NULL;
}
-/*
- * setmouse() - switch mouse on/off depending on current mode and 'mouse'
- */
+/// Set UI mouse depending on current mode and 'mouse'.
+///
+/// Emits mouse_on/mouse_off UI event (unless 'mouse' is empty).
void setmouse(void)
{
- int checkfor;
-
ui_cursor_shape();
- /* be quick when mouse is off */
- if (*p_mouse == NUL)
+ // Be quick when mouse is off.
+ if (*p_mouse == NUL) {
return;
+ }
- if (VIsual_active)
+ int checkfor = MOUSE_NORMAL; // assume normal mode
+ if (VIsual_active) {
checkfor = MOUSE_VISUAL;
- else if (State == HITRETURN || State == ASKMORE || State == SETWSIZE)
+ } else if (State == HITRETURN || State == ASKMORE || State == SETWSIZE) {
checkfor = MOUSE_RETURN;
- else if (State & INSERT)
+ } else if (State & INSERT) {
checkfor = MOUSE_INSERT;
- else if (State & CMDLINE)
+ } else if (State & CMDLINE) {
checkfor = MOUSE_COMMAND;
- else if (State == CONFIRM || State == EXTERNCMD)
- checkfor = ' '; /* don't use mouse for ":confirm" or ":!cmd" */
- else
- checkfor = MOUSE_NORMAL; /* assume normal mode */
+ } else if (State == CONFIRM || State == EXTERNCMD) {
+ checkfor = ' '; // don't use mouse for ":confirm" or ":!cmd"
+ }
if (mouse_has(checkfor)) {
ui_call_mouse_on();
diff --git a/src/nvim/mouse.h b/src/nvim/mouse.h
index 0149f7c7c0..6c5bc5dc0e 100644
--- a/src/nvim/mouse.h
+++ b/src/nvim/mouse.h
@@ -16,6 +16,7 @@
#define CURSOR_MOVED 0x100
#define MOUSE_FOLD_CLOSE 0x200 // clicked on '-' in fold column
#define MOUSE_FOLD_OPEN 0x400 // clicked on '+' in fold column
+#define MOUSE_WINBAR 0x800 // in window toolbar
// flags for jump_to_mouse()
#define MOUSE_FOCUS 0x01 // need to stay in this window
diff --git a/src/nvim/move.c b/src/nvim/move.c
index e6fee9999f..8a8a639a52 100644
--- a/src/nvim/move.c
+++ b/src/nvim/move.c
@@ -65,17 +65,10 @@ static void comp_botline(win_T *wp)
done = 0;
}
- for (; lnum <= wp->w_buffer->b_ml.ml_line_count; ++lnum) {
- int n;
+ for (; lnum <= wp->w_buffer->b_ml.ml_line_count; lnum++) {
linenr_T last = lnum;
- bool folded = hasFoldingWin(wp, lnum, NULL, &last, true, NULL);
- if (folded) {
- n = 1;
- } else if (lnum == wp->w_topline) {
- n = plines_win_nofill(wp, lnum, true) + wp->w_topfill;
- } else {
- n = plines_win(wp, lnum, true);
- }
+ bool folded;
+ int n = plines_win_full(wp, lnum, &last, &folded, true);
if (lnum <= wp->w_cursor.lnum && last >= wp->w_cursor.lnum) {
wp->w_cline_row = done;
wp->w_cline_height = n;
@@ -93,6 +86,7 @@ static void comp_botline(win_T *wp)
/* wp->w_botline is the line that is just below the window */
wp->w_botline = lnum;
wp->w_valid |= VALID_BOTLINE|VALID_BOTLINE_AP;
+ wp->w_viewport_invalid = true;
set_empty_rows(wp, done);
@@ -149,7 +143,8 @@ void update_topline(void)
int old_topfill;
bool check_topline = false;
bool check_botline = false;
- long save_so = p_so;
+ long *so_ptr = curwin->w_p_so >= 0 ? &curwin->w_p_so : &p_so;
+ long save_so = *so_ptr;
// If there is no valid screen and when the window height is zero just use
// the cursor line.
@@ -157,6 +152,7 @@ void update_topline(void)
curwin->w_topline = curwin->w_cursor.lnum;
curwin->w_botline = curwin->w_topline;
curwin->w_valid |= VALID_BOTLINE|VALID_BOTLINE_AP;
+ curwin->w_viewport_invalid = true;
curwin->w_scbind_pos = 1;
return;
}
@@ -165,9 +161,10 @@ void update_topline(void)
if (curwin->w_valid & VALID_TOPLINE)
return;
- /* When dragging with the mouse, don't scroll that quickly */
- if (mouse_dragging > 0)
- p_so = mouse_dragging - 1;
+ // When dragging with the mouse, don't scroll that quickly
+ if (mouse_dragging > 0) {
+ *so_ptr = mouse_dragging - 1;
+ }
old_topline = curwin->w_topline;
old_topfill = curwin->w_topfill;
@@ -180,6 +177,7 @@ void update_topline(void)
curwin->w_topline = 1;
curwin->w_botline = 2;
curwin->w_valid |= VALID_BOTLINE|VALID_BOTLINE_AP;
+ curwin->w_viewport_invalid = true;
curwin->w_scbind_pos = 1;
}
/*
@@ -213,15 +211,17 @@ void update_topline(void)
* scrolled). */
n = 0;
for (linenr_T lnum = curwin->w_cursor.lnum;
- lnum < curwin->w_topline + p_so; ++lnum) {
- ++n;
- /* stop at end of file or when we know we are far off */
- if (lnum >= curbuf->b_ml.ml_line_count || n >= halfheight)
+ lnum < curwin->w_topline + *so_ptr; lnum++) {
+ n++;
+ // stop at end of file or when we know we are far off
+ if (lnum >= curbuf->b_ml.ml_line_count || n >= halfheight) {
break;
+ }
(void)hasFolding(lnum, NULL, &lnum);
}
- } else
- n = curwin->w_topline + p_so - curwin->w_cursor.lnum;
+ } else {
+ n = curwin->w_topline + *so_ptr - curwin->w_cursor.lnum;
+ }
/* If we weren't very close to begin with, we scroll to put the
* cursor in the middle of the window. Otherwise put the cursor
@@ -254,7 +254,7 @@ void update_topline(void)
if (curwin->w_botline <= curbuf->b_ml.ml_line_count) {
if (curwin->w_cursor.lnum < curwin->w_botline) {
if (((long)curwin->w_cursor.lnum
- >= (long)curwin->w_botline - p_so
+ >= (long)curwin->w_botline - *so_ptr
|| hasAnyFolding(curwin)
)) {
lineoff_T loff;
@@ -273,13 +273,15 @@ void update_topline(void)
&& (loff.lnum + 1 < curwin->w_botline || loff.fill == 0)
) {
n += loff.height;
- if (n >= p_so)
+ if (n >= *so_ptr) {
break;
+ }
botline_forw(&loff);
}
- if (n >= p_so)
- /* sufficient context, no need to scroll */
+ if (n >= *so_ptr) {
+ // sufficient context, no need to scroll
check_botline = false;
+ }
} else {
/* sufficient context, no need to scroll */
check_botline = false;
@@ -292,7 +294,7 @@ void update_topline(void)
* botline - p_so (approximation of how much will be
* scrolled). */
for (linenr_T lnum = curwin->w_cursor.lnum;
- lnum >= curwin->w_botline - p_so; lnum--) {
+ lnum >= curwin->w_botline - *so_ptr; lnum--) {
line_count++;
// stop at end of file or when we know we are far off
if (lnum <= 0 || line_count > curwin->w_height_inner + 1) {
@@ -302,7 +304,7 @@ void update_topline(void)
}
} else
line_count = curwin->w_cursor.lnum - curwin->w_botline
- + 1 + p_so;
+ + 1 + *so_ptr;
if (line_count <= curwin->w_height_inner + 1) {
scroll_cursor_bot(scrolljump_value(), false);
} else {
@@ -312,6 +314,7 @@ void update_topline(void)
}
}
curwin->w_valid |= VALID_TOPLINE;
+ curwin->w_viewport_invalid = true;
win_check_anchored_floats(curwin);
/*
@@ -331,7 +334,7 @@ void update_topline(void)
validate_cursor();
}
- p_so = save_so;
+ *so_ptr = save_so;
}
/*
@@ -363,25 +366,28 @@ static int scrolljump_value(void)
*/
static bool check_top_offset(void)
{
- if (curwin->w_cursor.lnum < curwin->w_topline + p_so
+ long so = get_scrolloff_value();
+ if (curwin->w_cursor.lnum < curwin->w_topline + so
|| hasAnyFolding(curwin)
) {
lineoff_T loff;
loff.lnum = curwin->w_cursor.lnum;
loff.fill = 0;
- int n = curwin->w_topfill; /* always have this context */
- /* Count the visible screen lines above the cursor line. */
- while (n < p_so) {
+ int n = curwin->w_topfill; // always have this context
+ // Count the visible screen lines above the cursor line.
+ while (n < so) {
topline_back(&loff);
- /* Stop when included a line above the window. */
+ // Stop when included a line above the window.
if (loff.lnum < curwin->w_topline
|| (loff.lnum == curwin->w_topline && loff.fill > 0)
- )
+ ) {
break;
+ }
n += loff.height;
}
- if (n < p_so)
+ if (n < so) {
return true;
+ }
}
return false;
}
@@ -405,6 +411,7 @@ void check_cursor_moved(win_T *wp)
|VALID_CHEIGHT|VALID_CROW|VALID_TOPLINE);
wp->w_valid_cursor = wp->w_cursor;
wp->w_valid_leftcol = wp->w_leftcol;
+ wp->w_viewport_invalid = true;
} else if (wp->w_cursor.col != wp->w_valid_cursor.col
|| wp->w_leftcol != wp->w_valid_leftcol
|| wp->w_cursor.coladd != wp->w_valid_cursor.coladd
@@ -413,6 +420,7 @@ void check_cursor_moved(win_T *wp)
wp->w_valid_cursor.col = wp->w_cursor.col;
wp->w_valid_leftcol = wp->w_leftcol;
wp->w_valid_cursor.coladd = wp->w_cursor.coladd;
+ wp->w_viewport_invalid = true;
}
}
@@ -571,18 +579,14 @@ static void curs_rows(win_T *wp)
break;
wp->w_cline_row += wp->w_lines[i].wl_size;
} else {
- long fold_count = foldedCount(wp, lnum, NULL);
- if (fold_count) {
- lnum += fold_count;
- if (lnum > wp->w_cursor.lnum)
- break;
- ++wp->w_cline_row;
- } else if (lnum == wp->w_topline) {
- wp->w_cline_row += plines_win_nofill(wp, lnum++, true)
- + wp->w_topfill;
- } else {
- wp->w_cline_row += plines_win(wp, lnum++, true);
+ linenr_T last = lnum;
+ bool folded;
+ int n = plines_win_full(wp, lnum, &last, &folded, false);
+ lnum = last + 1;
+ if (folded && lnum > wp->w_cursor.lnum) {
+ break;
}
+ wp->w_cline_row += n;
}
}
@@ -593,18 +597,13 @@ static void curs_rows(win_T *wp)
|| (i < wp->w_lines_valid
&& (!wp->w_lines[i].wl_valid
|| wp->w_lines[i].wl_lnum != wp->w_cursor.lnum))) {
- if (wp->w_cursor.lnum == wp->w_topline)
- wp->w_cline_height = plines_win_nofill(wp, wp->w_cursor.lnum,
- true) + wp->w_topfill;
- else
- wp->w_cline_height = plines_win(wp, wp->w_cursor.lnum, true);
- wp->w_cline_folded = hasFoldingWin(wp, wp->w_cursor.lnum,
- NULL, NULL, true, NULL);
+ wp->w_cline_height = plines_win_full(wp, wp->w_cursor.lnum, NULL,
+ &wp->w_cline_folded, 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;
- wp->w_cline_folded = hasFoldingWin(wp, wp->w_cursor.lnum,
- NULL, NULL, true, NULL);
+ wp->w_cline_folded = hasFoldingWin(wp, wp->w_cursor.lnum, NULL,
+ NULL, true, NULL);
} else {
wp->w_cline_height = wp->w_lines[i].wl_size;
wp->w_cline_folded = wp->w_lines[i].wl_folded;
@@ -646,12 +645,9 @@ static void validate_cheight(void)
{
check_cursor_moved(curwin);
if (!(curwin->w_valid & VALID_CHEIGHT)) {
- if (curwin->w_cursor.lnum == curwin->w_topline)
- curwin->w_cline_height = plines_nofill(curwin->w_cursor.lnum)
- + curwin->w_topfill;
- else
- curwin->w_cline_height = plines(curwin->w_cursor.lnum);
- curwin->w_cline_folded = hasFolding(curwin->w_cursor.lnum, NULL, NULL);
+ curwin->w_cline_height = plines_win_full(curwin, curwin->w_cursor.lnum,
+ NULL, &curwin->w_cline_folded,
+ true);
curwin->w_valid |= VALID_CHEIGHT;
}
}
@@ -693,7 +689,7 @@ int win_col_off(win_T *wp)
{
return ((wp->w_p_nu || wp->w_p_rnu) ? number_width(wp) + 1 : 0)
+ (cmdwin_type == 0 || wp != curwin ? 0 : 1)
- + (int)wp->w_p_fdc
+ + win_fdccol_count(wp)
+ (win_signcol_count(wp) * win_signcol_width(wp));
}
@@ -733,6 +729,8 @@ void curs_columns(
colnr_T startcol;
colnr_T endcol;
colnr_T prev_skipcol;
+ long so = get_scrolloff_value();
+ long siso = get_sidescrolloff_value();
/*
* First make sure that w_topline is valid (after moving the cursor).
@@ -804,10 +802,10 @@ void curs_columns(
* If we get closer to the edge than 'sidescrolloff', scroll a little
* extra
*/
- assert(p_siso <= INT_MAX);
- int off_left = startcol - curwin->w_leftcol - (int)p_siso;
+ assert(siso <= INT_MAX);
+ int off_left = startcol - curwin->w_leftcol - (int)siso;
int off_right =
- endcol - curwin->w_leftcol - curwin->w_width_inner + (int)p_siso + 1;
+ endcol - curwin->w_leftcol - curwin->w_width_inner + (int)siso + 1;
if (off_left < 0 || off_right > 0) {
int diff = (off_left < 0) ? -off_left: off_right;
@@ -853,7 +851,7 @@ void curs_columns(
int plines = 0;
if ((curwin->w_wrow >= curwin->w_height_inner
|| ((prev_skipcol > 0
- || curwin->w_wrow + p_so >= curwin->w_height_inner)
+ || curwin->w_wrow + so >= curwin->w_height_inner)
&& (plines =
plines_win_nofill(curwin, curwin->w_cursor.lnum, false)) - 1
>= curwin->w_height_inner))
@@ -869,17 +867,18 @@ void curs_columns(
* 2: Less than "p_so" lines below
* 3: both of them */
extra = 0;
- if (curwin->w_skipcol + p_so * width > curwin->w_virtcol)
+ if (curwin->w_skipcol + so * width > curwin->w_virtcol) {
extra = 1;
- /* Compute last display line of the buffer line that we want at the
- * bottom of the window. */
+ }
+ // Compute last display line of the buffer line that we want at the
+ // bottom of the window.
if (plines == 0) {
plines = plines_win(curwin, curwin->w_cursor.lnum, false);
}
plines--;
- if (plines > curwin->w_wrow + p_so) {
- assert(p_so <= INT_MAX);
- n = curwin->w_wrow + (int)p_so;
+ if (plines > curwin->w_wrow + so) {
+ assert(so <= INT_MAX);
+ n = curwin->w_wrow + (int)so;
} else {
n = plines;
}
@@ -887,7 +886,7 @@ void curs_columns(
extra += 2;
}
- if (extra == 3 || plines < p_so * 2) {
+ if (extra == 3 || plines < so * 2) {
// not enough room for 'scrolloff', put cursor in the middle
n = curwin->w_virtcol / width;
if (n > curwin->w_height_inner / 2) {
@@ -901,9 +900,9 @@ void curs_columns(
}
curwin->w_skipcol = n * width;
} else if (extra == 1) {
- /* less then 'scrolloff' lines above, decrease skipcol */
- assert(p_so <= INT_MAX);
- extra = (curwin->w_skipcol + (int)p_so * width - curwin->w_virtcol
+ // less then 'scrolloff' lines above, decrease skipcol
+ assert(so <= INT_MAX);
+ extra = (curwin->w_skipcol + (int)so * width - curwin->w_virtcol
+ width - 1) / width;
if (extra > 0) {
if ((colnr_T)(extra * width) > curwin->w_skipcol)
@@ -997,7 +996,7 @@ void textpos2screenpos(win_T *wp, pos_T *pos, int *rowp, int *scolp,
col -= wp->w_leftcol;
- if (col >= 0 && col < width) {
+ if (col >= 0 && col < wp->w_width) {
coloff = col - scol + (local ? 0 : wp->w_wincol) + 1;
} else {
scol = ccol = ecol = 0;
@@ -1225,7 +1224,7 @@ void scrolldown_clamp(void)
end_row += curwin->w_cline_height - 1 -
curwin->w_virtcol / curwin->w_width_inner;
}
- if (end_row < curwin->w_height_inner - p_so) {
+ if (end_row < curwin->w_height_inner - get_scrolloff_value()) {
if (can_fill) {
++curwin->w_topfill;
check_topfill(curwin, true);
@@ -1265,14 +1264,14 @@ void scrollup_clamp(void)
validate_virtcol();
start_row -= curwin->w_virtcol / curwin->w_width_inner;
}
- if (start_row >= p_so) {
- if (curwin->w_topfill > 0)
- --curwin->w_topfill;
- else {
+ if (start_row >= get_scrolloff_value()) {
+ if (curwin->w_topfill > 0) {
+ curwin->w_topfill--;
+ } else {
(void)hasFolding(curwin->w_topline, NULL, &curwin->w_topline);
- ++curwin->w_topline;
+ curwin->w_topline++;
}
- ++curwin->w_botline; /* approximate w_botline */
+ curwin->w_botline++; // approximate w_botline
curwin->w_valid &= ~(VALID_WROW|VALID_CROW|VALID_BOTLINE);
}
}
@@ -1368,8 +1367,7 @@ void scroll_cursor_top(int min_scroll, int always)
linenr_T old_topline = curwin->w_topline;
linenr_T old_topfill = curwin->w_topfill;
linenr_T new_topline;
- assert(p_so <= INT_MAX);
- int off = (int)p_so;
+ int off = (int)get_scrolloff_value();
if (mouse_dragging > 0)
off = mouse_dragging - 1;
@@ -1466,6 +1464,7 @@ void scroll_cursor_top(int min_scroll, int always)
curwin->w_valid &=
~(VALID_WROW|VALID_CROW|VALID_BOTLINE|VALID_BOTLINE_AP);
curwin->w_valid |= VALID_TOPLINE;
+ curwin->w_viewport_invalid = true;
}
}
@@ -1511,7 +1510,8 @@ void scroll_cursor_bot(int min_scroll, int set_topbot)
linenr_T old_botline = curwin->w_botline;
int old_valid = curwin->w_valid;
int old_empty_rows = curwin->w_empty_rows;
- linenr_T cln = curwin->w_cursor.lnum; /* Cursor Line Number */
+ linenr_T cln = curwin->w_cursor.lnum; // Cursor Line Number
+ long so = get_scrolloff_value();
if (set_topbot) {
used = 0;
@@ -1570,15 +1570,14 @@ void scroll_cursor_bot(int min_scroll, int set_topbot)
/* Stop when scrolled nothing or at least "min_scroll", found "extra"
* context for 'scrolloff' and counted all lines below the window. */
if ((((scrolled <= 0 || scrolled >= min_scroll)
- && extra >= (
- mouse_dragging > 0 ? mouse_dragging - 1 :
- p_so))
+ && extra >= (mouse_dragging > 0 ? mouse_dragging - 1 : so))
|| boff.lnum + 1 > curbuf->b_ml.ml_line_count)
&& loff.lnum <= curwin->w_botline
&& (loff.lnum < curwin->w_botline
|| loff.fill >= fill_below_window)
- )
+ ) {
break;
+ }
/* Add one line above */
topline_back(&loff);
@@ -1609,9 +1608,8 @@ void scroll_cursor_bot(int min_scroll, int set_topbot)
if (used > curwin->w_height_inner) {
break;
}
- if (extra < (
- mouse_dragging > 0 ? mouse_dragging - 1 :
- p_so) || scrolled < min_scroll) {
+ if (extra < (mouse_dragging > 0 ? mouse_dragging - 1 : so)
+ || scrolled < min_scroll) {
extra += boff.height;
if (boff.lnum >= curwin->w_botline
|| (boff.lnum + 1 == curwin->w_botline
@@ -1671,6 +1669,7 @@ void scroll_cursor_bot(int min_scroll, int set_topbot)
curwin->w_valid = old_valid;
}
curwin->w_valid |= VALID_TOPLINE;
+ curwin->w_viewport_invalid = true;
}
/// Recompute topline to put the cursor halfway across the window
@@ -1745,9 +1744,8 @@ void cursor_correct(void)
* How many lines we would like to have above/below the cursor depends on
* whether the first/last line of the file is on screen.
*/
- assert(p_so <= INT_MAX);
- int above_wanted = (int)p_so;
- int below_wanted = (int)p_so;
+ int above_wanted = (int)get_scrolloff_value();
+ int below_wanted = (int)get_scrolloff_value();
if (mouse_dragging > 0) {
above_wanted = mouse_dragging - 1;
below_wanted = mouse_dragging - 1;
@@ -1761,8 +1759,7 @@ void cursor_correct(void)
}
validate_botline();
if (curwin->w_botline == curbuf->b_ml.ml_line_count + 1
- && mouse_dragging == 0
- ) {
+ && mouse_dragging == 0) {
below_wanted = 0;
int max_off = (curwin->w_height_inner - 1) / 2;
if (above_wanted > max_off) {
@@ -1829,6 +1826,7 @@ void cursor_correct(void)
}
}
curwin->w_valid |= VALID_TOPLINE;
+ curwin->w_viewport_invalid = true;
}
@@ -1843,6 +1841,7 @@ int onepage(Direction dir, long count)
int retval = OK;
lineoff_T loff;
linenr_T old_topline = curwin->w_topline;
+ long so = get_scrolloff_value();
if (curbuf->b_ml.ml_line_count == 1) { /* nothing to do */
beep_flush();
@@ -1858,7 +1857,7 @@ int onepage(Direction dir, long count)
* last line.
*/
if (dir == FORWARD
- ? ((curwin->w_topline >= curbuf->b_ml.ml_line_count - p_so)
+ ? ((curwin->w_topline >= curbuf->b_ml.ml_line_count - so)
&& curwin->w_botline > curbuf->b_ml.ml_line_count)
: (curwin->w_topline == 1
&& curwin->w_topfill ==
diff --git a/src/nvim/msgpack_rpc/channel.c b/src/nvim/msgpack_rpc/channel.c
index 0c874d7922..92ca29209e 100644
--- a/src/nvim/msgpack_rpc/channel.c
+++ b/src/nvim/msgpack_rpc/channel.c
@@ -564,7 +564,7 @@ void rpc_close(Channel *channel)
static void exit_event(void **argv)
{
if (!exiting) {
- mch_exit(0);
+ os_exit(0);
}
}
diff --git a/src/nvim/msgpack_rpc/channel.h b/src/nvim/msgpack_rpc/channel.h
index 9ff5abdc5f..90e1c7d48b 100644
--- a/src/nvim/msgpack_rpc/channel.h
+++ b/src/nvim/msgpack_rpc/channel.h
@@ -15,7 +15,7 @@
/// HACK: os/input.c drains this queue immediately before blocking for input.
/// Events on this queue are async-safe, but they need the resolved state
/// of os_inchar(), so they are processed "just-in-time".
-MultiQueue *ch_before_blocking_events;
+EXTERN MultiQueue *ch_before_blocking_events INIT(= NULL);
#ifdef INCLUDE_GENERATED_DECLARATIONS
diff --git a/src/nvim/msgpack_rpc/server.c b/src/nvim/msgpack_rpc/server.c
index 6168f097a7..062ea784ca 100644
--- a/src/nvim/msgpack_rpc/server.c
+++ b/src/nvim/msgpack_rpc/server.c
@@ -151,7 +151,7 @@ int server_start(const char *endpoint)
result = socket_watcher_start(watcher, MAX_CONNECTIONS, connection_cb);
if (result < 0) {
- WLOG("Failed to start server: %s", uv_strerror(result));
+ WLOG("Failed to start server: %s: %s", uv_strerror(result), watcher->addr);
socket_watcher_close(watcher, free_server);
return result;
}
diff --git a/src/nvim/normal.c b/src/nvim/normal.c
index e7e6d2b365..968cfde388 100644
--- a/src/nvim/normal.c
+++ b/src/nvim/normal.c
@@ -24,7 +24,7 @@
#include "nvim/diff.h"
#include "nvim/digraph.h"
#include "nvim/edit.h"
-#include "nvim/eval.h"
+#include "nvim/eval/userfunc.h"
#include "nvim/ex_cmds.h"
#include "nvim/ex_cmds2.h"
#include "nvim/ex_docmd.h"
@@ -615,14 +615,19 @@ static void normal_redraw_mode_message(NormalState *s)
kmsg = keep_msg;
keep_msg = NULL;
- // showmode() will clear keep_msg, but we want to use it anyway
+ // Showmode() will clear keep_msg, but we want to use it anyway.
+ // First update w_topline.
+ setcursor();
update_screen(0);
// now reset it, otherwise it's put in the history again
keep_msg = kmsg;
+
+ kmsg = vim_strsave(keep_msg);
msg_attr((const char *)kmsg, keep_msg_attr);
xfree(kmsg);
}
setcursor();
+ ui_cursor_shape(); // show different cursor shape
ui_flush();
if (msg_scroll || emsg_on_display) {
os_delay(1000L, true); // wait at least one second
@@ -760,7 +765,7 @@ static void normal_get_additional_char(NormalState *s)
// because if it's put back with vungetc() it's too late to apply
// mapping.
no_mapping--;
- while (enc_utf8 && lang && (s->c = vpeekc()) > 0
+ while (lang && (s->c = vpeekc()) > 0
&& (s->c >= 0x100 || MB_BYTE2LEN(vpeekc()) > 1)) {
s->c = plain_vgetc();
if (!utf_iscomposing(s->c)) {
@@ -874,8 +879,10 @@ static void normal_finish_command(NormalState *s)
s->old_mapped_len = typebuf_maplen();
}
- // If an operation is pending, handle it...
- do_pending_operator(&s->ca, s->old_col, false);
+ // If an operation is pending, handle it. But not for K_IGNORE.
+ if (s->ca.cmdchar != K_IGNORE) {
+ do_pending_operator(&s->ca, s->old_col, false);
+ }
// Wait for a moment when a message is displayed that will be overwritten
// by the mode message.
@@ -1255,13 +1262,20 @@ static void normal_redraw(NormalState *s)
maketitle();
}
+ curbuf->b_last_used = time(NULL);
+
// Display message after redraw. If an external message is still visible,
// it contains the kept message already.
if (keep_msg != NULL && !msg_ext_is_visible()) {
- // msg_attr_keep() will set keep_msg to NULL, must free the string here.
- // Don't reset keep_msg, msg_attr_keep() uses it to check for duplicates.
- char *p = (char *)keep_msg;
- msg_attr(p, keep_msg_attr);
+ char_u *const p = vim_strsave(keep_msg);
+
+ // msg_start() will set keep_msg to NULL, make a copy
+ // first. Don't reset keep_msg, msg_attr_keep() uses it to
+ // check for duplicates. Never put this message in
+ // history.
+ msg_hist_off = true;
+ msg_attr((const char *)p, keep_msg_attr);
+ msg_hist_off = false;
xfree(p);
}
@@ -1422,12 +1436,12 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank)
if (oap->motion_type == kMTLineWise) {
oap->inclusive = false;
} else if (oap->motion_type == kMTCharWise) {
- // If the motion already was characterwise, toggle "inclusive"
+ // If the motion already was charwise, toggle "inclusive"
oap->inclusive = !oap->inclusive;
}
oap->motion_type = kMTCharWise;
} else if (oap->motion_force == Ctrl_V) {
- // Change line- or characterwise motion into Visual block mode.
+ // Change line- or charwise motion into Visual block mode.
if (!VIsual_active) {
VIsual_active = true;
VIsual = oap->start;
@@ -1516,7 +1530,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank)
}
// In Select mode, a linewise selection is operated upon like a
- // characterwise selection.
+ // charwise selection.
// Special case: gH<Del> deletes the last line.
if (VIsual_select && VIsual_mode == 'V'
&& cap->oap->op_type != OP_DELETE) {
@@ -1553,9 +1567,13 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank)
if (!VIsual_active) {
if (hasFolding(oap->start.lnum, &oap->start.lnum, NULL))
oap->start.col = 0;
- if (hasFolding(curwin->w_cursor.lnum, NULL,
- &curwin->w_cursor.lnum))
+ if ((curwin->w_cursor.col > 0
+ || oap->inclusive
+ || oap->motion_type == kMTLineWise)
+ && hasFolding(curwin->w_cursor.lnum, NULL,
+ &curwin->w_cursor.lnum)) {
curwin->w_cursor.col = (colnr_T)STRLEN(get_cursor_line_ptr());
+ }
}
oap->end = curwin->w_cursor;
curwin->w_cursor = oap->start;
@@ -1705,13 +1723,12 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank)
}
}
- /* Include the trailing byte of a multi-byte char. */
- if (has_mbyte && oap->inclusive) {
- int l;
-
- l = (*mb_ptr2len)(ml_get_pos(&oap->end));
- if (l > 1)
+ // Include the trailing byte of a multi-byte char.
+ if (oap->inclusive) {
+ const int l = utfc_ptr2len(ml_get_pos(&oap->end));
+ if (l > 1) {
oap->end.col += l - 1;
+ }
}
curwin->w_set_curswant = true;
@@ -1840,10 +1857,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank)
restart_edit = 0;
// Restore linebreak, so that when the user edits it looks as before.
- if (curwin->w_p_lbr != lbr_saved) {
- curwin->w_p_lbr = lbr_saved;
- get_op_vcol(oap, redo_VIsual_mode, false);
- }
+ curwin->w_p_lbr = lbr_saved;
// Reset finish_op now, don't want it set inside edit().
finish_op = false;
@@ -1929,10 +1943,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank)
restart_edit = 0;
// Restore linebreak, so that when the user edits it looks as before.
- if (curwin->w_p_lbr != lbr_saved) {
- curwin->w_p_lbr = lbr_saved;
- get_op_vcol(oap, redo_VIsual_mode, false);
- }
+ curwin->w_p_lbr = lbr_saved;
op_insert(oap, cap->count1);
@@ -1958,18 +1969,15 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank)
CancelRedo();
} else {
// Restore linebreak, so that when the user edits it looks as before.
- if (curwin->w_p_lbr != lbr_saved) {
- curwin->w_p_lbr = lbr_saved;
- get_op_vcol(oap, redo_VIsual_mode, false);
- }
+ curwin->w_p_lbr = lbr_saved;
op_replace(oap, cap->nchar);
}
break;
case OP_FOLD:
- VIsual_reselect = false; /* don't reselect now */
- foldCreate(oap->start.lnum, oap->end.lnum);
+ VIsual_reselect = false; // don't reselect now
+ foldCreate(curwin, oap->start.lnum, oap->end.lnum);
break;
case OP_FOLDOPEN:
@@ -1987,9 +1995,9 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank)
case OP_FOLDDEL:
case OP_FOLDDELREC:
- VIsual_reselect = false; /* don't reselect now */
- deleteFold(oap->start.lnum, oap->end.lnum,
- oap->op_type == OP_FOLDDELREC, oap->is_VIsual);
+ VIsual_reselect = false; // don't reselect now
+ deleteFold(curwin, oap->start.lnum, oap->end.lnum,
+ oap->op_type == OP_FOLDDELREC, oap->is_VIsual);
break;
case OP_NR_ADD:
@@ -2560,7 +2568,14 @@ do_mouse (
* JUMP!
*/
jump_flags = jump_to_mouse(jump_flags,
- oap == NULL ? NULL : &(oap->inclusive), which_button);
+ oap == NULL ? NULL : &(oap->inclusive),
+ which_button);
+
+ // A click in the window toolbar has no side effects.
+ if (jump_flags & MOUSE_WINBAR) {
+ return false;
+ }
+
moved = (jump_flags & CURSOR_MOVED);
in_status_line = (jump_flags & IN_STATUS_LINE);
in_sep_line = (jump_flags & IN_SEP_LINE);
@@ -2588,12 +2603,13 @@ do_mouse (
/* Set global flag that we are extending the Visual area with mouse
* dragging; temporarily minimize 'scrolloff'. */
- if (VIsual_active && is_drag && p_so) {
- /* In the very first line, allow scrolling one line */
- if (mouse_row == 0)
+ if (VIsual_active && is_drag && get_scrolloff_value()) {
+ // In the very first line, allow scrolling one line
+ if (mouse_row == 0) {
mouse_dragging = 2;
- else
+ } else {
mouse_dragging = 1;
+ }
}
/* When dragging the mouse above the window, scroll down. */
@@ -2900,17 +2916,17 @@ static void find_end_of_word(pos_T *pos)
*/
static int get_mouse_class(char_u *p)
{
- int c;
-
- if (has_mbyte && MB_BYTE2LEN(p[0]) > 1)
+ if (MB_BYTE2LEN(p[0]) > 1) {
return mb_get_class(p);
+ }
- c = *p;
- if (c == ' ' || c == '\t')
+ const int c = *p;
+ if (c == ' ' || c == '\t') {
return 0;
-
- if (vim_iswordc(c))
+ }
+ if (vim_iswordc(c)) {
return 2;
+ }
/*
* There are a few special cases where we want certain combinations of
@@ -3646,7 +3662,9 @@ static void nv_help(cmdarg_T *cap)
*/
static void nv_addsub(cmdarg_T *cap)
{
- if (!VIsual_active && cap->oap->op_type == OP_NOP) {
+ if (bt_prompt(curbuf) && !prompt_curpos_editable()) {
+ clearopbeep(cap->oap);
+ } else if (!VIsual_active && cap->oap->op_type == OP_NOP) {
prep_redo_cmd(cap);
cap->oap->op_type = cap->cmdchar == Ctrl_A ? OP_NR_ADD : OP_NR_SUB;
op_addsub(cap->oap, cap->count1, cap->arg);
@@ -3754,7 +3772,6 @@ find_decl (
bool retval = true;
bool incll;
int searchflags = flags_arg;
- bool valid;
pat = xmalloc(len + 7);
@@ -3789,10 +3806,8 @@ find_decl (
/* Search forward for the identifier, ignore comment lines. */
clearpos(&found_pos);
for (;; ) {
- valid = false;
- (void)valid; // Avoid "dead assignment" warning.
t = searchit(curwin, curbuf, &curwin->w_cursor, NULL, FORWARD,
- pat, 1L, searchflags, RE_LAST, (linenr_T)0, NULL, NULL);
+ pat, 1L, searchflags, RE_LAST, NULL);
if (curwin->w_cursor.lnum >= old_pos.lnum) {
t = false; // match after start is failure too
}
@@ -3825,7 +3840,7 @@ find_decl (
curwin->w_cursor.col = 0;
continue;
}
- valid = is_ident(get_cursor_line_ptr(), curwin->w_cursor.col);
+ bool valid = is_ident(get_cursor_line_ptr(), curwin->w_cursor.col);
// If the current position is not a valid identifier and a previous match is
// present, favor that one instead.
@@ -3923,18 +3938,20 @@ static bool nv_screengo(oparg_T *oap, int dir, long dist)
n = ((linelen - width1 - 1) / width2 + 1) * width2 + width1;
else
n = width1;
- if (curwin->w_curswant > (colnr_T)n + 1)
- curwin->w_curswant -= ((curwin->w_curswant - n) / width2 + 1)
- * width2;
+ if (curwin->w_curswant >= n) {
+ curwin->w_curswant = n - 1;
+ }
}
while (dist--) {
if (dir == BACKWARD) {
- if ((long)curwin->w_curswant >= width2)
- /* move back within line */
+ if (curwin->w_curswant >= width1) {
+ // Move back within the line. This can give a negative value
+ // for w_curswant if width1 < width2 (with cpoptions+=n),
+ // which will get clipped to column 0.
curwin->w_curswant -= width2;
- else {
- /* to previous line */
+ } else {
+ // to previous line
if (curwin->w_cursor.lnum == 1) {
retval = false;
break;
@@ -3971,6 +3988,13 @@ static bool nv_screengo(oparg_T *oap, int dir, long dist)
}
curwin->w_cursor.lnum++;
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
+ // clipped to column 0.
+ if (curwin->w_curswant >= width1) {
+ curwin->w_curswant -= width2;
+ }
linelen = linetabsize(get_cursor_line_ptr());
}
}
@@ -4085,9 +4109,9 @@ void scroll_redraw(int up, long count)
scrollup(count, true);
else
scrolldown(count, true);
- if (p_so) {
- /* Adjust the cursor position for 'scrolloff'. Mark w_topline as
- * valid, otherwise the screen jumps back at the end of the file. */
+ if (get_scrolloff_value()) {
+ // Adjust the cursor position for 'scrolloff'. Mark w_topline as
+ // valid, otherwise the screen jumps back at the end of the file.
cursor_correct();
check_cursor_moved(curwin);
curwin->w_valid |= VALID_TOPLINE;
@@ -4116,6 +4140,7 @@ void scroll_redraw(int up, long count)
}
if (curwin->w_cursor.lnum != prev_lnum)
coladvance(curwin->w_curswant);
+ curwin->w_viewport_invalid = true;
redraw_later(VALID);
}
@@ -4131,8 +4156,8 @@ static void nv_zet(cmdarg_T *cap)
int old_fen = curwin->w_p_fen;
bool undo = false;
- assert(p_siso <= INT_MAX);
- int l_p_siso = (int)p_siso;
+ int l_p_siso = (int)get_sidescrolloff_value();
+ assert(l_p_siso <= INT_MAX);
if (ascii_isdigit(nchar)) {
/*
@@ -4336,11 +4361,12 @@ dozet:
/* "zD": delete fold at cursor recursively */
case 'd':
case 'D': if (foldManualAllowed(false)) {
- if (VIsual_active)
+ if (VIsual_active) {
nv_operator(cap);
- else
- deleteFold(curwin->w_cursor.lnum,
- curwin->w_cursor.lnum, nchar == 'D', false);
+ } else {
+ deleteFold(curwin, curwin->w_cursor.lnum,
+ curwin->w_cursor.lnum, nchar == 'D', false);
+ }
}
break;
@@ -4348,11 +4374,11 @@ dozet:
case 'E': if (foldmethodIsManual(curwin)) {
clearFolding(curwin);
changed_window_setting();
- } else if (foldmethodIsMarker(curwin))
- deleteFold((linenr_T)1, curbuf->b_ml.ml_line_count,
- true, false);
- else
+ } else if (foldmethodIsMarker(curwin)) {
+ deleteFold(curwin, (linenr_T)1, curbuf->b_ml.ml_line_count, true, false);
+ } else {
EMSG(_("E352: Cannot erase folds with current 'foldmethod'"));
+ }
break;
/* "zn": fold none: reset 'foldenable' */
@@ -4454,16 +4480,16 @@ dozet:
case 'r':
curwin->w_p_fdl += cap->count1;
{
- int d = getDeepestNesting();
+ int d = getDeepestNesting(curwin);
if (curwin->w_p_fdl >= d) {
curwin->w_p_fdl = d;
}
}
break;
- /* "zR": open all folds */
- case 'R': curwin->w_p_fdl = getDeepestNesting();
- old_fdl = -1; /* force an update */
+ case 'R': // "zR": open all folds
+ curwin->w_p_fdl = getDeepestNesting(curwin);
+ old_fdl = -1; // force an update
break;
case 'j': /* "zj" move to next fold downwards */
@@ -4577,7 +4603,7 @@ static void nv_colon(cmdarg_T *cap)
nv_operator(cap);
} else {
if (cap->oap->op_type != OP_NOP) {
- // Using ":" as a movement is characterwise exclusive.
+ // Using ":" as a movement is charwise exclusive.
cap->oap->motion_type = kMTCharWise;
cap->oap->inclusive = false;
} else if (cap->count0 && !is_cmdkey) {
@@ -4678,9 +4704,8 @@ static void nv_ctrlo(cmdarg_T *cap)
}
}
-/*
- * CTRL-^ command, short for ":e #"
- */
+// CTRL-^ command, short for ":e #". Works even when the alternate buffer is
+// not named.
static void nv_hat(cmdarg_T *cap)
{
if (!checkclearopq(cap->oap))
@@ -4905,10 +4930,9 @@ static void nv_ident(cmdarg_T *cap)
*p++ = '\\';
/* When current byte is a part of multibyte character, copy all
* bytes of that character. */
- if (has_mbyte) {
- size_t len = (size_t)((*mb_ptr2len)(ptr) - 1);
- for (size_t i = 0; i < len && n > 0; ++i, --n)
- *p++ = *ptr++;
+ const size_t len = (size_t)(utfc_ptr2len(ptr) - 1);
+ for (size_t i = 0; i < len && n > 0; i++, n--) {
+ *p++ = *ptr++;
}
*p++ = *ptr++;
}
@@ -4919,16 +4943,19 @@ static void nv_ident(cmdarg_T *cap)
* Execute the command.
*/
if (cmdchar == '*' || cmdchar == '#') {
- if (!g_cmd && (
- has_mbyte ? vim_iswordp(mb_prevptr(get_cursor_line_ptr(), ptr)) :
- vim_iswordc(ptr[-1])))
+ if (!g_cmd
+ && vim_iswordp(mb_prevptr(get_cursor_line_ptr(), ptr))) {
STRCAT(buf, "\\>");
- /* put pattern in search history */
+ }
+ // put pattern in search history
init_history();
add_to_history(HIST_SEARCH, (char_u *)buf, true, NUL);
- (void)normal_search(cap, cmdchar == '*' ? '/' : '?', (char_u *)buf, 0);
+ (void)normal_search(cap, cmdchar == '*' ? '/' : '?', (char_u *)buf, 0,
+ NULL);
} else {
+ g_tag_at_cursor = true;
do_cmdline_cmd(buf);
+ g_tag_at_cursor = false;
}
xfree(buf);
@@ -4963,9 +4990,8 @@ get_visual_text (
*pp = ml_get_pos(&VIsual);
*lenp = (size_t)curwin->w_cursor.col - (size_t)VIsual.col + 1;
}
- if (has_mbyte)
- /* Correct the length to include the whole last character. */
- *lenp += (size_t)((*mb_ptr2len)(*pp + (*lenp - 1)) - 1);
+ // Correct the length to include the whole last character.
+ *lenp += (size_t)(utfc_ptr2len(*pp + (*lenp - 1)) - 1);
}
reset_VIsual_and_resel();
return true;
@@ -5125,14 +5151,10 @@ static void nv_right(cmdarg_T *cap)
break;
} else if (PAST_LINE) {
curwin->w_set_curswant = true;
- if (virtual_active())
+ if (virtual_active()) {
oneright();
- else {
- if (has_mbyte)
- curwin->w_cursor.col +=
- (*mb_ptr2len)(get_cursor_pos_ptr());
- else
- ++curwin->w_cursor.col;
+ } else {
+ curwin->w_cursor.col += (*mb_ptr2len)(get_cursor_pos_ptr());
}
}
}
@@ -5187,11 +5209,7 @@ static void nv_left(cmdarg_T *cap)
char_u *cp = get_cursor_pos_ptr();
if (*cp != NUL) {
- if (has_mbyte) {
- curwin->w_cursor.col += (*mb_ptr2len)(cp);
- } else {
- curwin->w_cursor.col++;
- }
+ curwin->w_cursor.col += utfc_ptr2len(cp);
}
cap->retval |= CA_NO_ADJ_OP_END;
}
@@ -5245,6 +5263,13 @@ static void nv_down(cmdarg_T *cap)
// In the cmdline window a <CR> executes the command.
if (cmdwin_type != 0 && cap->cmdchar == CAR) {
cmdwin_result = CAR;
+ } else if (bt_prompt(curbuf) && cap->cmdchar == CAR
+ && curwin->w_cursor.lnum == curbuf->b_ml.ml_line_count) {
+ // In a prompt buffer a <CR> in the last line invokes the callback.
+ invoke_prompt_callback();
+ if (restart_edit == 0) {
+ restart_edit = 'a';
+ }
} else {
cap->oap->motion_type = kMTLineWise;
if (cursor_down(cap->count1, cap->oap->op_type == OP_NOP) == false) {
@@ -5346,7 +5371,7 @@ static void nv_search(cmdarg_T *cap)
// When using 'incsearch' the cursor may be moved to set a different search
// start position.
- cap->searchbuf = getcmdline(cap->cmdchar, cap->count1, 0);
+ cap->searchbuf = getcmdline(cap->cmdchar, cap->count1, 0, true);
if (cap->searchbuf == NULL) {
clearop(oap);
@@ -5355,7 +5380,7 @@ static void nv_search(cmdarg_T *cap)
(void)normal_search(cap, cap->cmdchar, cap->searchbuf,
(cap->arg || !equalpos(save_cursor, curwin->w_cursor))
- ? 0 : SEARCH_MARK);
+ ? 0 : SEARCH_MARK, NULL);
}
/*
@@ -5365,14 +5390,15 @@ static void nv_search(cmdarg_T *cap)
static void nv_next(cmdarg_T *cap)
{
pos_T old = curwin->w_cursor;
- int i = normal_search(cap, 0, NULL, SEARCH_MARK | cap->arg);
+ int wrapped = false;
+ int i = normal_search(cap, 0, NULL, SEARCH_MARK | cap->arg, &wrapped);
- if (i == 1 && equalpos(old, curwin->w_cursor)) {
+ if (i == 1 && !wrapped && equalpos(old, curwin->w_cursor)) {
// Avoid getting stuck on the current cursor position, which can happen when
// an offset is given and the cursor is on the last char in the buffer:
// Repeat with count + 1.
cap->count1 += 1;
- (void)normal_search(cap, 0, NULL, SEARCH_MARK | cap->arg);
+ (void)normal_search(cap, 0, NULL, SEARCH_MARK | cap->arg, NULL);
cap->count1 -= 1;
}
}
@@ -5386,18 +5412,24 @@ static int normal_search(
cmdarg_T *cap,
int dir,
char_u *pat,
- int opt /* extra flags for do_search() */
+ int opt, // extra flags for do_search()
+ int *wrapped
)
{
int i;
+ searchit_arg_T sia;
cap->oap->motion_type = kMTCharWise;
cap->oap->inclusive = false;
cap->oap->use_reg_one = true;
curwin->w_set_curswant = true;
+ memset(&sia, 0, sizeof(sia));
i = do_search(cap->oap, dir, pat, cap->count1,
- opt | SEARCH_OPT | SEARCH_ECHO | SEARCH_MSG, NULL, NULL);
+ opt | SEARCH_OPT | SEARCH_ECHO | SEARCH_MSG, &sia);
+ if (wrapped != NULL) {
+ *wrapped = sia.sa_wrapped;
+ }
if (i == 0) {
clearop(cap->oap);
} else {
@@ -5830,6 +5862,10 @@ static void nv_undo(cmdarg_T *cap)
static void nv_kundo(cmdarg_T *cap)
{
if (!checkclearopq(cap->oap)) {
+ if (bt_prompt(curbuf)) {
+ clearopbeep(cap->oap);
+ return;
+ }
u_undo((int)cap->count1);
curwin->w_set_curswant = true;
}
@@ -5842,10 +5878,14 @@ static void nv_replace(cmdarg_T *cap)
{
char_u *ptr;
int had_ctrl_v;
- long n;
- if (checkclearop(cap->oap))
+ if (checkclearop(cap->oap)) {
return;
+ }
+ if (bt_prompt(curbuf) && !prompt_curpos_editable()) {
+ clearopbeep(cap->oap);
+ return;
+ }
/* get another character */
if (cap->nchar == Ctrl_V) {
@@ -5896,7 +5936,7 @@ static void nv_replace(cmdarg_T *cap)
/* Abort if not enough characters to replace. */
ptr = get_cursor_pos_ptr();
if (STRLEN(ptr) < (unsigned)cap->count1
- || (has_mbyte && mb_charlen(ptr) < cap->count1)
+ || (mb_charlen(ptr) < cap->count1)
) {
clearopbeep(cap->oap);
return;
@@ -5938,71 +5978,44 @@ static void nv_replace(cmdarg_T *cap)
NUL, 'r', NUL, had_ctrl_v, cap->nchar);
curbuf->b_op_start = curwin->w_cursor;
- if (has_mbyte) {
- int old_State = State;
-
- if (cap->ncharC1 != 0)
- AppendCharToRedobuff(cap->ncharC1);
- if (cap->ncharC2 != 0)
- AppendCharToRedobuff(cap->ncharC2);
-
- /* 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 (n = cap->count1; n > 0; --n) {
- State = REPLACE;
- if (cap->nchar == Ctrl_E || cap->nchar == Ctrl_Y) {
- int c = ins_copychar(curwin->w_cursor.lnum
- + (cap->nchar == Ctrl_Y ? -1 : 1));
- if (c != NUL)
- ins_char(c);
- else
- /* will be decremented further down */
- ++curwin->w_cursor.col;
- } else
- ins_char(cap->nchar);
- State = old_State;
- if (cap->ncharC1 != 0)
- ins_char(cap->ncharC1);
- if (cap->ncharC2 != 0)
- ins_char(cap->ncharC2);
- }
- } else {
- /*
- * Replace the characters within one line.
- */
- for (n = cap->count1; n > 0; --n) {
- /*
- * Get ptr again, because u_save and/or showmatch() will have
- * released the line. At the same time we let know that the
- * line will be changed.
- */
- ptr = ml_get_buf(curbuf, curwin->w_cursor.lnum, true);
- if (cap->nchar == Ctrl_E || cap->nchar == Ctrl_Y) {
- int c = ins_copychar(curwin->w_cursor.lnum
- + (cap->nchar == Ctrl_Y ? -1 : 1));
- if (c != NUL) {
- assert(c >= 0 && c <= UCHAR_MAX);
- ptr[curwin->w_cursor.col] = (char_u)c;
- }
+ const int old_State = State;
+
+ if (cap->ncharC1 != 0) {
+ AppendCharToRedobuff(cap->ncharC1);
+ }
+ if (cap->ncharC2 != 0) {
+ AppendCharToRedobuff(cap->ncharC2);
+ }
+
+ // 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--) {
+ State = REPLACE;
+ if (cap->nchar == Ctrl_E || cap->nchar == Ctrl_Y) {
+ int c = ins_copychar(curwin->w_cursor.lnum
+ + (cap->nchar == Ctrl_Y ? -1 : 1));
+ if (c != NUL) {
+ ins_char(c);
} else {
- assert(cap->nchar >= 0 && cap->nchar <= UCHAR_MAX);
- ptr[curwin->w_cursor.col] = (char_u)cap->nchar;
+ // will be decremented further down
+ curwin->w_cursor.col++;
}
- if (p_sm && msg_silent == 0)
- showmatch(cap->nchar);
- ++curwin->w_cursor.col;
+ } else {
+ ins_char(cap->nchar);
+ }
+ State = old_State;
+ if (cap->ncharC1 != 0) {
+ ins_char(cap->ncharC1);
+ }
+ if (cap->ncharC2 != 0) {
+ ins_char(cap->ncharC2);
}
-
- /* mark the buffer as changed and prepare for displaying */
- changed_bytes(curwin->w_cursor.lnum,
- (colnr_T)(curwin->w_cursor.col - cap->count1));
}
--curwin->w_cursor.col; /* cursor on the last replaced char */
/* if the character on the left of the current cursor is a multi-byte
* character, move two characters left */
- if (has_mbyte)
- mb_adjust_cursor();
+ mb_adjust_cursor();
curbuf->b_op_end = curwin->w_cursor;
curwin->w_set_curswant = true;
set_last_insert(cap->nchar);
@@ -6209,7 +6222,11 @@ static void v_visop(cmdarg_T *cap)
*/
static void nv_subst(cmdarg_T *cap)
{
- if (VIsual_active) { /* "vs" and "vS" are the same as "vc" */
+ if (bt_prompt(curbuf) && !prompt_curpos_editable()) {
+ clearopbeep(cap->oap);
+ return;
+ }
+ if (VIsual_active) { // "vs" and "vS" are the same as "vc"
if (cap->cmdchar == 'S') {
VIsual_mode_orig = VIsual_mode;
VIsual_mode = 'V';
@@ -6292,9 +6309,7 @@ static void nv_gomark(cmdarg_T *cap)
}
}
-/*
- * Handle CTRL-O, CTRL-I, "g;" and "g," commands.
- */
+// Handle CTRL-O, CTRL-I, "g;", "g,", and "CTRL-Tab" commands.
static void nv_pcmark(cmdarg_T *cap)
{
pos_T *pos;
@@ -6302,11 +6317,16 @@ static void nv_pcmark(cmdarg_T *cap)
const bool old_KeyTyped = KeyTyped; // getting file may reset it
if (!checkclearopq(cap->oap)) {
- if (cap->cmdchar == 'g')
+ if (cap->cmdchar == TAB && mod_mask == MOD_MASK_CTRL) {
+ goto_tabpage_lastused();
+ return;
+ }
+ if (cap->cmdchar == 'g') {
pos = movechangelist((int)cap->count1);
- else
+ } else {
pos = movemark((int)cap->count1);
- if (pos == (pos_T *)-1) { /* jump to other file */
+ }
+ if (pos == (pos_T *)-1) { // jump to other file
curwin->w_set_curswant = true;
check_cursor();
} else if (pos != NULL) /* can jump */
@@ -6356,8 +6376,8 @@ static void nv_visual(cmdarg_T *cap)
if (cap->cmdchar == Ctrl_Q)
cap->cmdchar = Ctrl_V;
- /* 'v', 'V' and CTRL-V can be used while an operator is pending to make it
- * characterwise, linewise, or blockwise. */
+ // 'v', 'V' and CTRL-V can be used while an operator is pending to make it
+ // charwise, linewise, or blockwise.
if (cap->oap->op_type != OP_NOP) {
motion_force = cap->oap->motion_force = cap->cmdchar;
finish_op = false; // operator doesn't finish now but later
@@ -6733,6 +6753,22 @@ static void nv_g_cmd(cmdarg_T *cap)
curwin->w_set_curswant = true;
break;
+ case 'M':
+ {
+ const char_u *const ptr = get_cursor_line_ptr();
+
+ oap->motion_type = kMTCharWise;
+ oap->inclusive = false;
+ i = (int)mb_string2cells_len(ptr, STRLEN(ptr));
+ if (cap->count0 > 0 && cap->count0 <= 100) {
+ coladvance((colnr_T)(i * cap->count0 / 100));
+ } else {
+ coladvance((colnr_T)(i / 2));
+ }
+ curwin->w_set_curswant = true;
+ }
+ break;
+
case '_':
/* "g_": to the last non-blank character in the line or <count> lines
* downward. */
@@ -6797,10 +6833,14 @@ static void nv_g_cmd(cmdarg_T *cap)
} else if (nv_screengo(oap, FORWARD, cap->count1 - 1) == false)
clearopbeep(oap);
} else {
+ if (cap->count1 > 1) {
+ // if it fails, let the cursor still move to the last char
+ cursor_down(cap->count1 - 1, false);
+ }
i = curwin->w_leftcol + curwin->w_width_inner - col_off - 1;
coladvance((colnr_T)i);
- /* Make sure we stick in this column. */
+ // Make sure we stick in this column.
validate_virtcol();
curwin->w_curswant = curwin->w_virtcol;
curwin->w_set_curswant = false;
@@ -7019,6 +7059,11 @@ static void nv_g_cmd(cmdarg_T *cap)
if (!checkclearop(oap))
goto_tabpage(-(int)cap->count1);
break;
+ case TAB:
+ if (!checkclearop(oap)) {
+ goto_tabpage_lastused();
+ }
+ break;
case '+':
case '-': /* "g+" and "g-": undo or redo along the timeline */
@@ -7119,10 +7164,15 @@ static void nv_tilde(cmdarg_T *cap)
{
if (!p_to
&& !VIsual_active
- && cap->oap->op_type != OP_TILDE)
+ && cap->oap->op_type != OP_TILDE) {
+ if (bt_prompt(curbuf) && !prompt_curpos_editable()) {
+ clearopbeep(cap->oap);
+ return;
+ }
n_swapchar(cap);
- else
+ } else {
nv_operator(cap);
+ }
}
/*
@@ -7135,6 +7185,12 @@ static void nv_operator(cmdarg_T *cap)
op_type = get_op_type(cap->cmdchar, cap->nchar);
+ if (bt_prompt(curbuf) && op_is_change(op_type)
+ && !prompt_curpos_editable()) {
+ clearopbeep(cap->oap);
+ return;
+ }
+
if (op_type == cap->oap->op_type) /* double operator works on lines */
nv_lineop(cap);
else if (!checkclearop(cap->oap)) {
@@ -7320,10 +7376,9 @@ static void adjust_cursor(oparg_T *oap)
&& (!VIsual_active || *p_sel == 'o')
&& !virtual_active() && (ve_flags & VE_ONEMORE) == 0
) {
- --curwin->w_cursor.col;
- /* prevent cursor from moving on the trail byte */
- if (has_mbyte)
- mb_adjust_cursor();
+ curwin->w_cursor.col--;
+ // prevent cursor from moving on the trail byte
+ mb_adjust_cursor();
oap->inclusive = true;
}
}
@@ -7350,10 +7405,7 @@ static void adjust_for_sel(cmdarg_T *cap)
{
if (VIsual_active && cap->oap->inclusive && *p_sel == 'e'
&& gchar_cursor() != NUL && lt(VIsual, curwin->w_cursor)) {
- if (has_mbyte)
- inc_cursor();
- else
- ++curwin->w_cursor.col;
+ inc_cursor();
cap->oap->inclusive = false;
}
}
@@ -7799,8 +7851,11 @@ static void nv_put_opt(cmdarg_T *cap, bool fix_indent)
clearop(cap->oap);
assert(cap->opcount >= 0);
nv_diffgetput(true, (size_t)cap->opcount);
- } else
+ } else {
clearopbeep(cap->oap);
+ }
+ } else if (bt_prompt(curbuf) && !prompt_curpos_editable()) {
+ clearopbeep(cap->oap);
} else {
if (fix_indent) {
dir = (cap->cmdchar == ']' && cap->nchar == 'p')
@@ -7812,8 +7867,9 @@ static void nv_put_opt(cmdarg_T *cap, bool fix_indent)
? BACKWARD : FORWARD;
}
prep_redo_cmd(cap);
- if (cap->cmdchar == 'g')
+ if (cap->cmdchar == 'g') {
flags |= PUT_CURSEND;
+ }
if (VIsual_active) {
/* Putting in Visual mode: The put text replaces the selected
@@ -7851,15 +7907,17 @@ static void nv_put_opt(cmdarg_T *cap, bool fix_indent)
cap->oap->regname = regname;
}
- /* When deleted a linewise Visual area, put the register as
- * lines to avoid it joined with the next line. When deletion was
- * characterwise, split a line when putting lines. */
- if (VIsual_mode == 'V')
+ // When deleted a linewise Visual area, put the register as
+ // lines to avoid it joined with the next line. When deletion was
+ // charwise, split a line when putting lines.
+ if (VIsual_mode == 'V') {
flags |= PUT_LINE;
- else if (VIsual_mode == 'v')
+ } else if (VIsual_mode == 'v') {
flags |= PUT_LINE_SPLIT;
- if (VIsual_mode == Ctrl_V && dir == FORWARD)
+ }
+ if (VIsual_mode == Ctrl_V && dir == FORWARD) {
flags |= PUT_LINE_FORWARD;
+ }
dir = BACKWARD;
if ((VIsual_mode != 'V'
&& curwin->w_cursor.col < curbuf->b_op_start.col)
@@ -7917,10 +7975,14 @@ static void nv_open(cmdarg_T *cap)
clearop(cap->oap);
assert(cap->opcount >= 0);
nv_diffgetput(false, (size_t)cap->opcount);
- } else if (VIsual_active) /* switch start and end of visual */
+ } else if (VIsual_active) {
+ // switch start and end of visual/
v_swap_corners(cap->cmdchar);
- else
+ } else if (bt_prompt(curbuf)) {
+ clearopbeep(cap->oap);
+ } else {
n_opencmd(cap);
+ }
}
// Calculate start/end virtual columns for operating in block mode.
@@ -7941,9 +8003,7 @@ static void get_op_vcol(
oap->motion_type = kMTBlockWise;
// prevent from moving onto a trail byte
- if (has_mbyte) {
- mark_mb_adjustpos(curwin->w_buffer, &oap->end);
- }
+ mark_mb_adjustpos(curwin->w_buffer, &oap->end);
getvvcol(curwin, &(oap->start), &oap->start_vcol, NULL, &oap->end_vcol);
if (!redo_VIsual_busy) {
diff --git a/src/nvim/ops.c b/src/nvim/ops.c
index 4f0c1b5cb9..595a699563 100644
--- a/src/nvim/ops.c
+++ b/src/nvim/ops.c
@@ -31,6 +31,7 @@
#include "nvim/indent.h"
#include "nvim/log.h"
#include "nvim/mark.h"
+#include "nvim/extmark.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
@@ -49,6 +50,7 @@
#include "nvim/undo.h"
#include "nvim/macros.h"
#include "nvim/window.h"
+#include "nvim/lib/kvec.h"
#include "nvim/os/input.h"
#include "nvim/os/time.h"
@@ -87,6 +89,10 @@ struct block_def {
# include "ops.c.generated.h"
#endif
+// Flags for third item in "opchars".
+#define OPF_LINES 1 // operator always works on lines
+#define OPF_CHANGE 2 // operator changes text
+
/*
* The names of operators.
* IMPORTANT: Index must correspond with defines in vim.h!!!
@@ -94,36 +100,36 @@ struct block_def {
*/
static char opchars[][3] =
{
- { NUL, NUL, false }, // OP_NOP
- { 'd', NUL, false }, // OP_DELETE
- { 'y', NUL, false }, // OP_YANK
- { 'c', NUL, false }, // OP_CHANGE
- { '<', NUL, true }, // OP_LSHIFT
- { '>', NUL, true }, // OP_RSHIFT
- { '!', NUL, true }, // OP_FILTER
- { 'g', '~', false }, // OP_TILDE
- { '=', NUL, true }, // OP_INDENT
- { 'g', 'q', true }, // OP_FORMAT
- { ':', NUL, true }, // OP_COLON
- { 'g', 'U', false }, // OP_UPPER
- { 'g', 'u', false }, // OP_LOWER
- { 'J', NUL, true }, // DO_JOIN
- { 'g', 'J', true }, // DO_JOIN_NS
- { 'g', '?', false }, // OP_ROT13
- { 'r', NUL, false }, // OP_REPLACE
- { 'I', NUL, false }, // OP_INSERT
- { 'A', NUL, false }, // OP_APPEND
- { 'z', 'f', true }, // OP_FOLD
- { 'z', 'o', true }, // OP_FOLDOPEN
- { 'z', 'O', true }, // OP_FOLDOPENREC
- { 'z', 'c', true }, // OP_FOLDCLOSE
- { 'z', 'C', true }, // OP_FOLDCLOSEREC
- { 'z', 'd', true }, // OP_FOLDDEL
- { 'z', 'D', true }, // OP_FOLDDELREC
- { 'g', 'w', true }, // OP_FORMAT2
- { 'g', '@', false }, // OP_FUNCTION
- { Ctrl_A, NUL, false }, // OP_NR_ADD
- { Ctrl_X, NUL, false }, // OP_NR_SUB
+ { NUL, NUL, 0 }, // OP_NOP
+ { 'd', NUL, OPF_CHANGE }, // OP_DELETE
+ { 'y', NUL, 0 }, // OP_YANK
+ { 'c', NUL, OPF_CHANGE }, // OP_CHANGE
+ { '<', NUL, OPF_LINES | OPF_CHANGE }, // OP_LSHIFT
+ { '>', NUL, OPF_LINES | OPF_CHANGE }, // OP_RSHIFT
+ { '!', NUL, OPF_LINES | OPF_CHANGE }, // OP_FILTER
+ { 'g', '~', OPF_CHANGE }, // OP_TILDE
+ { '=', NUL, OPF_LINES | OPF_CHANGE }, // OP_INDENT
+ { 'g', 'q', OPF_LINES | OPF_CHANGE }, // OP_FORMAT
+ { ':', NUL, OPF_LINES }, // OP_COLON
+ { 'g', 'U', OPF_CHANGE }, // OP_UPPER
+ { 'g', 'u', OPF_CHANGE }, // OP_LOWER
+ { 'J', NUL, OPF_LINES | OPF_CHANGE }, // DO_JOIN
+ { 'g', 'J', OPF_LINES | OPF_CHANGE }, // DO_JOIN_NS
+ { 'g', '?', OPF_CHANGE }, // OP_ROT13
+ { 'r', NUL, OPF_CHANGE }, // OP_REPLACE
+ { 'I', NUL, OPF_CHANGE }, // OP_INSERT
+ { 'A', NUL, OPF_CHANGE }, // OP_APPEND
+ { 'z', 'f', OPF_LINES }, // OP_FOLD
+ { 'z', 'o', OPF_LINES }, // OP_FOLDOPEN
+ { 'z', 'O', OPF_LINES }, // OP_FOLDOPENREC
+ { 'z', 'c', OPF_LINES }, // OP_FOLDCLOSE
+ { 'z', 'C', OPF_LINES }, // OP_FOLDCLOSEREC
+ { 'z', 'd', OPF_LINES }, // OP_FOLDDEL
+ { 'z', 'D', OPF_LINES }, // OP_FOLDDELREC
+ { 'g', 'w', OPF_LINES | OPF_CHANGE }, // OP_FORMAT2
+ { 'g', '@', OPF_CHANGE }, // OP_FUNCTION
+ { Ctrl_A, NUL, OPF_CHANGE }, // OP_NR_ADD
+ { Ctrl_X, NUL, OPF_CHANGE }, // OP_NR_SUB
};
/*
@@ -167,7 +173,13 @@ int get_op_type(int char1, int char2)
*/
int op_on_lines(int op)
{
- return opchars[op][2];
+ return opchars[op][2] & OPF_LINES;
+}
+
+// Return TRUE if operator "op" changes text.
+int op_is_change(int op)
+{
+ return opchars[op][2] & OPF_CHANGE;
}
/*
@@ -219,8 +231,6 @@ void op_shift(oparg_T *oap, int curs_top, int amount)
++curwin->w_cursor.lnum;
}
- changed_lines(oap->start.lnum, 0, oap->end.lnum + 1, 0L, true);
-
if (oap->motion_type == kMTBlockWise) {
curwin->w_cursor.lnum = oap->start.lnum;
curwin->w_cursor.col = block_col;
@@ -251,7 +261,7 @@ void op_shift(oparg_T *oap, int curs_top, int amount)
sprintf((char *)IObuff, _("%" PRId64 " lines %sed %d times"),
(int64_t)oap->line_count, s, amount);
}
- msg(IObuff);
+ msg_attr_keep(IObuff, 0, true, false);
}
/*
@@ -260,8 +270,11 @@ void op_shift(oparg_T *oap, int curs_top, int amount)
curbuf->b_op_start = oap->start;
curbuf->b_op_end.lnum = oap->end.lnum;
curbuf->b_op_end.col = (colnr_T)STRLEN(ml_get(oap->end.lnum));
- if (curbuf->b_op_end.col > 0)
- --curbuf->b_op_end.col;
+ if (curbuf->b_op_end.col > 0) {
+ curbuf->b_op_end.col--;
+ }
+
+ changed_lines(oap->start.lnum, 0, oap->end.lnum + 1, 0L, true);
}
// Shift the current line one shiftwidth left (if left != 0) or right
@@ -270,20 +283,21 @@ void shift_line(
int left,
int round,
int amount,
- int call_changed_bytes /* call changed_bytes() */
+ int call_changed_bytes // call changed_bytes()
)
{
int count;
int i, j;
int p_sw = get_sw_value(curbuf);
- count = get_indent(); /* get current indent */
+ count = get_indent(); // get current indent
- if (round) { /* round off indent */
- i = count / p_sw; /* number of p_sw rounded down */
- j = count % p_sw; /* extra spaces */
- if (j && left) /* first remove extra spaces */
- --amount;
+ if (round) { // round off indent
+ i = count / p_sw; // number of p_sw rounded down
+ j = count % p_sw; // extra spaces
+ if (j && left) { // first remove extra spaces
+ amount--;
+ }
if (left) {
i -= amount;
if (i < 0)
@@ -291,7 +305,7 @@ void shift_line(
} else
i += amount;
count = i * p_sw;
- } else { /* original vi indent */
+ } else { // original vi indent
if (left) {
count -= p_sw * amount;
if (count < 0)
@@ -300,11 +314,12 @@ void shift_line(
count += p_sw * amount;
}
- /* Set new indent */
- if (State & VREPLACE_FLAG)
- change_indent(INDENT_SET, count, FALSE, NUL, call_changed_bytes);
- else
+ // Set new indent
+ if (State & VREPLACE_FLAG) {
+ change_indent(INDENT_SET, count, false, NUL, call_changed_bytes);
+ } else {
(void)set_indent(count, call_changed_bytes ? SIN_CHANGED : 0);
+ }
}
/*
@@ -340,6 +355,8 @@ static void shift_block(oparg_T *oap, int amount)
char_u *const oldp = get_cursor_line_ptr();
+ int startcol, oldlen, newlen;
+
if (!left) {
/*
* 1. Get start vcol
@@ -349,16 +366,13 @@ static void shift_block(oparg_T *oap, int amount)
*/
total += bd.pre_whitesp; // all virtual WS up to & incl a split TAB
colnr_T ws_vcol = bd.start_vcol - bd.pre_whitesp;
+ char_u * old_textstart = bd.textstart;
if (bd.startspaces) {
- if (has_mbyte) {
- if ((*mb_ptr2len)(bd.textstart) == 1) {
- bd.textstart++;
- } else {
- ws_vcol = 0;
- bd.startspaces = 0;
- }
- } else {
+ if (utfc_ptr2len(bd.textstart) == 1) {
bd.textstart++;
+ } else {
+ ws_vcol = 0;
+ bd.startspaces = 0;
}
}
for (; ascii_iswhite(*bd.textstart); ) {
@@ -375,14 +389,19 @@ static void shift_block(oparg_T *oap, int amount)
j = ((ws_vcol % p_ts) + total) % p_ts; /* number of spp */
else
j = total;
- /* if we're splitting a TAB, allow for it */
- bd.textcol -= bd.pre_whitesp_c - (bd.startspaces != 0);
+
+ // if we're splitting a TAB, allow for it
+ 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 = (char_u *)xmalloc((size_t)col);
memset(newp, NUL, (size_t)col);
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 */
@@ -466,7 +485,10 @@ static void shift_block(oparg_T *oap, int amount)
// - the rest of the line, pointed to by non_white.
new_line_len = verbatim_diff + fill + STRLEN(non_white) + 1;
- newp = (char_u *) xmalloc(new_line_len);
+ newp = (char_u *)xmalloc(new_line_len);
+ 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);
STRMOVE(newp + verbatim_diff + fill, non_white);
@@ -474,6 +496,9 @@ static void shift_block(oparg_T *oap, int amount)
// replace the line
ml_replace(curwin->w_cursor.lnum, newp, false);
changed_bytes(curwin->w_cursor.lnum, (colnr_T)bd.textcol);
+ extmark_splice(curbuf, (int)curwin->w_cursor.lnum-1, startcol,
+ 0, oldlen, 0, newlen,
+ kExtmarkUndo);
State = oldstate;
curwin->w_cursor.col = oldcol;
p_ri = old_p_ri;
@@ -545,6 +570,7 @@ static void block_insert(oparg_T *oap, char_u *s, int b_insert, struct block_def
// copy up to shifted part
memmove(newp, oldp, (size_t)offset);
oldp += offset;
+ int startcol = offset;
// insert pre-padding
memset(newp + offset, ' ', (size_t)spaces);
@@ -553,6 +579,7 @@ static void block_insert(oparg_T *oap, char_u *s, int b_insert, struct block_def
memmove(newp + offset + spaces, s, s_len);
offset += (int)s_len;
+ int skipped = 0;
if (spaces && !bdp->is_short) {
// insert post-padding
memset(newp + offset + spaces, ' ', (size_t)(p_ts - spaces));
@@ -560,6 +587,7 @@ static void block_insert(oparg_T *oap, char_u *s, int b_insert, struct block_def
oldp++;
// We allowed for that TAB, remember this now
count++;
+ skipped = 1;
}
if (spaces > 0)
@@ -567,6 +595,9 @@ static void block_insert(oparg_T *oap, char_u *s, int b_insert, struct block_def
STRMOVE(newp + offset, oldp);
ml_replace(lnum, newp, false);
+ extmark_splice(curbuf, (int)lnum-1, startcol,
+ 0, skipped,
+ 0, offset-startcol, kExtmarkUndo);
if (lnum == oap->end.lnum) {
/* Set "']" mark to the end of the block instead of the end of
@@ -621,9 +652,10 @@ void op_reindent(oparg_T *oap, Indenter how)
amount = how(); /* get the indent for this line */
if (amount >= 0 && set_indent(amount, SIN_UNDO)) {
- /* did change the indent, call changed_lines() later */
- if (first_changed == 0)
+ // did change the indent, call changed_lines() later
+ if (first_changed == 0) {
first_changed = curwin->w_cursor.lnum;
+ }
last_changed = curwin->w_cursor.lnum;
}
}
@@ -671,13 +703,15 @@ int get_expr_register(void)
{
char_u *new_line;
- new_line = getcmdline('=', 0L, 0);
- if (new_line == NULL)
+ new_line = getcmdline('=', 0L, 0, true);
+ if (new_line == NULL) {
return NUL;
- if (*new_line == NUL) /* use previous line */
+ }
+ if (*new_line == NUL) { // use previous line
xfree(new_line);
- else
+ } else {
set_expr_line(new_line);
+ }
return '=';
}
@@ -808,6 +842,15 @@ static bool is_append_register(int regname)
return ASCII_ISUPPER(regname);
}
+/// @see get_yank_register
+/// @returns true when register should be inserted literally
+/// (selection or clipboard)
+static inline bool is_literal_register(int regname)
+ FUNC_ATTR_CONST
+{
+ return regname == '*' || regname == '+';
+}
+
/// Returns a copy of contents in register `name`
/// for use in do_put. Should be freed by caller.
yankreg_T *copy_register(int name)
@@ -1118,11 +1161,12 @@ static int put_in_typebuf(
*/
int insert_reg(
int regname,
- int literally /* insert literally, not as if typed */
+ bool literally_arg // insert literally, not as if typed
)
{
int retval = OK;
bool allocated;
+ const bool literally = literally_arg || is_literal_register(regname);
/*
* It is possible to get into an endless loop by having CTRL-R a in
@@ -1188,9 +1232,7 @@ static void stuffescaped(const char *arg, int literally)
/* stuff a single special character */
if (*arg != NUL) {
- const int c = (has_mbyte
- ? mb_cptr2char_adv((const char_u **)&arg)
- : (uint8_t)(*arg++));
+ const int c = mb_cptr2char_adv((const char_u **)&arg);
if (literally && ((c < ' ' && c != TAB) || c == DEL)) {
stuffcharReadbuff(Ctrl_V);
}
@@ -1294,12 +1336,14 @@ bool get_spec_reg(
/// register contents will be interpreted as commands.
///
/// @param regname Register name.
-/// @param literally Insert text literally instead of "as typed".
+/// @param literally_arg Insert text literally instead of "as typed".
/// @param remcr When true, don't add CR characters.
///
/// @returns FAIL for failure, OK otherwise
-bool cmdline_paste_reg(int regname, bool literally, bool remcr)
+bool cmdline_paste_reg(int regname, bool literally_arg, bool remcr)
{
+ const bool literally = literally_arg || is_literal_register(regname);
+
yankreg_T *reg = get_yank_register(regname, YREG_PASTE);
if (reg->y_array == NULL)
return FAIL;
@@ -1345,7 +1389,7 @@ int op_delete(oparg_T *oap)
linenr_T lnum;
char_u *ptr;
char_u *newp, *oldp;
- struct block_def bd;
+ struct block_def bd = { 0 };
linenr_T old_lcount = curbuf->b_ml.ml_line_count;
if (curbuf->b_ml.ml_flags & ML_EMPTY) { // nothing to do
@@ -1362,8 +1406,7 @@ int op_delete(oparg_T *oap)
return FAIL;
}
- if (has_mbyte)
- mb_adjust_opend(oap);
+ mb_adjust_opend(oap);
/*
* Imitate the strange Vi behaviour: If the delete spans more than one
@@ -1490,6 +1533,11 @@ int op_delete(oparg_T *oap)
STRMOVE(newp + bd.textcol + bd.startspaces + bd.endspaces, oldp);
// replace the line
ml_replace(lnum, newp, false);
+
+ extmark_splice(curbuf, (int)lnum-1, bd.textcol,
+ 0, bd.textlen,
+ 0, bd.startspaces+bd.endspaces,
+ kExtmarkUndo);
}
check_cursor_col();
@@ -1558,6 +1606,7 @@ int op_delete(oparg_T *oap)
oap->end = curwin->w_cursor;
curwin->w_cursor = oap->start;
}
+ mb_adjust_opend(oap);
}
if (oap->line_count == 1) { /* delete characters within one line */
@@ -1605,6 +1654,8 @@ int op_delete(oparg_T *oap)
(linenr_T)(curwin->w_cursor.lnum + oap->line_count)) == FAIL)
return FAIL;
+ curbuf_splice_pending++;
+ pos_T startpos = curwin->w_cursor; // start position for delete
truncate_line(true); // delete from cursor to end of line
curpos = curwin->w_cursor; // remember curwin->w_cursor
@@ -1618,6 +1669,9 @@ int op_delete(oparg_T *oap)
oap->op_type == OP_DELETE && !oap->is_VIsual);
curwin->w_cursor = curpos; // restore curwin->w_cursor
(void)do_join(2, false, false, false, false);
+ curbuf_splice_pending--;
+ extmark_splice(curbuf, (int)startpos.lnum-1, startpos.col,
+ (int)oap->line_count-1, n, 0, 0, kExtmarkUndo);
}
}
@@ -1627,8 +1681,9 @@ setmarks:
if (oap->motion_type == kMTBlockWise) {
curbuf->b_op_end.lnum = oap->end.lnum;
curbuf->b_op_end.col = oap->start.col;
- } else
+ } else {
curbuf->b_op_end = oap->start;
+ }
curbuf->b_op_start = oap->start;
return OK;
@@ -1653,8 +1708,11 @@ 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_u)c;
+ assert(c <= UCHAR_MAX);
+ *(ml_get_buf(curbuf, lp.lnum, true) + lp.col) = (char_u)c;
+ if (!curbuf_splice_pending) {
+ extmark_splice(curbuf, (int)lp.lnum-1, lp.col, 0, 1, 0, 1, kExtmarkUndo);
+ }
}
// Replace the character under the cursor with "c".
@@ -1694,8 +1752,7 @@ int op_replace(oparg_T *oap, int c)
c = NL;
}
- if (has_mbyte)
- mb_adjust_opend(oap);
+ mb_adjust_opend(oap);
if (u_save((linenr_T)(oap->start.lnum - 1),
(linenr_T)(oap->end.lnum + 1)) == FAIL)
@@ -1775,6 +1832,7 @@ int op_replace(oparg_T *oap, int c)
size_t after_p_len = 0;
int col = oldlen - bd.textcol - bd.textlen + 1;
assert(col >= 0);
+ int newrows = 0, newcols = 0;
if (had_ctrl_v_cr || (c != '\r' && c != '\n')) {
// strlen(newp) at this point
int newp_len = bd.textcol + bd.startspaces;
@@ -1787,21 +1845,27 @@ int op_replace(oparg_T *oap, int c)
newp_len += bd.endspaces;
// copy the part after the changed part
memmove(newp + newp_len, oldp, (size_t)col);
- }
+ }
+ newcols = newp_len - bd.textcol;
} else {
// Replacing with \r or \n means splitting the line.
after_p_len = (size_t)col;
after_p = (char_u *)xmalloc(after_p_len);
memmove(after_p, oldp, after_p_len);
+ newrows = 1;
}
// replace the line
ml_replace(curwin->w_cursor.lnum, newp, false);
+ 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);
oap->end.lnum++;
xfree(after_p);
}
+ extmark_splice(curbuf, (int)baselnum-1, bd.textcol,
+ 0, bd.textlen,
+ newrows, newcols, kExtmarkUndo);
}
} else {
// Characterwise or linewise motion replace.
@@ -1814,6 +1878,8 @@ int op_replace(oparg_T *oap, int c)
} else if (!oap->inclusive)
dec(&(oap->end));
+ // TODO(bfredl): we could batch all the splicing
+ // done on the same line, at least
while (ltoreq(curwin->w_cursor, oap->end)) {
n = gchar_cursor();
if (n != NUL) {
@@ -1961,17 +2027,16 @@ void op_tilde(oparg_T *oap)
* Returns TRUE if some character was changed.
*/
static int swapchars(int op_type, pos_T *pos, int length)
+ FUNC_ATTR_NONNULL_ALL
{
- int todo;
int did_change = 0;
- for (todo = length; todo > 0; --todo) {
- if (has_mbyte) {
- int len = (*mb_ptr2len)(ml_get_pos(pos));
+ for (int todo = length; todo > 0; todo--) {
+ const int len = utfc_ptr2len(ml_get_pos(pos));
- /* we're counting bytes, not characters */
- if (len > 0)
- todo -= len - 1;
+ // we're counting bytes, not characters
+ if (len > 0) {
+ todo -= len - 1;
}
did_change |= swapchar(op_type, pos);
if (inc(pos) == -1) /* at end of file */
@@ -2026,8 +2091,8 @@ bool swapchar(int op_type, pos_T *pos)
pos_T sp = curwin->w_cursor;
curwin->w_cursor = *pos;
- /* don't use del_char(), it also removes composing chars */
- del_bytes(utf_ptr2len(get_cursor_pos_ptr()), FALSE, FALSE);
+ // don't use del_char(), it also removes composing chars
+ del_bytes(utf_ptr2len(get_cursor_pos_ptr()), false, false);
ins_char(nc);
curwin->w_cursor = sp;
} else {
@@ -2100,8 +2165,9 @@ void op_insert(oparg_T *oap, long count1)
* values in "bd". */
if (u_save_cursor() == FAIL)
return;
- for (i = 0; i < bd.endspaces; i++)
+ for (i = 0; i < bd.endspaces; i++) {
ins_char(' ');
+ }
bd.textlen += bd.endspaces;
}
} else {
@@ -2333,6 +2399,9 @@ int op_change(oparg_T *oap)
oldp += bd.textcol;
STRMOVE(newp + offset, oldp);
ml_replace(linenr, newp, false);
+ extmark_splice(curbuf, (int)linenr-1, bd.textcol,
+ 0, 0,
+ 0, vpos.coladd+(int)ins_len, kExtmarkUndo);
}
}
check_cursor();
@@ -2477,7 +2546,7 @@ static void op_yank_reg(oparg_T *oap, bool message, yankreg_T *reg, bool append)
case kMTCharWise:
{
colnr_T startcol = 0, endcol = MAXCOL;
- int is_oneChar = FALSE;
+ int is_oneChar = false;
colnr_T cs, ce;
p = ml_get(lnum);
bd.startspaces = 0;
@@ -2508,8 +2577,8 @@ static void op_yank_reg(oparg_T *oap, bool message, yankreg_T *reg, bool append)
&& utf_head_off(p, p + endcol) == 0)) {
if (oap->start.lnum == oap->end.lnum
&& oap->start.col == oap->end.col) {
- /* Special case: inside a single char */
- is_oneChar = TRUE;
+ // Special case: inside a single char
+ is_oneChar = true;
bd.startspaces = oap->end.coladd
- oap->start.coladd + oap->inclusive;
endcol = startcol;
@@ -2671,14 +2740,18 @@ static void do_autocmd_textyankpost(oparg_T *oap, yankreg_T *reg)
tv_dict_add_str(dict, S_LEN("regname"), buf);
// Motion type: inclusive or exclusive.
- tv_dict_add_special(dict, S_LEN("inclusive"),
- oap->inclusive ? kSpecialVarTrue : kSpecialVarFalse);
+ tv_dict_add_bool(dict, S_LEN("inclusive"),
+ oap->inclusive ? kBoolVarTrue : kBoolVarFalse);
// Kind of operation: yank, delete, change).
buf[0] = (char)get_op_char(oap->op_type);
buf[1] = NUL;
tv_dict_add_str(dict, S_LEN("operator"), buf);
+ // Selection type: visual or not.
+ tv_dict_add_bool(dict, S_LEN("visual"),
+ oap->is_VIsual ? kBoolVarTrue : kBoolVarFalse);
+
tv_dict_set_keys_readonly(dict);
textlock++;
apply_autocmds(EVENT_TEXTYANKPOST, NULL, NULL, false, curbuf);
@@ -2688,7 +2761,6 @@ static void do_autocmd_textyankpost(oparg_T *oap, yankreg_T *reg)
recursive = false;
}
-
/*
* Put contents of register "regname" into the text.
* Caller must check "regname" to be valid!
@@ -2703,8 +2775,8 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
char_u *oldp;
int yanklen;
size_t totlen = 0; // init for gcc
- linenr_T lnum;
- colnr_T col;
+ linenr_T lnum = 0;
+ colnr_T col = 0;
size_t i; // index in y_array[]
MotionType y_type;
size_t y_size;
@@ -2998,7 +3070,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
getvcol(curwin, &curwin->w_cursor, NULL, NULL, &col);
// move to start of next multi-byte character
- curwin->w_cursor.col += (*mb_ptr2len)(get_cursor_pos_ptr());
+ curwin->w_cursor.col += utfc_ptr2len(get_cursor_pos_ptr());
col++;
} else {
getvcol(curwin, &curwin->w_cursor, &col, NULL, &endcol2);
@@ -3008,10 +3080,12 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
if (ve_flags == VE_ALL
&& (curwin->w_cursor.coladd > 0
|| endcol2 == curwin->w_cursor.col)) {
- if (dir == FORWARD && c == NUL)
- ++col;
- if (dir != FORWARD && c != NUL)
- ++curwin->w_cursor.col;
+ if (dir == FORWARD && c == NUL) {
+ col++;
+ }
+ if (dir != FORWARD && c != NUL && curwin->w_cursor.coladd > 0) {
+ curwin->w_cursor.col++;
+ }
if (c == TAB) {
if (dir == BACKWARD && curwin->w_cursor.col)
curwin->w_cursor.col--;
@@ -3108,6 +3182,10 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
assert(columns >= 0);
memmove(ptr, oldp + bd.textcol + delcount, (size_t)columns);
ml_replace(curwin->w_cursor.lnum, newp, false);
+ extmark_splice(curbuf, (int)curwin->w_cursor.lnum-1, bd.textcol,
+ 0, delcount,
+ 0, (int)totlen,
+ kExtmarkUndo);
++curwin->w_cursor.lnum;
if (i == 0)
@@ -3209,6 +3287,9 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
if (totlen && (restart_edit != 0 || (flags & PUT_CURSEND)))
++curwin->w_cursor.col;
changed_bytes(lnum, col);
+ extmark_splice(curbuf, (int)lnum-1, col,
+ 0, 0,
+ 0, (int)totlen, kExtmarkUndo);
} else {
// Insert at least one line. When y_type is kMTCharWise, break the first
// line in two.
@@ -3264,13 +3345,22 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
first_indent = FALSE;
} else if ((indent = get_indent() + indent_diff) < 0)
indent = 0;
- (void)set_indent(indent, 0);
+ (void)set_indent(indent, SIN_NOMARK);
curwin->w_cursor = old_pos;
/* remember how many chars were removed */
if (cnt == count && i == y_size - 1)
lendiff -= (int)STRLEN(ml_get(lnum));
}
}
+
+ if (y_type == kMTCharWise) {
+ extmark_splice(curbuf, (int)new_cursor.lnum-1, col, 0, 0,
+ (int)y_size-1, (int)STRLEN(y_array[y_size-1]),
+ kExtmarkUndo);
+ } else if (y_type == kMTLineWise && flags & PUT_LINE_SPLIT) {
+ extmark_splice(curbuf, (int)new_cursor.lnum-1, col, 0, 0,
+ (int)y_size+1, 0, kExtmarkUndo);
+ }
}
error:
@@ -3281,11 +3371,13 @@ error:
curbuf->b_op_start.lnum++;
}
// Skip mark_adjust when adding lines after the last one, there
- // can't be marks there. But still needed in diff mode.
+ // can't be marks there.
if (curbuf->b_op_start.lnum + (y_type == kMTCharWise) - 1 + nr_lines
- < curbuf->b_ml.ml_line_count || curwin->w_p_diff) {
+ < curbuf->b_ml.ml_line_count) {
+ 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, false);
+ (linenr_T)MAXLNUM, nr_lines, 0L, kind);
}
// note changed text for displaying and folding
@@ -3347,7 +3439,7 @@ end:
/* If the cursor is past the end of the line put it at the end. */
adjust_cursor_eol();
-}
+} // NOLINT(readability/fn_size)
/*
* When the cursor is on the NUL past the end of the line and it should not be
@@ -3543,7 +3635,7 @@ dis_msg(
while (*p != NUL
&& !(*p == ESC && skip_esc && *(p + 1) == NUL)
&& (n -= ptr2cells(p)) >= 0) {
- if (has_mbyte && (l = (*mb_ptr2len)(p)) > 1) {
+ if ((l = utfc_ptr2len(p)) > 1) {
msg_outtrans_len(p, l);
p += l;
} else
@@ -3689,7 +3781,10 @@ int do_join(size_t count,
if (insert_space && t > 0) {
curr = skipwhite(curr);
- if (*curr != ')' && currsize != 0 && endcurr1 != TAB
+ if (*curr != NUL
+ && *curr != ')'
+ && sumsize != 0
+ && endcurr1 != TAB
&& (!has_format_option(FO_MBYTE_JOIN)
|| (utf_ptr2char(curr) < 0x100 && endcurr1 < 0x100))
&& (!has_format_option(FO_MBYTE_JOIN2)
@@ -3706,6 +3801,13 @@ int do_join(size_t count,
}
}
}
+
+ if (t > 0 && curbuf_splice_pending == 0) {
+ extmark_splice(curbuf, (int)curwin->w_cursor.lnum-1, sumsize,
+ 1, (int)(curr- curr_start),
+ 0, spaces[t],
+ kExtmarkUndo);
+ }
currsize = (int)STRLEN(curr);
sumsize += currsize + spaces[t];
endcurr1 = endcurr2 = NUL;
@@ -3740,6 +3842,9 @@ int do_join(size_t count,
* column. This is not Vi compatible, but Vi deletes the marks, thus that
* should not really be a problem.
*/
+
+ curbuf_splice_pending++;
+
for (t = (linenr_T)count - 1;; t--) {
cend -= currsize;
memmove(cend, curr, (size_t)currsize);
@@ -3751,12 +3856,17 @@ int do_join(size_t count,
// If deleting more spaces than adding, the cursor moves no more than
// 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;
+ long lnum_amount = (linenr_T)-t;
+ long col_amount = (long)(cend - newp - spaces_removed);
+
+ mark_col_adjust(lnum, mincol, lnum_amount, col_amount, spaces_removed);
- mark_col_adjust(curwin->w_cursor.lnum + t, (colnr_T)0, (linenr_T)-t,
- (long)(cend - newp - spaces_removed), spaces_removed);
if (t == 0) {
break;
}
+
curr = curr_start = ml_get((linenr_T)(curwin->w_cursor.lnum + t - 1));
if (remove_comments)
curr += comments[t - 1];
@@ -3764,6 +3874,7 @@ int do_join(size_t count,
curr = skipwhite(curr);
currsize = (int)STRLEN(curr);
}
+
ml_replace(curwin->w_cursor.lnum, newp, false);
if (setmark) {
@@ -3786,6 +3897,7 @@ int do_join(size_t count,
curwin->w_cursor.lnum++;
del_lines((long)count - 1, false);
curwin->w_cursor.lnum = t;
+ curbuf_splice_pending--;
/*
* Set the cursor column:
@@ -4189,7 +4301,7 @@ format_lines(
int indent = (int)getwhitecols_curline();
if (indent > 0) {
- (void)del_bytes(indent, FALSE, FALSE);
+ (void)del_bytes(indent, false, false);
mark_col_adjust(curwin->w_cursor.lnum,
(colnr_T)0, 0L, (long)-indent, 0);
}
@@ -4279,15 +4391,13 @@ int paragraph_start(linenr_T lnum)
return TRUE; /* after empty line */
do_comments = has_format_option(FO_Q_COMS);
- if (fmt_check_par(lnum - 1
- , &leader_len, &leader_flags, do_comments
- ))
- return TRUE; /* after non-paragraph line */
+ if (fmt_check_par(lnum - 1, &leader_len, &leader_flags, do_comments)) {
+ return true; // after non-paragraph line
+ }
- if (fmt_check_par(lnum
- , &next_leader_len, &next_leader_flags, do_comments
- ))
- return TRUE; /* "lnum" is not a paragraph line */
+ if (fmt_check_par(lnum, &next_leader_len, &next_leader_flags, do_comments)) {
+ return true; // "lnum" is not a paragraph line
+ }
if (has_format_option(FO_WHITE_PAR) && !ends_in_white(lnum - 1))
return TRUE; /* missing trailing space in previous line. */
@@ -4324,14 +4434,17 @@ static void block_prep(oparg_T *oap, struct block_def *bdp, linenr_T lnum,
char_u *line;
char_u *prev_pstart;
char_u *prev_pend;
+ const int lbr_saved = curwin->w_p_lbr;
+ // Avoid a problem with unwanted linebreaks in block mode.
+ curwin->w_p_lbr = false;
bdp->startspaces = 0;
bdp->endspaces = 0;
bdp->textlen = 0;
bdp->start_vcol = 0;
bdp->end_vcol = 0;
- bdp->is_short = FALSE;
- bdp->is_oneChar = FALSE;
+ bdp->is_short = false;
+ bdp->is_oneChar = false;
bdp->pre_whitesp = 0;
bdp->pre_whitesp_c = 0;
bdp->end_char_vcols = 0;
@@ -4357,9 +4470,10 @@ static void block_prep(oparg_T *oap, struct block_def *bdp, linenr_T lnum,
bdp->start_char_vcols = incr;
if (bdp->start_vcol < oap->start_vcol) { /* line too short */
bdp->end_vcol = bdp->start_vcol;
- bdp->is_short = TRUE;
- if (!is_del || oap->op_type == OP_APPEND)
+ bdp->is_short = true;
+ if (!is_del || oap->op_type == OP_APPEND) {
bdp->endspaces = oap->end_vcol - oap->start_vcol + 1;
+ }
} else {
/* notice: this converts partly selected Multibyte characters to
* spaces, too. */
@@ -4368,11 +4482,11 @@ static void block_prep(oparg_T *oap, struct block_def *bdp, linenr_T lnum,
bdp->startspaces = bdp->start_char_vcols - bdp->startspaces;
pend = pstart;
bdp->end_vcol = bdp->start_vcol;
- if (bdp->end_vcol > oap->end_vcol) { /* it's all in one character */
- bdp->is_oneChar = TRUE;
- if (oap->op_type == OP_INSERT)
+ if (bdp->end_vcol > oap->end_vcol) { // it's all in one character
+ bdp->is_oneChar = true;
+ if (oap->op_type == OP_INSERT) {
bdp->endspaces = bdp->start_char_vcols - bdp->startspaces;
- else if (oap->op_type == OP_APPEND) {
+ } else if (oap->op_type == OP_APPEND) {
bdp->startspaces += oap->end_vcol - oap->start_vcol + 1;
bdp->endspaces = bdp->start_char_vcols - bdp->startspaces;
} else {
@@ -4397,17 +4511,16 @@ static void block_prep(oparg_T *oap, struct block_def *bdp, linenr_T lnum,
if (bdp->end_vcol <= oap->end_vcol
&& (!is_del
|| oap->op_type == OP_APPEND
- || oap->op_type == OP_REPLACE)) { /* line too short */
- bdp->is_short = TRUE;
- /* Alternative: include spaces to fill up the block.
- * Disadvantage: can lead to trailing spaces when the line is
- * short where the text is put */
- /* if (!is_del || oap->op_type == OP_APPEND) */
- if (oap->op_type == OP_APPEND || virtual_op)
+ || oap->op_type == OP_REPLACE)) { // line too short
+ bdp->is_short = true;
+ // Alternative: include spaces to fill up the block.
+ // Disadvantage: can lead to trailing spaces when the line is
+ // short where the text is put
+ // if (!is_del || oap->op_type == OP_APPEND)
+ if (oap->op_type == OP_APPEND || virtual_op) {
bdp->endspaces = oap->end_vcol - bdp->end_vcol
+ oap->inclusive;
- else
- bdp->endspaces = 0; /* replace doesn't add characters */
+ }
} else if (bdp->end_vcol > oap->end_vcol) {
bdp->endspaces = bdp->end_vcol - oap->end_vcol - 1;
if (!is_del && bdp->endspaces) {
@@ -4424,6 +4537,7 @@ static void block_prep(oparg_T *oap, struct block_def *bdp, linenr_T lnum,
}
bdp->textcol = (colnr_T) (pstart - line);
bdp->textstart = pstart;
+ curwin->w_p_lbr = lbr_saved;
}
/// Handle the add/subtract operator.
@@ -4536,7 +4650,7 @@ void op_addsub(oparg_T *oap, linenr_T Prenum1, bool g_cmd)
int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1)
{
int col;
- char_u *buf1;
+ char_u *buf1 = NULL;
char_u buf2[NUMBUFLEN];
int pre; // 'X' or 'x': hex; '0': octal; 'B' or 'b': bin
static bool hexupper = false; // 0xABC
@@ -4559,17 +4673,23 @@ int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1)
int maxlen = 0;
pos_T startpos;
pos_T endpos;
+ colnr_T save_coladd = 0;
dohex = (vim_strchr(curbuf->b_p_nf, 'x') != NULL); // "heX"
dooct = (vim_strchr(curbuf->b_p_nf, 'o') != NULL); // "Octal"
dobin = (vim_strchr(curbuf->b_p_nf, 'b') != NULL); // "Bin"
doalp = (vim_strchr(curbuf->b_p_nf, 'p') != NULL); // "alPha"
+ if (virtual_active()) {
+ save_coladd = pos->coladd;
+ pos->coladd = 0;
+ }
+
curwin->w_cursor = *pos;
ptr = ml_get(pos->lnum);
col = pos->col;
- if (*ptr == NUL) {
+ if (*ptr == NUL || col + !!save_coladd >= (int)STRLEN(ptr)) {
goto theend;
}
@@ -4641,7 +4761,7 @@ int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1)
if (visual) {
while (ptr[col] != NUL && length > 0 && !ascii_isdigit(ptr[col])
&& !(doalp && ASCII_ISALPHA(ptr[col]))) {
- int mb_len = MB_PTR2LEN(ptr + col);
+ int mb_len = utfc_ptr2len(ptr + col);
col += mb_len;
length -= mb_len;
@@ -4845,7 +4965,6 @@ int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1)
*ptr = NUL;
STRCAT(buf1, buf2);
ins_str(buf1); // insert the new number
- xfree(buf1);
endpos = curwin->w_cursor;
if (curwin->w_cursor.col) {
curwin->w_cursor.col--;
@@ -4860,10 +4979,13 @@ int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1)
}
theend:
+ xfree(buf1);
if (visual) {
curwin->w_cursor = save_cursor;
} else if (did_change) {
curwin->w_set_curswant = true;
+ } else if (virtual_active()) {
+ curwin->w_cursor.coladd = save_coladd;
}
return did_change;
@@ -5114,8 +5236,7 @@ void write_reg_contents_lst(int name, char_u **strings,
/// write_reg_contents_ex - store `str` in register `name`
///
-/// If `str` ends in '\n' or '\r', use linewise, otherwise use
-/// characterwise.
+/// If `str` ends in '\n' or '\r', use linewise, otherwise use charwise.
///
/// @warning when `name` is '/', `len` and `must_append` are ignored. This
/// means that `str` MUST be NUL-terminated.
@@ -5576,7 +5697,8 @@ void cursor_pos_info(dict_T *dict)
bom_count = bomb_size();
if (dict == NULL && bom_count > 0) {
- vim_snprintf((char *)IObuff + STRLEN(IObuff), IOSIZE - STRLEN(IObuff),
+ const size_t len = STRLEN(IObuff);
+ vim_snprintf((char *)IObuff + len, IOSIZE - len,
_("(+%" PRId64 " for BOM)"), (int64_t)bom_count);
}
if (dict == NULL) {
diff --git a/src/nvim/option.c b/src/nvim/option.c
index 3ccc67eb14..d789ad3587 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -66,6 +66,7 @@
#include "nvim/path.h"
#include "nvim/popupmnu.h"
#include "nvim/regexp.h"
+#include "nvim/ex_session.h"
#include "nvim/screen.h"
#include "nvim/spell.h"
#include "nvim/spellfile.h"
@@ -76,6 +77,9 @@
#include "nvim/undo.h"
#include "nvim/window.h"
#include "nvim/os/os.h"
+#ifdef WIN32
+# include "nvim/os/pty_conpty_win.h"
+#endif
#include "nvim/api/private/helpers.h"
#include "nvim/os/input.h"
#include "nvim/os/lang.h"
@@ -133,6 +137,7 @@ static char_u *p_cms;
static char_u *p_cpt;
static char_u *p_cfu;
static char_u *p_ofu;
+static char_u *p_tfu;
static int p_eol;
static int p_fixeol;
static int p_et;
@@ -294,7 +299,8 @@ static char *(p_scbopt_values[]) = { "ver", "hor", "jump", NULL };
static char *(p_debug_values[]) = { "msg", "throw", "beep", NULL };
static char *(p_ead_values[]) = { "both", "ver", "hor", NULL };
static char *(p_buftype_values[]) = { "nofile", "nowrite", "quickfix",
- "help", "acwrite", "terminal", NULL };
+ "help", "acwrite", "terminal",
+ "prompt", NULL };
static char *(p_bufhidden_values[]) = { "hide", "unload", "delete",
"wipe", NULL };
@@ -309,6 +315,9 @@ static char *(p_scl_values[]) = { "yes", "no", "auto", "auto:1", "auto:2",
"auto:3", "auto:4", "auto:5", "auto:6", "auto:7", "auto:8", "auto:9",
"yes:1", "yes:2", "yes:3", "yes:4", "yes:5", "yes:6", "yes:7", "yes:8",
"yes:9", NULL };
+static char *(p_fdc_values[]) = { "auto:1", "auto:2",
+ "auto:3", "auto:4", "auto:5", "auto:6", "auto:7", "auto:8", "auto:9",
+ "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", NULL };
/// All possible flags for 'shm'.
static char_u SHM_ALL[] = {
@@ -493,16 +502,41 @@ static inline char *add_dir(char *dest, const char *const dir,
return dest;
}
+char *get_lib_dir(void)
+{
+ // TODO(bfredl): too fragile? Ideally default_lib_dir would be made empty
+ // in an appimage build
+ if (strlen(default_lib_dir) != 0
+ && os_isdir((const char_u *)default_lib_dir)) {
+ return xstrdup(default_lib_dir);
+ }
+
+ // Find library path relative to the nvim binary: ../lib/nvim/
+ char exe_name[MAXPATHL];
+ vim_get_prefix_from_exepath(exe_name);
+ if (append_path(exe_name, "lib" _PATHSEPSTR "nvim", MAXPATHL) == OK) {
+ return xstrdup(exe_name);
+ }
+ return NULL;
+}
+
/// Sets &runtimepath to default value.
///
/// Windows: Uses "…/nvim-data" for kXDGDataHome to avoid storing
/// configuration and data files in the same path. #4403
-static void set_runtimepath_default(void)
+///
+/// If "clean_arg" is true, Nvim was started with --clean.
+static void set_runtimepath_default(bool clean_arg)
{
size_t rtp_size = 0;
- char *const data_home = stdpaths_get_xdg_var(kXDGDataHome);
- char *const config_home = stdpaths_get_xdg_var(kXDGConfigHome);
+ char *const data_home = clean_arg
+ ? NULL
+ : stdpaths_get_xdg_var(kXDGDataHome);
+ char *const config_home = clean_arg
+ ? 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);
char *const config_dirs = stdpaths_get_xdg_var(kXDGConfigDirs);
#define SITE_SIZE (sizeof("site") - 1)
@@ -510,6 +544,7 @@ static void set_runtimepath_default(void)
size_t data_len = 0;
size_t config_len = 0;
size_t vimruntime_len = 0;
+ size_t libdir_len = 0;
if (data_home != NULL) {
data_len = strlen(data_home);
if (data_len != 0) {
@@ -539,6 +574,12 @@ static void set_runtimepath_default(void)
rtp_size += vimruntime_len + memcnt(vimruntime, ',', vimruntime_len) + 1;
}
}
+ if (libdir != NULL) {
+ libdir_len = strlen(libdir);
+ if (libdir_len != 0) {
+ rtp_size += libdir_len + memcnt(libdir, ',', libdir_len) + 1;
+ }
+ }
rtp_size += compute_double_colon_len(data_dirs, NVIM_SIZE + 1 + SITE_SIZE + 1,
AFTER_SIZE + 1);
rtp_size += compute_double_colon_len(config_dirs, NVIM_SIZE + 1,
@@ -557,6 +598,7 @@ static void set_runtimepath_default(void)
true);
rtp_cur = add_dir(rtp_cur, vimruntime, vimruntime_len, kXDGNone,
NULL, 0, NULL, 0);
+ rtp_cur = add_dir(rtp_cur, libdir, libdir_len, kXDGNone, NULL, 0, NULL, 0);
rtp_cur = add_colon_dirs(rtp_cur, data_dirs, "site", SITE_SIZE,
"after", AFTER_SIZE, false);
rtp_cur = add_dir(rtp_cur, data_home, data_len, kXDGDataHome,
@@ -578,16 +620,16 @@ static void set_runtimepath_default(void)
xfree(data_home);
xfree(config_home);
xfree(vimruntime);
+ xfree(libdir);
}
#undef NVIM_SIZE
-/*
- * Initialize the options, first part.
- *
- * Called only once from main(), just after creating the first buffer.
- */
-void set_init_1(void)
+/// 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.
+void set_init_1(bool clean_arg)
{
int opt_idx;
@@ -730,7 +772,7 @@ void set_init_1(void)
true);
// Set default for &runtimepath. All necessary expansions are performed in
// this function.
- set_runtimepath_default();
+ set_runtimepath_default(clean_arg);
/*
* Set all the options (except the terminal options) to their default
@@ -831,10 +873,8 @@ void set_init_1(void)
set_helplang_default(get_mess_lang());
}
-/*
- * Set an option to its default value.
- * This does not take care of side effects!
- */
+/// Set an option to its default value.
+/// This does not take care of side effects!
static void
set_option_default(
int opt_idx,
@@ -867,11 +907,19 @@ set_option_default(
if (options[opt_idx].indir == PV_SCROLL) {
win_comp_scroll(curwin);
} else {
- *(long *)varp = (long)(intptr_t)options[opt_idx].def_val[dvi];
+ long def_val = (long)options[opt_idx].def_val[dvi];
+ if ((long *)varp == &curwin->w_p_so
+ || (long *)varp == &curwin->w_p_siso) {
+ // 'scrolloff' and 'sidescrolloff' local values have a
+ // different default value than the global default.
+ *(long *)varp = -1;
+ } else {
+ *(long *)varp = def_val;
+ }
// May also set global value for local option.
if (both) {
*(long *)get_varp_scope(&(options[opt_idx]), OPT_GLOBAL) =
- *(long *)varp;
+ def_val;
}
}
} else { // P_BOOL
@@ -897,9 +945,7 @@ set_option_default(
set_option_sctx_idx(opt_idx, opt_flags, current_sctx);
}
-/*
- * Set all options (except terminal options) to their default value.
- */
+/// Set all options (except terminal options) to their default value.
static void
set_options_default(
int opt_flags // OPT_FREE, OPT_LOCAL and/or OPT_GLOBAL
@@ -942,10 +988,8 @@ static void set_string_default(const char *name, char *val, bool allocated)
}
}
-/*
- * Set the Vi-default value of a number option.
- * Used for 'lines' and 'columns'.
- */
+/// Set the Vi-default value of a number option.
+/// Used for 'lines' and 'columns'.
void set_number_default(char *name, long val)
{
int opt_idx;
@@ -1072,10 +1116,8 @@ void set_init_3(void)
set_title_defaults(); // 'title', 'icon'
}
-/*
- * When 'helplang' is still at its default value, set it to "lang".
- * Only the first two characters of "lang" are used.
- */
+/// When 'helplang' is still at its default value, set it to "lang".
+/// Only the first two characters of "lang" are used.
void set_helplang_default(const char *lang)
{
if (lang == NULL) {
@@ -1107,13 +1149,11 @@ void set_helplang_default(const char *lang)
}
-/*
- * 'title' and 'icon' only default to true if they have not been set or reset
- * in .vimrc and we can read the old value.
- * When 'title' and 'icon' have been reset in .vimrc, we won't even check if
- * they can be reset. This reduces startup time when using X on a remote
- * machine.
- */
+/// 'title' and 'icon' only default to true if they have not been set or reset
+/// in .vimrc and we can read the old value.
+/// When 'title' and 'icon' have been reset in .vimrc, we won't even check if
+/// they can be reset. This reduces startup time when using X on a remote
+/// machine.
void set_title_defaults(void)
{
int idx1;
@@ -1978,10 +2018,8 @@ static char_u *illegal_char(char_u *errbuf, size_t errbuflen, int c)
return errbuf;
}
-/*
- * Convert a key name or string into a key value.
- * Used for 'wildchar' and 'cedit' options.
- */
+/// Convert a key name or string into a key value.
+/// Used for 'wildchar' and 'cedit' options.
static int string_to_key(char_u *arg)
{
if (*arg == '<') {
@@ -1993,10 +2031,8 @@ static int string_to_key(char_u *arg)
return *arg;
}
-/*
- * Check value of 'cedit' and set cedit_key.
- * Returns NULL if value is OK, error message otherwise.
- */
+/// Check value of 'cedit' and set cedit_key.
+/// Returns NULL if value is OK, error message otherwise.
static char_u *check_cedit(void)
{
int n;
@@ -2017,13 +2053,10 @@ static char_u *check_cedit(void)
// maketitle() to create and display it.
// When switching the title or icon off, call ui_set_{icon,title}(NULL) to get
// the old value back.
-static void did_set_title(
- int icon // Did set icon instead of title
-)
+static void did_set_title(void)
{
if (starting != NO_SCREEN) {
maketitle();
- resettitle();
}
}
@@ -2083,13 +2116,11 @@ void set_options_bin(
}
}
-/*
- * Find the parameter represented by the given character (eg ', :, ", or /),
- * and return its associated value in the 'shada' string.
- * Only works for number parameters, not for 'r' or 'n'.
- * If the parameter is not specified in the string or there is no following
- * number, return -1.
- */
+/// Find the parameter represented by the given character (eg ', :, ", or /),
+/// and return its associated value in the 'shada' string.
+/// Only works for number parameters, not for 'r' or 'n'.
+/// If the parameter is not specified in the string or there is no following
+/// number, return -1.
int get_shada_parameter(int type)
{
char_u *p;
@@ -2101,11 +2132,9 @@ int get_shada_parameter(int type)
return -1;
}
-/*
- * Find the parameter represented by the given character (eg ''', ':', '"', or
- * '/') in the 'shada' option and return a pointer to the string after it.
- * Return NULL if the parameter is not specified in the string.
- */
+/// Find the parameter represented by the given character (eg ''', ':', '"', or
+/// '/') in the 'shada' option and return a pointer to the string after it.
+/// Return NULL if the parameter is not specified in the string.
char_u *find_shada_parameter(int type)
{
char_u *p;
@@ -2125,12 +2154,10 @@ char_u *find_shada_parameter(int type)
return NULL;
}
-/*
- * Expand environment variables for some string options.
- * These string options cannot be indirect!
- * If "val" is NULL expand the current value of the option.
- * Return pointer to NameBuff, or NULL when not expanded.
- */
+/// Expand environment variables for some string options.
+/// These string options cannot be indirect!
+/// If "val" is NULL expand the current value of the option.
+/// Return pointer to NameBuff, or NULL when not expanded.
static char_u *option_expand(int opt_idx, char_u *val)
{
// if option doesn't need expansion nothing to do
@@ -2183,6 +2210,7 @@ static void didset_options(void)
(void)opt_strings_flags(p_tc, p_tc_values, &tc_flags, false);
(void)opt_strings_flags(p_ve, p_ve_values, &ve_flags, true);
(void)opt_strings_flags(p_wop, p_wop_values, &wop_flags, true);
+ (void)opt_strings_flags(p_jop, p_jop_values, &jop_flags, true);
(void)spell_check_msm();
(void)spell_check_sps();
(void)compile_cap_prog(curwin->w_s);
@@ -2204,18 +2232,16 @@ static void didset_options2(void)
(void)opt_strings_flags(p_cb, p_cb_values, &cb_flags, true);
// Parse default for 'fillchars'.
- (void)set_chars_option(curwin, &curwin->w_p_fcs);
+ (void)set_chars_option(curwin, &curwin->w_p_fcs, true);
// Parse default for 'listchars'.
- (void)set_chars_option(curwin, &curwin->w_p_lcs);
+ (void)set_chars_option(curwin, &curwin->w_p_lcs, true);
// Parse default for 'wildmode'.
check_opt_wim();
}
-/*
- * Check for string options that are NULL (normally only termcap options).
- */
+/// Check for string options that are NULL (normally only termcap options).
void check_options(void)
{
int opt_idx;
@@ -2227,9 +2253,7 @@ void check_options(void)
}
}
-/*
- * Check string options in a buffer for NULL value.
- */
+/// Check string options in a buffer for NULL value.
void check_buf_options(buf_T *buf)
{
check_string_option(&buf->b_p_bh);
@@ -2273,6 +2297,7 @@ void check_buf_options(buf_T *buf)
check_string_option(&buf->b_p_ep);
check_string_option(&buf->b_p_path);
check_string_option(&buf->b_p_tags);
+ check_string_option(&buf->b_p_tfu);
check_string_option(&buf->b_p_tc);
check_string_option(&buf->b_p_dict);
check_string_option(&buf->b_p_tsr);
@@ -2281,13 +2306,11 @@ void check_buf_options(buf_T *buf)
check_string_option(&buf->b_p_menc);
}
-/*
- * Free the string allocated for an option.
- * Checks for the string being empty_option. This may happen if we're out of
- * memory, vim_strsave() returned NULL, which was replaced by empty_option by
- * check_options().
- * Does NOT check for P_ALLOCED flag!
- */
+/// Free the string allocated for an option.
+/// Checks for the string being empty_option. This may happen if we're out of
+/// memory, vim_strsave() returned NULL, which was replaced by empty_option by
+/// check_options().
+/// Does NOT check for P_ALLOCED flag!
void free_string_option(char_u *p)
{
if (p != empty_option) {
@@ -2325,10 +2348,8 @@ int was_set_insecurely(char_u *opt, int opt_flags)
return -1;
}
-/*
- * Get a pointer to the flags used for the P_INSECURE flag of option
- * "opt_idx". For some local options a local flags field is used.
- */
+/// Get a pointer to the flags used for the P_INSECURE flag of option
+/// "opt_idx". For some local options a local flags field is used.
static uint32_t *insecure_flag(int opt_idx, int opt_flags)
{
if (opt_flags & OPT_LOCAL)
@@ -2346,9 +2367,7 @@ static uint32_t *insecure_flag(int opt_idx, int opt_flags)
}
-/*
- * Redraw the window title and/or tab page text later.
- */
+/// Redraw the window title and/or tab page text later.
static void redraw_titles(void)
{
need_maketitle = true;
@@ -2429,9 +2448,7 @@ set_string_option_direct(
}
}
-/*
- * Set global value for string option when it's a local option.
- */
+/// Set global value for string option when it's a local option.
static void
set_string_option_global(
int opt_idx, // option index
@@ -2506,12 +2523,41 @@ static char *set_string_option(const int opt_idx, const char *const value,
return r;
}
+/// Return true if "val" is a valid name: only consists of alphanumeric ASCII
+/// characters or characters in "allowed".
+static bool valid_name(const char_u *val, const char *allowed)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ for (const char_u *s = val; *s != NUL; s++) {
+ if (!ASCII_ISALNUM(*s)
+ && vim_strchr((const char_u *)allowed, *s) == NULL) {
+ return false;
+ }
+ }
+ return true;
+}
+
/// Return true if "val" is a valid 'filetype' name.
/// Also used for 'syntax' and 'keymap'.
-static bool valid_filetype(char_u *val)
+static bool valid_filetype(const char_u *val)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ return valid_name(val, ".-_");
+}
+
+/// Return true if "val" is a valid 'spelllang' value.
+bool valid_spelllang(const char_u *val)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ return valid_name(val, ".-_,@");
+}
+
+/// Return true if "val" is a valid 'spellfile' value.
+static bool valid_spellfile(const char_u *val)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
- for (char_u *s = val; *s != NUL; s++) {
- if (!ASCII_ISALNUM(*s) && vim_strchr((char_u *)".-_", *s) == NULL) {
+ for (const char_u *s = val; *s != NUL; s++) {
+ if (!vim_isfilec(*s) && *s != ',') {
return false;
}
}
@@ -2630,6 +2676,10 @@ did_set_string_option(
if (strcmp((char *)(*varp), HIGHLIGHT_INIT) != 0) {
errmsg = e_unsupportedoption;
}
+ } else if (varp == &p_jop) { // 'jumpoptions'
+ if (opt_strings_flags(p_jop, p_jop_values, &jop_flags, true) != OK) {
+ errmsg = e_invarg;
+ }
} else if (gvarp == &p_nf) { // 'nrformats'
if (check_opt_strings(*varp, p_nf_values, true) != OK) {
errmsg = e_invarg;
@@ -2661,11 +2711,11 @@ did_set_string_option(
errmsg = e_invarg;
} else {
FOR_ALL_TAB_WINDOWS(tp, wp) {
- if (set_chars_option(wp, &wp->w_p_lcs) != NULL) {
+ if (set_chars_option(wp, &wp->w_p_lcs, true) != NULL) {
errmsg = (char_u *)_("E834: Conflicts with value of 'listchars'");
goto ambw_end;
}
- if (set_chars_option(wp, &wp->w_p_fcs) != NULL) {
+ if (set_chars_option(wp, &wp->w_p_fcs, true) != NULL) {
errmsg = (char_u *)_("E835: Conflicts with value of 'fillchars'");
goto ambw_end;
}
@@ -2866,10 +2916,26 @@ ambw_end:
}
s = skip_to_option_part(s);
}
- } else if (varp == &curwin->w_p_lcs) { // 'listchars'
- errmsg = set_chars_option(curwin, varp);
- } else if (varp == &curwin->w_p_fcs) { // 'fillchars'
- errmsg = set_chars_option(curwin, varp);
+ } else if (varp == &p_lcs) { // 'listchars'
+ errmsg = set_chars_option(curwin, varp, false);
+ if (!errmsg) {
+ FOR_ALL_TAB_WINDOWS(tp, wp) {
+ set_chars_option(wp, &wp->w_p_lcs, true);
+ }
+ }
+ redraw_all_later(NOT_VALID);
+ } else if (varp == &curwin->w_p_lcs) { // local 'listchars'
+ errmsg = set_chars_option(curwin, varp, true);
+ } else if (varp == &p_fcs) { // 'fillchars'
+ errmsg = set_chars_option(curwin, varp, false);
+ if (!errmsg) {
+ FOR_ALL_TAB_WINDOWS(tp, wp) {
+ set_chars_option(wp, &wp->w_p_fcs, true);
+ }
+ }
+ redraw_all_later(NOT_VALID);
+ } else if (varp == &curwin->w_p_fcs) { // local 'fillchars'
+ errmsg = set_chars_option(curwin, varp, true);
} else if (varp == &p_cedit) { // 'cedit'
errmsg = check_cedit();
} else if (varp == &p_vfile) { // 'verbosefile'
@@ -2960,7 +3026,7 @@ ambw_end:
} else {
stl_syntax &= ~flagval;
}
- did_set_title(varp == &p_iconstring);
+ did_set_title();
} else if (varp == &p_sel) { // 'selection'
if (*p_sel == NUL
@@ -3009,7 +3075,14 @@ ambw_end:
|| varp == &(curwin->w_s->b_p_spf)) {
// When 'spelllang' or 'spellfile' is set and there is a window for this
// buffer in which 'spell' is set load the wordlists.
- errmsg = did_set_spell_option(varp == &(curwin->w_s->b_p_spf));
+ const bool is_spellfile = varp == &(curwin->w_s->b_p_spf);
+
+ if ((is_spellfile && !valid_spellfile(*varp))
+ || (!is_spellfile && !valid_spelllang(*varp))) {
+ errmsg = e_invarg;
+ } else {
+ errmsg = did_set_spell_option(is_spellfile);
+ }
} else if (varp == &(curwin->w_s->b_p_spc)) {
// When 'spellcapcheck' is set compile the regexp program.
errmsg = compile_cap_prog(curwin->w_s);
@@ -3110,6 +3183,11 @@ ambw_end:
if (check_opt_strings(*varp, p_scl_values, false) != OK) {
errmsg = e_invarg;
}
+ } else if (varp == &curwin->w_p_fdc || varp == &curwin->w_allbuf_opt.wo_fdc) {
+ // 'foldcolumn'
+ if (check_opt_strings(*varp, p_fdc_values, false) != OK) {
+ errmsg = e_invarg;
+ }
} else if (varp == &p_pt) {
// 'pastetoggle': translate key codes like in a mapping
if (*p_pt) {
@@ -3499,7 +3577,7 @@ skip:
///
/// @param varp either &curwin->w_p_lcs or &curwin->w_p_fcs
/// @return error message, NULL if it's OK.
-static char_u *set_chars_option(win_T *wp, char_u **varp)
+static char_u *set_chars_option(win_T *wp, char_u **varp, bool set)
{
int round, i, len, entries;
char_u *p, *s;
@@ -3519,6 +3597,9 @@ static char_u *set_chars_option(win_T *wp, char_u **varp)
{ &wp->w_p_fcs_chars.stlnc, "stlnc", ' ' },
{ &wp->w_p_fcs_chars.vert, "vert", 9474 }, // │
{ &wp->w_p_fcs_chars.fold, "fold", 183 }, // ·
+ { &wp->w_p_fcs_chars.foldopen, "foldopen", '-' },
+ { &wp->w_p_fcs_chars.foldclosed, "foldclose", '+' },
+ { &wp->w_p_fcs_chars.foldsep, "foldsep", 9474 }, // │
{ &wp->w_p_fcs_chars.diff, "diff", '-' },
{ &wp->w_p_fcs_chars.msgsep, "msgsep", ' ' },
{ &wp->w_p_fcs_chars.eob, "eob", '~' },
@@ -3534,25 +3615,33 @@ static char_u *set_chars_option(win_T *wp, char_u **varp)
{ &wp->w_p_lcs_chars.conceal, "conceal", NUL },
};
- if (varp == &wp->w_p_lcs) {
+ if (varp == &p_lcs || varp == &wp->w_p_lcs) {
tab = lcs_tab;
entries = ARRAY_SIZE(lcs_tab);
+ if (varp == &wp->w_p_lcs && wp->w_p_lcs[0] == NUL) {
+ varp = &p_lcs;
+ }
} else {
tab = fcs_tab;
entries = ARRAY_SIZE(fcs_tab);
+ if (varp == &wp->w_p_fcs && wp->w_p_fcs[0] == NUL) {
+ varp = &p_fcs;
+ }
if (*p_ambw == 'd') {
// XXX: If ambiwidth=double then "|" and "·" take 2 columns, which is
// forbidden (TUI limitation?). Set old defaults.
fcs_tab[2].def = '|';
+ fcs_tab[6].def = '|';
fcs_tab[3].def = '-';
} else {
fcs_tab[2].def = 9474; // │
+ fcs_tab[6].def = 9474; // │
fcs_tab[3].def = 183; // ·
}
}
// first round: check for valid value, second round: assign values
- for (round = 0; round <= 1; round++) {
+ for (round = 0; round <= (set ? 1 : 0); round++) {
if (round > 0) {
// After checking that the value is valid: set defaults
for (i = 0; i < entries; i++) {
@@ -3560,7 +3649,7 @@ static char_u *set_chars_option(win_T *wp, char_u **varp)
*(tab[i].cp) = tab[i].def;
}
}
- if (varp == &wp->w_p_lcs) {
+ if (varp == &p_lcs || varp == &wp->w_p_lcs) {
wp->w_p_lcs_chars.tab1 = NUL;
wp->w_p_lcs_chars.tab3 = NUL;
}
@@ -3626,10 +3715,8 @@ static char_u *set_chars_option(win_T *wp, char_u **varp)
return NULL; // no error
}
-/*
- * Check validity of options with the 'statusline' format.
- * Return error message or NULL.
- */
+/// Check validity of options with the 'statusline' format.
+/// Return error message or NULL.
char_u *check_stl_option(char_u *s)
{
int itemcnt = 0;
@@ -3722,16 +3809,15 @@ static char_u *did_set_spell_option(bool is_spellfile)
return errmsg;
}
-/*
- * Set curbuf->b_cap_prog to the regexp program for 'spellcapcheck'.
- * Return error message when failed, NULL when OK.
- */
+/// Set curbuf->b_cap_prog to the regexp program for 'spellcapcheck'.
+/// Return error message when failed, NULL when OK.
static char_u *compile_cap_prog(synblock_T *synblock)
+ FUNC_ATTR_NONNULL_ALL
{
regprog_T *rp = synblock->b_cap_prog;
char_u *re;
- if (*synblock->b_p_spc == NUL) {
+ if (synblock->b_p_spc == NULL || *synblock->b_p_spc == NUL) {
synblock->b_cap_prog = NULL;
} else {
// Prepend a ^ so that we only match at one column
@@ -3771,7 +3857,8 @@ static bool parse_winhl_opt(win_T *wp)
w_hl_id_normal = hl_id;
} else {
for (hlf = 0; hlf < (int)HLF_COUNT; hlf++) {
- if (strncmp(hlf_names[hlf], p, nlen) == 0) {
+ if (strlen(hlf_names[hlf]) == nlen
+ && strncmp(hlf_names[hlf], p, nlen) == 0) {
w_hl_ids[hlf] = hl_id;
break;
}
@@ -3988,9 +4075,9 @@ static char *set_bool_option(const int opt_idx, char_u *const varp,
(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(false);
+ did_set_title();
} else if ((int *)varp == &p_icon) {
- did_set_title(true);
+ did_set_title();
} else if ((int *)varp == &curbuf->b_changed) {
if (!value) {
save_file_ff(curbuf); // Buffer is unchanged
@@ -4240,7 +4327,7 @@ static char *set_num_option(int opt_idx, char_u *varp, long value,
}
} else if (pp == &p_so) {
if (value < 0 && full_screen) {
- errmsg = e_scroll;
+ errmsg = e_positive;
}
} else if (pp == &p_siso) {
if (value < 0 && full_screen) {
@@ -4262,12 +4349,6 @@ static char *set_num_option(int opt_idx, char_u *varp, long value,
if (value < 0) {
errmsg = e_positive;
}
- } else if (pp == &curwin->w_p_fdc || pp == &curwin->w_allbuf_opt.wo_fdc) {
- if (value < 0) {
- errmsg = e_positive;
- } else if (value > 12) {
- errmsg = e_invarg;
- }
} else if (pp == &curwin->w_p_cole || pp == &curwin->w_allbuf_opt.wo_cole) {
if (value < 0) {
errmsg = e_positive;
@@ -4306,6 +4387,10 @@ static char *set_num_option(int opt_idx, char_u *varp, long value,
if (value < 0) {
errmsg = e_positive;
}
+ } else if (pp == &p_wd) {
+ if (value < 0) {
+ errmsg = e_positive;
+ }
}
// Don't change the value and return early if validation failed.
@@ -4567,9 +4652,7 @@ static void trigger_optionsset_string(int opt_idx, int opt_flags,
}
}
-/*
- * Called after an option changed: check if something needs to be redrawn.
- */
+/// Called after an option changed: check if something needs to be redrawn.
static void check_redraw(uint32_t flags)
{
// Careful: P_RCLR and P_RALL are a combination of other P_ flags
@@ -5024,10 +5107,8 @@ static int find_key_option(const char_u *arg, bool has_lt)
return find_key_option_len(arg, STRLEN(arg), has_lt);
}
-/*
- * if 'all' == 0: show changed options
- * if 'all' == 1: show all normal options
- */
+/// if 'all' == 0: show changed options
+/// if 'all' == 1: show all normal options
static void
showoptions(
int all,
@@ -5167,12 +5248,17 @@ void ui_refresh_options(void)
}
ui_call_option_set(name, value);
}
+ if (p_mouse != NULL) {
+ if (*p_mouse == NUL) {
+ ui_call_mouse_off();
+ } else {
+ setmouse();
+ }
+ }
}
-/*
- * showoneopt: show the value of one option
- * must not be called with a hidden option!
- */
+/// showoneopt: show the value of one option
+/// must not be called with a hidden option!
static void
showoneopt(
vimoption_T *p,
@@ -5208,28 +5294,26 @@ showoneopt(
info_message = false;
}
-/*
- * Write modified options as ":set" commands to a file.
- *
- * There are three values for "opt_flags":
- * OPT_GLOBAL: Write global option values and fresh values of
- * buffer-local options (used for start of a session
- * file).
- * OPT_GLOBAL + OPT_LOCAL: Idem, add fresh values of window-local options for
- * curwin (used for a vimrc file).
- * OPT_LOCAL: Write buffer-local option values for curbuf, fresh
- * and local values for window-local options of
- * curwin. Local values are also written when at the
- * default value, because a modeline or autocommand
- * may have set them when doing ":edit file" and the
- * user has set them back at the default or fresh
- * value.
- * When "local_only" is true, don't write fresh
- * values, only local values (for ":mkview").
- * (fresh value = value used for a new buffer or window for a local option).
- *
- * Return FAIL on error, OK otherwise.
- */
+/// Write modified options as ":set" commands to a file.
+///
+/// There are three values for "opt_flags":
+/// OPT_GLOBAL: Write global option values and fresh values of
+/// buffer-local options (used for start of a session
+/// file).
+/// OPT_GLOBAL + OPT_LOCAL: Idem, add fresh values of window-local options for
+/// curwin (used for a vimrc file).
+/// OPT_LOCAL: Write buffer-local option values for curbuf, fresh
+/// and local values for window-local options of
+/// curwin. Local values are also written when at the
+/// default value, because a modeline or autocommand
+/// may have set them when doing ":edit file" and the
+/// user has set them back at the default or fresh
+/// value.
+/// When "local_only" is true, don't write fresh
+/// values, only local values (for ":mkview").
+/// (fresh value = value used for a new buffer or window for a local option).
+///
+/// Return FAIL on error, OK otherwise.
int makeset(FILE *fd, int opt_flags, int local_only)
{
vimoption_T *p;
@@ -5325,8 +5409,9 @@ int makeset(FILE *fd, int opt_flags, int local_only)
do_endif = true;
}
if (put_setstring(fd, cmd, p->fullname, (char_u **)varp,
- (p->flags & P_EXPAND) != 0) == FAIL)
+ p->flags) == FAIL) {
return FAIL;
+ }
if (do_endif) {
if (put_line(fd, "endif") == FAIL) {
return FAIL;
@@ -5340,18 +5425,16 @@ int makeset(FILE *fd, int opt_flags, int local_only)
return OK;
}
-/*
- * Generate set commands for the local fold options only. Used when
- * 'sessionoptions' or 'viewoptions' contains "folds" but not "options".
- */
+/// Generate set commands for the local fold options only. Used when
+/// 'sessionoptions' or 'viewoptions' contains "folds" but not "options".
int makefoldset(FILE *fd)
{
- if (put_setstring(fd, "setlocal", "fdm", &curwin->w_p_fdm, false) == FAIL
- || put_setstring(fd, "setlocal", "fde", &curwin->w_p_fde, false)
+ if (put_setstring(fd, "setlocal", "fdm", &curwin->w_p_fdm, 0) == FAIL
+ || put_setstring(fd, "setlocal", "fde", &curwin->w_p_fde, 0)
== FAIL
- || put_setstring(fd, "setlocal", "fmr", &curwin->w_p_fmr, false)
+ || put_setstring(fd, "setlocal", "fmr", &curwin->w_p_fmr, 0)
== FAIL
- || put_setstring(fd, "setlocal", "fdi", &curwin->w_p_fdi, false)
+ || put_setstring(fd, "setlocal", "fdi", &curwin->w_p_fdi, 0)
== FAIL
|| put_setnum(fd, "setlocal", "fdl", &curwin->w_p_fdl) == FAIL
|| put_setnum(fd, "setlocal", "fml", &curwin->w_p_fml) == FAIL
@@ -5364,10 +5447,13 @@ int makefoldset(FILE *fd)
return OK;
}
-static int put_setstring(FILE *fd, char *cmd, char *name, char_u **valuep, int expand)
+static int put_setstring(FILE *fd, char *cmd, char *name,
+ char_u **valuep, uint64_t flags)
{
char_u *s;
- char_u *buf;
+ char_u *buf = NULL;
+ char_u *part = NULL;
+ char_u *p;
if (fprintf(fd, "%s %s=", cmd, name) < 0) {
return FAIL;
@@ -5385,9 +5471,40 @@ static int put_setstring(FILE *fd, char *cmd, char *name, char_u **valuep, int e
return FAIL;
}
}
- } else if (expand) {
- buf = xmalloc(MAXPATHL);
- home_replace(NULL, *valuep, buf, MAXPATHL, false);
+ } else if ((flags & P_EXPAND) != 0) {
+ size_t size = (size_t)STRLEN(*valuep) + 1;
+
+ // replace home directory in the whole option value into "buf"
+ buf = xmalloc(size);
+ home_replace(NULL, *valuep, buf, size, false);
+
+ // If the option value is longer than MAXPATHL, we need to append
+ // earch comma separated part of the option sperately, so that it
+ // can be expanded when read back.
+ if (size >= MAXPATHL && (flags & P_COMMA) != 0
+ && vim_strchr(*valuep, ',') != NULL) {
+ part = xmalloc(size);
+
+ // write line break to clear the option, e.g. ':set rtp='
+ if (put_eol(fd) == FAIL) {
+ goto fail;
+ }
+ p = buf;
+ while (*p != NUL) {
+ // for each comma seperated option part, append value to
+ // the option, :set rtp+=value
+ if (fprintf(fd, "%s %s+=", cmd, name) < 0) {
+ goto fail;
+ }
+ (void)copy_option_part(&p, part, size, ",");
+ if (put_escstr(fd, part, 2) == FAIL || put_eol(fd) == FAIL) {
+ goto fail;
+ }
+ }
+ xfree(buf);
+ xfree(part);
+ return OK;
+ }
if (put_escstr(fd, buf, 2) == FAIL) {
xfree(buf);
return FAIL;
@@ -5401,6 +5518,10 @@ static int put_setstring(FILE *fd, char *cmd, char *name, char_u **valuep, int e
return FAIL;
}
return OK;
+fail:
+ xfree(buf);
+ xfree(part);
+ return FAIL;
}
static int put_setnum(FILE *fd, char *cmd, char *name, long *valuep)
@@ -5436,12 +5557,10 @@ static int put_setbool(FILE *fd, char *cmd, char *name, int value)
return OK;
}
-/*
- * Compute columns for ruler and shown command. 'sc_col' is also used to
- * decide what the maximum length of a message on the status line can be.
- * If there is a status line for the last window, 'sc_col' is independent
- * of 'ru_col'.
- */
+/// Compute columns for ruler and shown command. 'sc_col' is also used to
+/// decide what the maximum length of a message on the status line can be.
+/// If there is a status line for the last window, 'sc_col' is independent
+/// of 'ru_col'.
#define COL_RULER 17 // columns needed by standard ruler
@@ -5520,6 +5639,12 @@ void unset_global_local_option(char *name, void *from)
clear_string_option(&buf->b_p_tc);
buf->b_tc_flags = 0;
break;
+ case PV_SISO:
+ curwin->w_p_siso = -1;
+ break;
+ case PV_SO:
+ curwin->w_p_so = -1;
+ break;
case PV_DEF:
clear_string_option(&buf->b_p_def);
break;
@@ -5556,12 +5681,20 @@ void unset_global_local_option(char *name, void *from)
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_win_later((win_T *)from, 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_win_later((win_T *)from, NOT_VALID);
+ break;
}
}
-/*
- * Get pointer to option variable, depending on local or global scope.
- */
+/// Get pointer to option variable, depending on local or global scope.
static char_u *get_varp_scope(vimoption_T *p, int opt_flags)
{
if ((opt_flags & OPT_GLOBAL) && p->indir != PV_NONE) {
@@ -5582,24 +5715,27 @@ static char_u *get_varp_scope(vimoption_T *p, int opt_flags)
case PV_AR: return (char_u *)&(curbuf->b_p_ar);
case PV_TAGS: return (char_u *)&(curbuf->b_p_tags);
case PV_TC: return (char_u *)&(curbuf->b_p_tc);
+ case PV_SISO: return (char_u *)&(curwin->w_p_siso);
+ case PV_SO: return (char_u *)&(curwin->w_p_so);
case PV_DEF: return (char_u *)&(curbuf->b_p_def);
case PV_INC: return (char_u *)&(curbuf->b_p_inc);
case PV_DICT: return (char_u *)&(curbuf->b_p_dict);
case PV_TSR: return (char_u *)&(curbuf->b_p_tsr);
+ case PV_TFU: return (char_u *)&(curbuf->b_p_tfu);
case PV_STL: return (char_u *)&(curwin->w_p_stl);
case PV_UL: return (char_u *)&(curbuf->b_p_ul);
case PV_LW: return (char_u *)&(curbuf->b_p_lw);
case PV_BKC: return (char_u *)&(curbuf->b_p_bkc);
case PV_MENC: return (char_u *)&(curbuf->b_p_menc);
+ case PV_FCS: return (char_u *)&(curwin->w_p_fcs);
+ case PV_LCS: return (char_u *)&(curwin->w_p_lcs);
}
return NULL; // "cannot happen"
}
return get_varp(p);
}
-/*
- * Get pointer to option variable.
- */
+/// Get pointer to option variable.
static char_u *get_varp(vimoption_T *p)
{
// hidden option, always return NULL
@@ -5623,6 +5759,10 @@ static char_u *get_varp(vimoption_T *p)
? (char_u *)&(curbuf->b_p_tags) : p->var;
case PV_TC: return *curbuf->b_p_tc != NUL
? (char_u *)&(curbuf->b_p_tc) : p->var;
+ case PV_SISO: return curwin->w_p_siso >= 0
+ ? (char_u *)&(curwin->w_p_siso) : p->var;
+ case PV_SO: return curwin->w_p_so >= 0
+ ? (char_u *)&(curwin->w_p_so) : p->var;
case PV_BKC: return *curbuf->b_p_bkc != NUL
? (char_u *)&(curbuf->b_p_bkc) : p->var;
case PV_DEF: return *curbuf->b_p_def != NUL
@@ -5649,6 +5789,10 @@ static char_u *get_varp(vimoption_T *p)
? (char_u *)&(curbuf->b_p_lw) : p->var;
case PV_MENC: return *curbuf->b_p_menc != NUL
? (char_u *)&(curbuf->b_p_menc) : p->var;
+ case PV_FCS: return *curwin->w_p_fcs != NUL
+ ? (char_u *)&(curwin->w_p_fcs) : p->var;
+ case PV_LCS: return *curwin->w_p_lcs != NUL
+ ? (char_u *)&(curwin->w_p_lcs) : p->var;
case PV_ARAB: return (char_u *)&(curwin->w_p_arab);
case PV_LIST: return (char_u *)&(curwin->w_p_list);
@@ -5738,6 +5882,7 @@ static char_u *get_varp(vimoption_T *p)
case PV_SPF: return (char_u *)&(curwin->w_s->b_p_spf);
case PV_SPL: return (char_u *)&(curwin->w_s->b_p_spl);
case PV_SW: return (char_u *)&(curbuf->b_p_sw);
+ case PV_TFU: return (char_u *)&(curbuf->b_p_tfu);
case PV_TS: return (char_u *)&(curbuf->b_p_ts);
case PV_TW: return (char_u *)&(curbuf->b_p_tw);
case PV_UDF: return (char_u *)&(curbuf->b_p_udf);
@@ -5745,8 +5890,6 @@ static char_u *get_varp(vimoption_T *p)
case PV_KMAP: return (char_u *)&(curbuf->b_p_keymap);
case PV_SCL: return (char_u *)&(curwin->w_p_scl);
case PV_WINHL: return (char_u *)&(curwin->w_p_winhl);
- case PV_FCS: return (char_u *)&(curwin->w_p_fcs);
- case PV_LCS: return (char_u *)&(curwin->w_p_lcs);
case PV_WINBL: return (char_u *)&(curwin->w_p_winbl);
default: IEMSG(_("E356: get_varp ERROR"));
}
@@ -5754,9 +5897,7 @@ static char_u *get_varp(vimoption_T *p)
return (char_u *)&(curbuf->b_p_wm);
}
-/*
- * Get the value of 'equalprg', either the buffer-local one or the global one.
- */
+/// Get the value of 'equalprg', either the buffer-local one or the global one.
char_u *get_equalprg(void)
{
if (*curbuf->b_p_ep == NUL) {
@@ -5765,22 +5906,18 @@ char_u *get_equalprg(void)
return curbuf->b_p_ep;
}
-/*
- * Copy options from one window to another.
- * Used when splitting a window.
- */
+/// Copy options from one window to another.
+/// Used when splitting a window.
void win_copy_options(win_T *wp_from, win_T *wp_to)
{
copy_winopt(&wp_from->w_onebuf_opt, &wp_to->w_onebuf_opt);
copy_winopt(&wp_from->w_allbuf_opt, &wp_to->w_allbuf_opt);
}
-/*
- * Copy the options from one winopt_T to another.
- * Doesn't free the old option values in "to", use clear_winopt() for that.
- * The 'scroll' option is not copied, because it depends on the window height.
- * The 'previewwindow' option is reset, there can be only one preview window.
- */
+/// Copy the options from one winopt_T to another.
+/// Doesn't free the old option values in "to", use clear_winopt() for that.
+/// The 'scroll' option is not copied, because it depends on the window height.
+/// The 'previewwindow' option is reset, there can be only one preview window.
void copy_winopt(winopt_T *from, winopt_T *to)
{
to->wo_arab = from->wo_arab;
@@ -5808,8 +5945,9 @@ void copy_winopt(winopt_T *from, winopt_T *to)
to->wo_diff_saved = from->wo_diff_saved;
to->wo_cocu = vim_strsave(from->wo_cocu);
to->wo_cole = from->wo_cole;
- to->wo_fdc = from->wo_fdc;
- to->wo_fdc_save = from->wo_fdc_save;
+ to->wo_fdc = vim_strsave(from->wo_fdc);
+ to->wo_fdc_save = from->wo_diff_saved
+ ? vim_strsave(from->wo_fdc_save) : empty_option;
to->wo_fen = from->wo_fen;
to->wo_fen_save = from->wo_fen_save;
to->wo_fdi = vim_strsave(from->wo_fdi);
@@ -5831,20 +5969,18 @@ void copy_winopt(winopt_T *from, winopt_T *to)
check_winopt(to); // don't want NULL pointers
}
-/*
- * Check string options in a window for a NULL value.
- */
+/// Check string options in a window for a NULL value.
void check_win_options(win_T *win)
{
check_winopt(&win->w_onebuf_opt);
check_winopt(&win->w_allbuf_opt);
}
-/*
- * Check for NULL pointers in a winopt_T and replace them with empty_option.
- */
+/// Check for NULL pointers in a winopt_T and replace them with empty_option.
static void check_winopt(winopt_T *wop)
{
+ check_string_option(&wop->wo_fdc);
+ check_string_option(&wop->wo_fdc_save);
check_string_option(&wop->wo_fdi);
check_string_option(&wop->wo_fdm);
check_string_option(&wop->wo_fdm_save);
@@ -5862,11 +5998,11 @@ static void check_winopt(winopt_T *wop)
check_string_option(&wop->wo_lcs);
}
-/*
- * Free the allocated memory inside a winopt_T.
- */
+/// Free the allocated memory inside a winopt_T.
void clear_winopt(winopt_T *wop)
{
+ clear_string_option(&wop->wo_fdc);
+ clear_string_option(&wop->wo_fdc_save);
clear_string_option(&wop->wo_fdi);
clear_string_option(&wop->wo_fdm);
clear_string_option(&wop->wo_fdm_save);
@@ -5888,22 +6024,20 @@ void didset_window_options(win_T *wp)
{
check_colorcolumn(wp);
briopt_check(wp);
- set_chars_option(wp, &wp->w_p_fcs);
- set_chars_option(wp, &wp->w_p_lcs);
+ set_chars_option(wp, &wp->w_p_fcs, true);
+ set_chars_option(wp, &wp->w_p_lcs, true);
parse_winhl_opt(wp); // sets w_hl_needs_update also for w_p_winbl
wp->w_grid.blending = wp->w_p_winbl > 0;
}
-/*
- * Copy global option values to local options for one buffer.
- * Used when creating a new buffer and sometimes when entering a buffer.
- * flags:
- * BCO_ENTER We will enter the buf buffer.
- * BCO_ALWAYS Always copy the options, but only set b_p_initialized when
- * appropriate.
- * BCO_NOHELP Don't copy the values to a help buffer.
- */
+/// Copy global option values to local options for one buffer.
+/// Used when creating a new buffer and sometimes when entering a buffer.
+/// flags:
+/// BCO_ENTER We will enter the buf buffer.
+/// BCO_ALWAYS Always copy the options, but only set b_p_initialized when
+/// appropriate.
+/// BCO_NOHELP Don't copy the values to a help buffer.
void buf_copy_options(buf_T *buf, int flags)
{
int should_copy = true;
@@ -5945,10 +6079,8 @@ void buf_copy_options(buf_T *buf, int flags)
save_p_isk = buf->b_p_isk;
buf->b_p_isk = NULL;
}
- /*
- * Always free the allocated strings.
- * If not already initialized, set 'readonly' and copy 'fileformat'.
- */
+ // Always free the allocated strings. If not already initialized,
+ // reset 'readonly' and copy 'fileformat'.
if (!buf->b_p_initialized) {
free_buf_options(buf, true);
buf->b_p_ro = false; // don't copy readonly
@@ -6000,6 +6132,7 @@ void buf_copy_options(buf_T *buf, int flags)
buf->b_p_cpt = vim_strsave(p_cpt);
buf->b_p_cfu = vim_strsave(p_cfu);
buf->b_p_ofu = vim_strsave(p_ofu);
+ buf->b_p_tfu = vim_strsave(p_tfu);
buf->b_p_sts = p_sts;
buf->b_p_sts_nopaste = p_sts_nopaste;
buf->b_p_com = vim_strsave(p_com);
@@ -6099,9 +6232,7 @@ void buf_copy_options(buf_T *buf, int flags)
}
}
-/*
- * Reset the 'modifiable' option and its default value.
- */
+/// Reset the 'modifiable' option and its default value.
void reset_modifiable(void)
{
int opt_idx;
@@ -6114,17 +6245,13 @@ void reset_modifiable(void)
}
}
-/*
- * Set the global value for 'iminsert' to the local value.
- */
+/// Set the global value for 'iminsert' to the local value.
void set_iminsert_global(void)
{
p_iminsert = curbuf->b_p_iminsert;
}
-/*
- * Set the global value for 'imsearch' to the local value.
- */
+/// Set the global value for 'imsearch' to the local value.
void set_imsearch_global(void)
{
p_imsearch = curbuf->b_p_imsearch;
@@ -6426,10 +6553,8 @@ void ExpandOldSetting(int *num_file, char_u ***file)
*num_file = 1;
}
-/*
- * Get the value for the numeric or string option *opp in a nice format into
- * NameBuff[]. Must not be called with a hidden option!
- */
+/// Get the value for the numeric or string option///opp in a nice format into
+/// NameBuff[]. Must not be called with a hidden option!
static void
option_value2string(
vimoption_T *opp,
@@ -6482,21 +6607,18 @@ static int wc_use_keyname(char_u *varp, long *wcp)
return false;
}
-/*
- * Any character has an equivalent 'langmap' character. This is used for
- * keyboards that have a special language mode that sends characters above
- * 128 (although other characters can be translated too). The "to" field is a
- * Vim command character. This avoids having to switch the keyboard back to
- * ASCII mode when leaving Insert mode.
- *
- * langmap_mapchar[] maps any of 256 chars to an ASCII char used for Vim
- * commands.
- * langmap_mapga.ga_data is a sorted table of langmap_entry_T.
- * This does the same as langmap_mapchar[] for characters >= 256.
- */
-/*
- * With multi-byte support use growarray for 'langmap' chars >= 256
- */
+/// Any character has an equivalent 'langmap' character. This is used for
+/// keyboards that have a special language mode that sends characters above
+/// 128 (although other characters can be translated too). The "to" field is a
+/// Vim command character. This avoids having to switch the keyboard back to
+/// ASCII mode when leaving Insert mode.
+///
+/// langmap_mapchar[] maps any of 256 chars to an ASCII char used for Vim
+/// commands.
+/// langmap_mapga.ga_data is a sorted table of langmap_entry_T.
+/// This does the same as langmap_mapchar[] for characters >= 256.
+///
+/// With multi-byte support use growarray for 'langmap' chars >= 256
typedef struct {
int from;
int to;
@@ -6504,10 +6626,8 @@ typedef struct {
static garray_T langmap_mapga = GA_EMPTY_INIT_VALUE;
-/*
- * Search for an entry in "langmap_mapga" for "from". If found set the "to"
- * field. If not found insert a new entry at the appropriate location.
- */
+/// Search for an entry in "langmap_mapga" for "from". If found set the "to"
+/// field. If not found insert a new entry at the appropriate location.
static void langmap_set_entry(int from, int to)
{
langmap_entry_T *entries = (langmap_entry_T *)(langmap_mapga.ga_data);
@@ -6542,9 +6662,7 @@ static void langmap_set_entry(int from, int to)
entries[0].to = to;
}
-/*
- * Apply 'langmap' to multi-byte character "c" and return the result.
- */
+/// Apply 'langmap' to multi-byte character "c" and return the result.
int langmap_adjust_mb(int c)
{
langmap_entry_T *entries = (langmap_entry_T *)(langmap_mapga.ga_data);
@@ -6575,10 +6693,8 @@ static void langmap_init(void)
ga_init(&langmap_mapga, sizeof(langmap_entry_T), 8);
}
-/*
- * Called when langmap option is set; the language map can be
- * changed at any time!
- */
+/// Called when langmap option is set; the language map can be
+/// changed at any time!
static void langmap_set(void)
{
char_u *p;
@@ -6681,9 +6797,7 @@ bool shortmess(int x)
&& vim_strchr((char_u *)SHM_ALL_ABBREVIATIONS, x) != NULL)));
}
-/*
- * paste_option_changed() - Called after p_paste was set or reset.
- */
+/// paste_option_changed() - Called after p_paste was set or reset.
static void paste_option_changed(void)
{
static int old_p_paste = false;
@@ -6830,9 +6944,7 @@ void reset_option_was_set(const char *name)
}
}
-/*
- * fill_breakat_flags() -- called when 'breakat' changes value.
- */
+/// fill_breakat_flags() -- called when 'breakat' changes value.
static void fill_breakat_flags(void)
{
char_u *p;
@@ -6849,12 +6961,10 @@ static void fill_breakat_flags(void)
}
}
-/*
- * Check an option that can be a range of string values.
- *
- * Return OK for correct value, FAIL otherwise.
- * Empty is always OK.
- */
+/// Check an option that can be a range of string values.
+///
+/// Return OK for correct value, FAIL otherwise.
+/// Empty is always OK.
static int check_opt_strings(
char_u *val,
char **values,
@@ -6864,13 +6974,11 @@ static int check_opt_strings(
return opt_strings_flags(val, values, NULL, list);
}
-/*
- * Handle an option that can be a range of string values.
- * Set a flag in "*flagp" for each string present.
- *
- * Return OK for correct value, FAIL otherwise.
- * Empty is always OK.
- */
+/// Handle an option that can be a range of string values.
+/// Set a flag in "*flagp" for each string present.
+///
+/// Return OK for correct value, FAIL otherwise.
+/// Empty is always OK.
static int opt_strings_flags(
char_u *val, // new value
char **values, // array of valid string values
@@ -6903,9 +7011,7 @@ static int opt_strings_flags(
return OK;
}
-/*
- * Read the 'wildmode' option, fill wim_flags[].
- */
+/// Read the 'wildmode' option, fill wim_flags[].
static int check_opt_wim(void)
{
char_u new_wim_flags[4];
@@ -6928,6 +7034,8 @@ static int check_opt_wim(void)
new_wim_flags[idx] |= WIM_FULL;
} else if (i == 4 && STRNCMP(p, "list", 4) == 0) {
new_wim_flags[idx] |= WIM_LIST;
+ } else if (i == 8 && STRNCMP(p, "lastused", 8) == 0) {
+ new_wim_flags[idx] |= WIM_BUFLASTUSED;
} else {
return FAIL;
}
@@ -6956,25 +7064,24 @@ static int check_opt_wim(void)
return OK;
}
-/*
- * Check if backspacing over something is allowed.
- * The parameter what is one of the following: whatBS_INDENT, BS_EOL
- * or BS_START
- */
+/// Check if backspacing over something is allowed.
+/// The parameter what is one of the following: whatBS_INDENT, BS_EOL
+/// or BS_START
bool can_bs(int what)
{
+ if (what == BS_START && bt_prompt(curbuf)) {
+ return false;
+ }
switch (*p_bs) {
- case '2': return true;
- case '1': return what != BS_START;
- case '0': return false;
+ case '2': return true;
+ case '1': return what != BS_START;
+ case '0': return false;
}
return vim_strchr(p_bs, what) != NULL;
}
-/*
- * Save the current values of 'fileformat' and 'fileencoding', so that we know
- * the file must be considered changed when the value is different.
- */
+/// Save the current values of 'fileformat' and 'fileencoding', so that we know
+/// the file must be considered changed when the value is different.
void save_file_ff(buf_T *buf)
{
buf->b_start_ffc = *buf->b_p_ff;
@@ -7024,18 +7131,14 @@ bool file_ff_differs(buf_T *buf, bool ignore_empty)
return STRCMP(buf->b_start_fenc, buf->b_p_fenc) != 0;
}
-/*
- * return OK if "p" is a valid fileformat name, FAIL otherwise.
- */
+/// return OK if "p" is a valid fileformat name, FAIL otherwise.
int check_ff_value(char_u *p)
{
return check_opt_strings(p, p_ff_values, false);
}
-/*
- * Return the effective shiftwidth value for current buffer, using the
- * 'tabstop' value when 'shiftwidth' is zero.
- */
+/// Return the effective shiftwidth value for current buffer, using the
+/// 'tabstop' value when 'shiftwidth' is zero.
int get_sw_value(buf_T *buf)
{
long result = buf->b_p_sw ? buf->b_p_sw : buf->b_p_ts;
@@ -7043,8 +7146,8 @@ int get_sw_value(buf_T *buf)
return (int)result;
}
-// Return the effective softtabstop value for the current buffer,
-// using the effective shiftwidth value when 'softtabstop' is negative.
+/// Return the effective softtabstop value for the current buffer,
+/// using the effective shiftwidth value when 'softtabstop' is negative.
int get_sts_value(void)
{
long result = curbuf->b_p_sts < 0 ? get_sw_value(curbuf) : curbuf->b_p_sts;
@@ -7052,12 +7155,10 @@ int get_sts_value(void)
return (int)result;
}
-/*
- * Check matchpairs option for "*initc".
- * If there is a match set "*initc" to the matching character and "*findc" to
- * the opposite character. Set "*backwards" to the direction.
- * When "switchit" is true swap the direction.
- */
+/// Check matchpairs option for "*initc".
+/// If there is a match set "*initc" to the matching character and "*findc" to
+/// the opposite character. Set "*backwards" to the direction.
+/// When "switchit" is true swap the direction.
void find_mps_values(int *initc, int *findc, int *backwards, int switchit)
{
char_u *ptr = curbuf->b_p_mps;
@@ -7129,9 +7230,9 @@ static bool briopt_check(win_T *wp)
}
}
- wp->w_p_brishift = bri_shift;
- wp->w_p_brimin = bri_min;
- wp->w_p_brisbr = bri_sbr;
+ wp->w_briopt_shift = bri_shift;
+ wp->w_briopt_min = bri_min;
+ wp->w_briopt_sbr = bri_sbr;
return true;
}
@@ -7162,12 +7263,13 @@ int get_fileformat(buf_T *buf)
/// argument.
///
/// @param eap can be NULL!
-int get_fileformat_force(buf_T *buf, exarg_T *eap)
+int get_fileformat_force(const buf_T *buf, const exarg_T *eap)
+ FUNC_ATTR_NONNULL_ARG(1)
{
int c;
if (eap != NULL && eap->force_ff != 0) {
- c = eap->cmd[eap->force_ff];
+ c = eap->force_ff;
} else {
if ((eap != NULL && eap->force_bin != 0)
? (eap->force_bin == FORCE_BIN) : buf->b_p_bin) {
@@ -7347,3 +7449,21 @@ dict_T *get_winbuf_options(const int bufopt)
return d;
}
+
+/// Return the effective 'scrolloff' value for the current window, using the
+/// global value when appropriate.
+long get_scrolloff_value(void)
+{
+ // Disallow scrolloff in terminal-mode. #11915
+ if (State & TERM_FOCUS) {
+ return 0;
+ }
+ return curwin->w_p_so < 0 ? p_so : curwin->w_p_so;
+}
+
+/// Return the effective 'sidescrolloff' value for the current window, using the
+/// global value when appropriate.
+long get_sidescrolloff_value(void)
+{
+ return curwin->w_p_siso < 0 ? p_siso : curwin->w_p_siso;
+}
diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h
index 08df495250..ecaa941082 100644
--- a/src/nvim/option_defs.h
+++ b/src/nvim/option_defs.h
@@ -277,6 +277,7 @@ enum {
#define WIM_FULL 1
#define WIM_LONGEST 2
#define WIM_LIST 4
+#define WIM_BUFLASTUSED 8
// arguments for can_bs()
#define BS_INDENT 'i' // "Indent"
@@ -371,8 +372,9 @@ EXTERN long p_columns; // 'columns'
EXTERN int p_confirm; // 'confirm'
EXTERN int p_cp; // 'compatible'
EXTERN char_u *p_cot; // 'completeopt'
-EXTERN long p_ph; // 'pumheight'
EXTERN long p_pb; // 'pumblend'
+EXTERN long p_ph; // 'pumheight'
+EXTERN long p_pw; // 'pumwidth'
EXTERN char_u *p_cpo; // 'cpoptions'
EXTERN char_u *p_csprg; // 'cscopeprg'
EXTERN int p_csre; // 'cscoperelative'
@@ -473,6 +475,12 @@ EXTERN char_u *p_isf; // 'isfname'
EXTERN char_u *p_isi; // 'isident'
EXTERN char_u *p_isp; // 'isprint'
EXTERN int p_js; // 'joinspaces'
+EXTERN char_u *p_jop; // 'jumpooptions'
+EXTERN unsigned jop_flags;
+#ifdef IN_OPTION_C
+static char *(p_jop_values[]) = { "stack", NULL };
+#endif
+#define JOP_STACK 0x01
EXTERN char_u *p_kp; // 'keywordprg'
EXTERN char_u *p_km; // 'keymodel'
EXTERN char_u *p_langmap; // 'langmap'
@@ -484,6 +492,7 @@ EXTERN long p_linespace; // 'linespace'
EXTERN char_u *p_lispwords; // 'lispwords'
EXTERN long p_ls; // 'laststatus'
EXTERN long p_stal; // 'showtabline'
+EXTERN char_u *p_lcs; // 'listchars'
EXTERN int p_lz; // 'lazyredraw'
EXTERN int p_lpl; // 'loadplugins'
@@ -518,10 +527,18 @@ EXTERN long p_pyx; // 'pyxversion'
EXTERN char_u *p_rdb; // 'redrawdebug'
EXTERN unsigned rdb_flags;
# ifdef IN_OPTION_C
-static char *(p_rdb_values[]) = { "compositor", "nothrottle", NULL };
+static char *(p_rdb_values[]) = {
+ "compositor",
+ "nothrottle",
+ "invalid",
+ "nodelta",
+ NULL
+};
# endif
# define RDB_COMPOSITOR 0x001
# define RDB_NOTHROTTLE 0x002
+# define RDB_INVALID 0x004
+# define RDB_NODELTA 0x008
EXTERN long p_rdt; // 'redrawtime'
EXTERN int p_remap; // 'remap'
@@ -561,8 +578,8 @@ static char *(p_ssop_values[]) = {
# define SSOP_HELP 0x040
# define SSOP_BLANK 0x080
# define SSOP_GLOBALS 0x100
-# define SSOP_SLASH 0x200
-# define SSOP_UNIX 0x400
+# define SSOP_SLASH 0x200 // Deprecated, always set.
+# define SSOP_UNIX 0x400 // Deprecated, always set.
# define SSOP_SESDIR 0x800
# define SSOP_CURDIR 0x1000
# define SSOP_FOLDS 0x2000
@@ -603,13 +620,14 @@ EXTERN char_u *p_swb; // 'switchbuf'
EXTERN unsigned swb_flags;
#ifdef IN_OPTION_C
static char *(p_swb_values[]) =
- { "useopen", "usetab", "split", "newtab", "vsplit", NULL };
+ { "useopen", "usetab", "split", "newtab", "vsplit", "uselast", NULL };
#endif
#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 int p_tbs; ///< 'tagbsearch'
EXTERN char_u *p_tc; ///< 'tagcase'
EXTERN unsigned tc_flags; ///< flags from 'tagcase'
@@ -644,6 +662,7 @@ EXTERN long p_ul; ///< 'undolevels'
EXTERN long p_ur; ///< 'undoreload'
EXTERN long p_uc; ///< 'updatecount'
EXTERN long p_ut; ///< 'updatetime'
+EXTERN char_u *p_fcs; ///< 'fillchar'
EXTERN char_u *p_shada; ///< 'shada'
EXTERN char *p_shadafile; ///< 'shadafile'
EXTERN char_u *p_vdir; ///< 'viewdir'
@@ -772,6 +791,7 @@ enum {
, BV_SUA
, BV_SW
, BV_SWF
+ , BV_TFU
, BV_TAGS
, BV_TC
, BV_TS
@@ -816,6 +836,8 @@ enum {
, WV_RLC
, WV_SCBIND
, WV_SCROLL
+ , WV_SISO
+ , WV_SO
, WV_SPELL
, WV_CUC
, WV_CUL
diff --git a/src/nvim/options.lua b/src/nvim/options.lua
index 52e788944b..60a38dc2e3 100644
--- a/src/nvim/options.lua
+++ b/src/nvim/options.lua
@@ -612,7 +612,7 @@ return {
alloced=true,
redraw={'current_window'},
varname='p_dip',
- defaults={if_true={vi="internal,filler"}}
+ defaults={if_true={vi="internal,filler,closeoff"}}
},
{
full_name='digraph', abbreviation='dg',
@@ -804,11 +804,12 @@ return {
},
{
full_name='fillchars', abbreviation='fcs',
- type='string', list='onecomma', scope={'window'},
+ type='string', list='onecomma', scope={'global', 'window'},
deny_duplicates=true,
vi_def=true,
alloced=true,
redraw={'current_window'},
+ varname='p_fcs',
defaults={if_true={vi=''}}
},
{
@@ -830,10 +831,11 @@ return {
},
{
full_name='foldcolumn', abbreviation='fdc',
- type='number', scope={'window'},
+ type='string', scope={'window'},
vi_def=true,
+ alloced=true,
redraw={'current_window'},
- defaults={if_true={vi=false}}
+ defaults={if_true={vi="0"}}
},
{
full_name='foldenable', abbreviation='fen',
@@ -1269,7 +1271,11 @@ return {
deny_duplicates=true,
vi_def=true,
varname='p_isi',
- defaults={if_true={vi="@,48-57,_,192-255"}}
+ defaults={
+ condition='WIN32',
+ if_true={vi="@,48-57,_,128-167,224-235"},
+ if_false={vi="@,48-57,_,192-255"}
+ }
},
{
full_name='iskeyword', abbreviation='isk',
@@ -1299,6 +1305,14 @@ return {
defaults={if_true={vi=true}}
},
{
+ full_name='jumpoptions', abbreviation='jop',
+ type='string', list='onecomma', scope={'global'},
+ deny_duplicates=true,
+ varname='p_jop',
+ vim=true,
+ defaults={if_true={vim=''}}
+ },
+ {
full_name='keymap', abbreviation='kmp',
type='string', scope={'buffer'},
normal_fname_chars=true,
@@ -1420,11 +1434,12 @@ return {
},
{
full_name='listchars', abbreviation='lcs',
- type='string', list='onecomma', scope={'window'},
+ type='string', list='onecomma', scope={'global', 'window'},
deny_duplicates=true,
vim=true,
alloced=true,
redraw={'current_window'},
+ varname='p_lcs',
defaults={if_true={vi="eol:$", vim="tab:> ,trail:-,nbsp:+"}}
},
{
@@ -1806,6 +1821,14 @@ return {
defaults={if_true={vi=true}}
},
{
+ full_name='pumblend', abbreviation='pb',
+ type='number', scope={'global'},
+ vi_def=true,
+ redraw={'ui_option'},
+ varname='p_pb',
+ defaults={if_true={vi=0}}
+ },
+ {
full_name='pumheight', abbreviation='ph',
type='number', scope={'global'},
vi_def=true,
@@ -1813,12 +1836,11 @@ return {
defaults={if_true={vi=0}}
},
{
- full_name='pumblend', abbreviation='pb',
+ full_name='pumwidth', abbreviation='pw',
type='number', scope={'global'},
vi_def=true,
- redraw={'ui_option'},
- varname='p_pb',
- defaults={if_true={vi=0}}
+ varname='p_pw',
+ defaults={if_true={vi=15}}
},
{
full_name='pyxversion', abbreviation='pyx',
@@ -1972,7 +1994,7 @@ return {
},
{
full_name='scrolloff', abbreviation='so',
- type='number', scope={'global'},
+ type='number', scope={'global', 'window'},
vi_def=true,
vim=true,
redraw={'all_windows'},
@@ -2211,10 +2233,10 @@ return {
},
{
full_name='sidescrolloff', abbreviation='siso',
- type='number', scope={'global'},
+ type='number', scope={'global', 'window'},
vi_def=true,
vim=true,
- redraw={'current_buffer'},
+ redraw={'all_windows'},
varname='p_siso',
defaults={if_true={vi=0}}
},
@@ -2323,9 +2345,9 @@ return {
full_name='startofline', abbreviation='sol',
type='bool', scope={'global'},
vi_def=true,
- vim=true,
+ vim=false,
varname='p_sol',
- defaults={if_true={vi=true}}
+ defaults={if_true={vi=false}}
},
{
full_name='statusline', abbreviation='stl',
@@ -2389,6 +2411,14 @@ return {
defaults={if_true={vi=""}}
},
{
+ full_name='tagfunc', abbreviation='tfu',
+ type='string', scope={'buffer'},
+ vim=true,
+ vi_def=true,
+ varname='p_tfu',
+ defaults={if_true={vi=""}}
+ },
+ {
full_name='tabline', abbreviation='tal',
type='string', scope={'global'},
vi_def=true,
@@ -2561,6 +2591,7 @@ return {
type='bool', scope={'global'},
vi_def=true,
vim=true,
+ redraw={'ui_option'},
varname='p_ttimeout',
defaults={if_true={vi=true}}
},
@@ -2568,6 +2599,7 @@ return {
full_name='ttimeoutlen', abbreviation='ttm',
type='number', scope={'global'},
vi_def=true,
+ redraw={'ui_option'},
varname='p_ttm',
defaults={if_true={vi=50}}
},
diff --git a/src/nvim/os/env.c b/src/nvim/os/env.c
index f5dbf0694e..082ad58223 100644
--- a/src/nvim/os/env.c
+++ b/src/nvim/os/env.c
@@ -44,6 +44,16 @@ 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
const char *os_getenv(const char *name)
@@ -55,6 +65,7 @@ const char *os_getenv(const char *name)
return NULL;
}
uv_mutex_lock(&mutex);
+ int r = 0;
if (pmap_has(cstr_t)(envmap, name)
&& !!(e = (char *)pmap_get(cstr_t)(envmap, name))) {
if (e[0] != '\0') {
@@ -67,7 +78,7 @@ const char *os_getenv(const char *name)
pmap_del2(envmap, name);
}
e = xmalloc(size);
- int r = uv_os_getenv(name, e, &size);
+ r = uv_os_getenv(name, e, &size);
if (r == UV_ENOBUFS) {
e = xrealloc(e, size);
r = uv_os_getenv(name, e, &size);
@@ -75,14 +86,15 @@ const char *os_getenv(const char *name)
if (r != 0 || size == 0 || e[0] == '\0') {
xfree(e);
e = NULL;
- if (r != 0 && r != UV_ENOENT && r != UV_UNKNOWN) {
- ELOG("uv_os_getenv(%s) failed: %d %s", name, r, uv_err_name(r));
- }
goto end;
}
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));
+ }
return (e == NULL || size == 0 || e[0] == '\0') ? NULL : e;
}
@@ -102,9 +114,6 @@ bool os_env_exists(const char *name)
assert(r != UV_EINVAL);
if (r != 0 && r != UV_ENOENT && r != UV_ENOBUFS) {
ELOG("uv_os_getenv(%s) failed: %d %s", name, r, uv_err_name(r));
-#ifdef WIN32
- return (r == UV_UNKNOWN);
-#endif
}
return (r == 0 || r == UV_ENOBUFS);
}
@@ -135,15 +144,25 @@ int os_setenv(const char *name, const char *value, int overwrite)
}
#endif
uv_mutex_lock(&mutex);
- int r = uv_os_setenv(name, value);
+ int r;
+#ifdef WIN32
+ // libintl uses getenv() for LC_ALL/LANG/etc., so we must use _putenv_s().
+ if (striequal(name, "LC_ALL") || striequal(name, "LANGUAGE")
+ || striequal(name, "LANG") || striequal(name, "LC_MESSAGES")) {
+ r = _putenv_s(name, value); // NOLINT
+ assert(r == 0);
+ }
+#endif
+ r = uv_os_setenv(name, value);
assert(r != UV_EINVAL);
// Destroy the old map item. Do this AFTER uv_os_setenv(), because `value`
// could be a previous os_getenv() result.
pmap_del2(envmap, name);
+ // 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));
}
- uv_mutex_unlock(&mutex);
return r == 0 ? 0 : -1;
}
@@ -157,46 +176,148 @@ int os_unsetenv(const char *name)
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));
}
- uv_mutex_unlock(&mutex);
return r == 0 ? 0 : -1;
}
+/// Returns number of variables in the current environment variables block
+size_t os_get_fullenv_size(void)
+{
+ size_t len = 0;
+#ifdef _WIN32
+ wchar_t *envstrings = GetEnvironmentStringsW();
+ wchar_t *p = envstrings;
+ size_t l;
+ if (!envstrings) {
+ return len;
+ }
+ // GetEnvironmentStringsW() result has this format:
+ // var1=value1\0var2=value2\0...varN=valueN\0\0
+ while ((l = wcslen(p)) != 0) {
+ p += l + 1;
+ len++;
+ }
+
+ FreeEnvironmentStringsW(envstrings);
+#else
+# if defined(HAVE__NSGETENVIRON)
+ char **environ = *_NSGetEnviron();
+# else
+ extern char **environ;
+# endif
+
+ while (environ[len] != NULL) {
+ len++;
+ }
+
+#endif
+ return len;
+}
+
+void os_free_fullenv(char **env)
+{
+ if (!env) { return; }
+ for (char **it = env; *it; it++) {
+ XFREE_CLEAR(*it);
+ }
+ xfree(env);
+}
+
+/// Copies the current environment variables into the given array, `env`. Each
+/// array element is of the form "NAME=VALUE".
+/// Result must be freed by the caller.
+///
+/// @param[out] env array to populate with environment variables
+/// @param env_size size of `env`, @see os_fullenv_size
+void os_copy_fullenv(char **env, size_t env_size)
+{
+#ifdef _WIN32
+ wchar_t *envstrings = GetEnvironmentStringsW();
+ if (!envstrings) {
+ return;
+ }
+ wchar_t *p = envstrings;
+ size_t i = 0;
+ size_t l;
+ // GetEnvironmentStringsW() result has this format:
+ // var1=value1\0var2=value2\0...varN=valueN\0\0
+ while ((l = wcslen(p)) != 0 && i < env_size) {
+ char *utf8_str;
+ int conversion_result = utf16_to_utf8(p, -1, &utf8_str);
+ if (conversion_result != 0) {
+ EMSG2("utf16_to_utf8 failed: %d", conversion_result);
+ break;
+ }
+ p += l + 1;
+
+ env[i] = utf8_str;
+ i++;
+ }
+
+ FreeEnvironmentStringsW(envstrings);
+#else
+# if defined(HAVE__NSGETENVIRON)
+ char **environ = *_NSGetEnviron();
+# else
+ extern char **environ;
+# endif
+
+ for (size_t i = 0; i < env_size && environ[i] != NULL; i++) {
+ env[i] = xstrdup(environ[i]);
+ }
+#endif
+}
+
+/// Copy value of the environment variable at `index` in the current
+/// environment variables block.
+/// Result must be freed by the caller.
+///
+/// @param index nth item in environment variables block
+/// @return [allocated] environment variable's value, or NULL
char *os_getenvname_at_index(size_t index)
{
#ifdef _WIN32
- wchar_t *env = GetEnvironmentStringsW();
- if (!env) {
+ wchar_t *envstrings = GetEnvironmentStringsW();
+ if (!envstrings) {
return NULL;
}
+ wchar_t *p = envstrings;
char *name = NULL;
- size_t current_index = 0;
+ size_t i = 0;
+ size_t l;
// GetEnvironmentStringsW() result has this format:
// var1=value1\0var2=value2\0...varN=valueN\0\0
- for (wchar_t *it = env; *it != L'\0' || *(it + 1) != L'\0'; it++) {
- if (index == current_index) {
+ while ((l = wcslen(p)) != 0 && i <= index) {
+ if (i == index) {
char *utf8_str;
- int conversion_result = utf16_to_utf8(it, -1, &utf8_str);
+ int conversion_result = utf16_to_utf8(p, -1, &utf8_str);
if (conversion_result != 0) {
EMSG2("utf16_to_utf8 failed: %d", conversion_result);
break;
}
- size_t namesize = 0;
- while (utf8_str[namesize] != '=' && utf8_str[namesize] != NUL) {
- namesize++;
- }
- name = (char *)vim_strnsave((char_u *)utf8_str, namesize);
+
+ // 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),
+ '=');
+ assert(end != NULL);
+ ptrdiff_t len = end - utf8_str;
+ assert(len > 0);
+ name = xstrndup(utf8_str, (size_t)len);
xfree(utf8_str);
break;
}
- if (*it == L'\0') {
- current_index++;
- }
+
+ // Advance past the name and NUL
+ p += l + 1;
+ i++;
}
- FreeEnvironmentStringsW(env);
+ FreeEnvironmentStringsW(envstrings);
return name;
#else
# if defined(HAVE__NSGETENVIRON)
@@ -204,19 +325,20 @@ char *os_getenvname_at_index(size_t index)
# else
extern char **environ;
# endif
- // Check if index is inside the environ array and is not the last element.
+
+ // check if index is inside the environ array
for (size_t i = 0; i <= index; i++) {
if (environ[i] == NULL) {
return NULL;
}
}
char *str = environ[index];
- size_t namesize = 0;
- while (str[namesize] != '=' && str[namesize] != NUL) {
- namesize++;
- }
- char *name = (char *)vim_strnsave((char_u *)str, namesize);
- return name;
+ assert(str != NULL);
+ const char * const end = strchr(str, '=');
+ assert(end != NULL);
+ ptrdiff_t len = end - str;
+ assert(len > 0);
+ return xstrndup(str, (size_t)len);
#endif
}
@@ -725,6 +847,20 @@ const void *vim_env_iter_rev(const char delim,
}
}
+
+/// @param[out] exe_name should be at least MAXPATHL in size
+void vim_get_prefix_from_exepath(char *exe_name)
+{
+ // TODO(bfredl): param could have been written as "char exe_name[MAXPATHL]"
+ // but c_grammar.lua does not recognize it (yet).
+ xstrlcpy(exe_name, (char *)get_vim_var_str(VV_PROGPATH),
+ MAXPATHL * sizeof(*exe_name));
+ char *path_end = (char *)path_tail_with_sep((char_u *)exe_name);
+ *path_end = '\0'; // remove the trailing "nvim.exe"
+ path_end = (char *)path_tail((char_u *)exe_name);
+ *path_end = '\0'; // remove the trailing "bin/"
+}
+
/// Vim getenv() wrapper with special handling of $HOME, $VIM, $VIMRUNTIME,
/// allowing the user to override the Nvim runtime directory at runtime.
/// Result must be freed by the caller.
@@ -780,12 +916,7 @@ char *vim_getenv(const char *name)
char exe_name[MAXPATHL];
// Find runtime path relative to the nvim binary: ../share/nvim/runtime
if (vim_path == NULL) {
- xstrlcpy(exe_name, (char *)get_vim_var_str(VV_PROGPATH),
- sizeof(exe_name));
- char *path_end = (char *)path_tail_with_sep((char_u *)exe_name);
- *path_end = '\0'; // remove the trailing "nvim.exe"
- path_end = (char *)path_tail((char_u *)exe_name);
- *path_end = '\0'; // remove the trailing "bin/"
+ vim_get_prefix_from_exepath(exe_name);
if (append_path(
exe_name,
"share" _PATHSEPSTR "nvim" _PATHSEPSTR "runtime" _PATHSEPSTR,
diff --git a/src/nvim/os/fs.c b/src/nvim/os/fs.c
index 6d76cc3613..a3bef3389c 100644
--- a/src/nvim/os/fs.c
+++ b/src/nvim/os/fs.c
@@ -1159,6 +1159,30 @@ bool os_fileid_equal_fileinfo(const FileID *file_id,
&& file_id->device_id == file_info->stat.st_dev;
}
+/// Return the canonicalized absolute pathname.
+///
+/// @param[in] name Filename to be canonicalized.
+/// @param[out] buf Buffer to store the canonicalized values. A minimum length
+// of MAXPATHL+1 is required. If it is NULL, memory is
+// allocated. In that case, the caller should deallocate this
+// buffer.
+///
+/// @return pointer to the buf on success, or NULL.
+char *os_realpath(const char *name, char *buf)
+ FUNC_ATTR_NONNULL_ARG(1)
+{
+ uv_fs_t request;
+ int result = uv_fs_realpath(&fs_loop, &request, name, NULL);
+ if (result == kLibuvSuccess) {
+ if (buf == NULL) {
+ buf = xmallocz(MAXPATHL);
+ }
+ xstrlcpy(buf, request.ptr, MAXPATHL + 1);
+ }
+ uv_fs_req_cleanup(&request);
+ return result == kLibuvSuccess ? buf : NULL;
+}
+
#ifdef WIN32
# include <shlobj.h>
@@ -1245,4 +1269,49 @@ shortcut_end:
return rfname;
}
+#define is_path_sep(c) ((c) == L'\\' || (c) == L'/')
+/// Returns true if the path contains a reparse point (junction or symbolic
+/// link). Otherwise false in returned.
+bool os_is_reparse_point_include(const char *path)
+{
+ wchar_t *p, *q, *utf16_path;
+ wchar_t buf[MAX_PATH];
+ DWORD attr;
+ bool result = false;
+
+ const int r = utf8_to_utf16(path, -1, &utf16_path);
+ if (r != 0) {
+ EMSG2("utf8_to_utf16 failed: %d", r);
+ return false;
+ }
+
+ p = utf16_path;
+ if (isalpha(p[0]) && p[1] == L':' && is_path_sep(p[2])) {
+ p += 3;
+ } else if (is_path_sep(p[0]) && is_path_sep(p[1])) {
+ p += 2;
+ }
+
+ while (*p != L'\0') {
+ q = wcspbrk(p, L"\\/");
+ if (q == NULL) {
+ p = q = utf16_path + wcslen(utf16_path);
+ } else {
+ p = q + 1;
+ }
+ if (q - utf16_path >= MAX_PATH) {
+ break;
+ }
+ wcsncpy(buf, utf16_path, (size_t)(q - utf16_path));
+ buf[q - utf16_path] = L'\0';
+ attr = GetFileAttributesW(buf);
+ if (attr != INVALID_FILE_ATTRIBUTES
+ && (attr & FILE_ATTRIBUTE_REPARSE_POINT) != 0) {
+ result = true;
+ break;
+ }
+ }
+ xfree(utf16_path);
+ return result;
+}
#endif
diff --git a/src/nvim/os/input.c b/src/nvim/os/input.c
index c1580c5fc3..b7878d9da8 100644
--- a/src/nvim/os/input.c
+++ b/src/nvim/os/input.c
@@ -188,8 +188,13 @@ size_t input_enqueue(String keys)
char *ptr = keys.data;
char *end = ptr + keys.size;
- while (rbuffer_space(input_buffer) >= 6 && ptr < end) {
- uint8_t buf[6] = { 0 };
+ while (rbuffer_space(input_buffer) >= 19 && ptr < end) {
+ // A "<x>" form occupies at least 1 characters, and produces up
+ // to 19 characters (1 + 5 * 3 for the char and 3 for a modifier).
+ // In the case of K_SPECIAL(0x80) or CSI(0x9B), 3 bytes are escaped and
+ // needed, but since the keys are UTF-8, so the first byte cannot be
+ // K_SPECIAL(0x80) or CSI(0x9B).
+ uint8_t buf[19] = { 0 };
unsigned int new_size
= trans_special((const uint8_t **)&ptr, (size_t)(end - ptr), buf, true,
false);
diff --git a/src/nvim/os/os_win_console.c b/src/nvim/os/os_win_console.c
new file mode 100644
index 0000000000..18dcfeafa0
--- /dev/null
+++ b/src/nvim/os/os_win_console.c
@@ -0,0 +1,74 @@
+// 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/vim.h"
+#include "nvim/os/input.h"
+#include "nvim/os/os_win_console.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "os/os_win_console.c.generated.h"
+#endif
+
+
+int os_get_conin_fd(void)
+{
+ const HANDLE conin_handle = CreateFile("CONIN$",
+ GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_READ | FILE_SHARE_WRITE,
+ (LPSECURITY_ATTRIBUTES)NULL,
+ OPEN_EXISTING, 0, (HANDLE)NULL);
+ assert(conin_handle != INVALID_HANDLE_VALUE);
+ int conin_fd = _open_osfhandle((intptr_t)conin_handle, _O_RDONLY);
+ assert(conin_fd != -1);
+ return conin_fd;
+}
+
+void os_replace_stdin_to_conin(void)
+{
+ close(STDIN_FILENO);
+ const int conin_fd = os_get_conin_fd();
+ assert(conin_fd == STDIN_FILENO);
+}
+
+void os_replace_stdout_and_stderr_to_conout(void)
+{
+ const HANDLE conout_handle =
+ CreateFile("CONOUT$",
+ GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_READ | FILE_SHARE_WRITE,
+ (LPSECURITY_ATTRIBUTES)NULL,
+ OPEN_EXISTING, 0, (HANDLE)NULL);
+ assert(conout_handle != INVALID_HANDLE_VALUE);
+ close(STDOUT_FILENO);
+ const int conout_fd = _open_osfhandle((intptr_t)conout_handle, 0);
+ assert(conout_fd == STDOUT_FILENO);
+ close(STDERR_FILENO);
+ const int conerr_fd = _open_osfhandle((intptr_t)conout_handle, 0);
+ assert(conerr_fd == STDERR_FILENO);
+}
+
+void os_set_vtp(bool enable)
+{
+ static TriState is_legacy = kNone;
+ if (is_legacy == kNone) {
+ uv_tty_vtermstate_t state;
+ uv_tty_get_vterm_state(&state);
+ is_legacy = (state == UV_TTY_UNSUPPORTED) ? kTrue : kFalse;
+ }
+ if (!is_legacy && !os_has_vti()) {
+ uv_tty_set_vterm_state(enable ? UV_TTY_SUPPORTED : UV_TTY_UNSUPPORTED);
+ }
+}
+
+static bool os_has_vti(void)
+{
+ static TriState has_vti = kNone;
+ if (has_vti == kNone) {
+ HANDLE handle = (HANDLE)_get_osfhandle(input_global_fd());
+ DWORD dwMode;
+ if (handle != INVALID_HANDLE_VALUE && GetConsoleMode(handle, &dwMode)) {
+ has_vti = !!(dwMode & ENABLE_VIRTUAL_TERMINAL_INPUT) ? kTrue : kFalse;
+ }
+ }
+ return has_vti == kTrue;
+}
diff --git a/src/nvim/os/os_win_console.h b/src/nvim/os/os_win_console.h
new file mode 100644
index 0000000000..7b5800afa8
--- /dev/null
+++ b/src/nvim/os/os_win_console.h
@@ -0,0 +1,12 @@
+#ifndef NVIM_OS_OS_WIN_CONSOLE_H
+#define NVIM_OS_OS_WIN_CONSOLE_H
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "os/os_win_console.h.generated.h"
+#endif
+
+#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/pty_conpty_win.c b/src/nvim/os/pty_conpty_win.c
new file mode 100644
index 0000000000..5bcadd6490
--- /dev/null
+++ b/src/nvim/os/pty_conpty_win.c
@@ -0,0 +1,199 @@
+// 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/vim.h"
+#include "nvim/os/os.h"
+#include "nvim/os/pty_conpty_win.h"
+
+#ifndef EXTENDED_STARTUPINFO_PRESENT
+# define EXTENDED_STARTUPINFO_PRESENT 0x00080000
+#endif
+#ifndef PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE
+# define PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE 0x00020016
+#endif
+
+HRESULT (WINAPI *pCreatePseudoConsole)(COORD, HANDLE, HANDLE, DWORD, HPCON *);
+HRESULT (WINAPI *pResizePseudoConsole)(HPCON, COORD);
+void (WINAPI *pClosePseudoConsole)(HPCON);
+
+bool os_has_conpty_working(void)
+{
+ static TriState has_conpty = kNone;
+ if (has_conpty == kNone) {
+ has_conpty = os_dyn_conpty_init();
+ }
+
+ return has_conpty == kTrue;
+}
+
+TriState os_dyn_conpty_init(void)
+{
+ uv_lib_t kernel;
+ if (uv_dlopen("kernel32.dll", &kernel)) {
+ uv_dlclose(&kernel);
+ return kFalse;
+ }
+ static struct {
+ char *name;
+ FARPROC *ptr;
+ } conpty_entry[] = {
+ { "CreatePseudoConsole", (FARPROC *)&pCreatePseudoConsole },
+ { "ResizePseudoConsole", (FARPROC *)&pResizePseudoConsole },
+ { "ClosePseudoConsole", (FARPROC *)&pClosePseudoConsole },
+ { NULL, NULL }
+ };
+ for (int i = 0;
+ conpty_entry[i].name != NULL && conpty_entry[i].ptr != NULL; i++) {
+ if (uv_dlsym(&kernel, conpty_entry[i].name, (void **)conpty_entry[i].ptr)) {
+ uv_dlclose(&kernel);
+ return kFalse;
+ }
+ }
+ return kTrue;
+}
+
+conpty_t *os_conpty_init(char **in_name, char **out_name,
+ uint16_t width, uint16_t height)
+{
+ static int count = 0;
+ conpty_t *conpty_object = xcalloc(1, sizeof(*conpty_object));
+ const char *emsg = NULL;
+ HANDLE in_read = INVALID_HANDLE_VALUE;
+ HANDLE out_write = INVALID_HANDLE_VALUE;
+ char buf[MAXPATHL];
+ SECURITY_ATTRIBUTES sa = { 0 };
+ const DWORD mode = PIPE_ACCESS_INBOUND
+ | PIPE_ACCESS_OUTBOUND | FILE_FLAG_FIRST_PIPE_INSTANCE;
+
+ sa.nLength = sizeof(sa);
+ snprintf(buf, sizeof(buf), "\\\\.\\pipe\\nvim-term-in-%d-%d",
+ os_get_pid(), count);
+ *in_name = xstrdup(buf);
+ if ((in_read = CreateNamedPipeA(
+ *in_name,
+ mode,
+ PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
+ 1,
+ 0,
+ 0,
+ 30000,
+ &sa)) == INVALID_HANDLE_VALUE) {
+ emsg = "create input pipe failed";
+ goto failed;
+ }
+ snprintf(buf, sizeof(buf), "\\\\.\\pipe\\nvim-term-out-%d-%d",
+ os_get_pid(), count);
+ *out_name = xstrdup(buf);
+ if ((out_write = CreateNamedPipeA(
+ *out_name,
+ mode,
+ PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
+ 1,
+ 0,
+ 0,
+ 30000,
+ &sa)) == INVALID_HANDLE_VALUE) {
+ emsg = "create output pipe failed";
+ goto failed;
+ }
+ assert(width <= SHRT_MAX);
+ assert(height <= SHRT_MAX);
+ COORD size = { (int16_t)width, (int16_t)height };
+ HRESULT hr;
+ hr = pCreatePseudoConsole(size, in_read, out_write, 0, &conpty_object->pty);
+ if (FAILED(hr)) {
+ emsg = "create psudo console failed";
+ goto failed;
+ }
+
+ conpty_object->si_ex.StartupInfo.cb = sizeof(conpty_object->si_ex);
+ size_t bytes_required;
+ InitializeProcThreadAttributeList(NULL, 1, 0, & bytes_required);
+ conpty_object->si_ex.lpAttributeList =
+ (PPROC_THREAD_ATTRIBUTE_LIST)xmalloc(bytes_required);
+ if (!InitializeProcThreadAttributeList(
+ conpty_object->si_ex.lpAttributeList,
+ 1,
+ 0,
+ &bytes_required)) {
+ emsg = "InitializeProcThreadAttributeList failed";
+ goto failed;
+ }
+ if (!UpdateProcThreadAttribute(
+ conpty_object->si_ex.lpAttributeList,
+ 0,
+ PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE,
+ conpty_object->pty,
+ sizeof(conpty_object->pty),
+ NULL,
+ NULL)) {
+ emsg = "UpdateProcThreadAttribute failed";
+ goto failed;
+ }
+ count++;
+ goto finished;
+
+failed:
+ ELOG("os_conpty_init:%s : error code: %d",
+ emsg, os_translate_sys_error((int)GetLastError()));
+ os_conpty_free(conpty_object);
+ conpty_object = NULL;
+finished:
+ if (in_read != INVALID_HANDLE_VALUE) {
+ CloseHandle(in_read);
+ }
+ if (out_write != INVALID_HANDLE_VALUE) {
+ CloseHandle(out_write);
+ }
+ return conpty_object;
+}
+
+bool os_conpty_spawn(conpty_t *conpty_object, HANDLE *process_handle,
+ wchar_t *name, wchar_t *cmd_line, wchar_t *cwd,
+ wchar_t *env)
+{
+ PROCESS_INFORMATION pi = { 0 };
+ if (!CreateProcessW(
+ name,
+ cmd_line,
+ NULL,
+ NULL,
+ false,
+ EXTENDED_STARTUPINFO_PRESENT | CREATE_UNICODE_ENVIRONMENT,
+ env,
+ cwd,
+ &conpty_object->si_ex.StartupInfo,
+ &pi)) {
+ return false;
+ }
+ *process_handle = pi.hProcess;
+ return true;
+}
+
+void os_conpty_set_size(conpty_t *conpty_object,
+ uint16_t width, uint16_t height)
+{
+ assert(width <= SHRT_MAX);
+ assert(height <= SHRT_MAX);
+ COORD size = { (int16_t)width, (int16_t)height };
+ if (pResizePseudoConsole(conpty_object->pty, size) != S_OK) {
+ ELOG("ResizePseudoConsoel failed: error code: %d",
+ os_translate_sys_error((int)GetLastError()));
+ }
+}
+
+void os_conpty_free(conpty_t *conpty_object)
+{
+ if (conpty_object != NULL) {
+ if (conpty_object->si_ex.lpAttributeList != NULL) {
+ DeleteProcThreadAttributeList(conpty_object->si_ex.lpAttributeList);
+ xfree(conpty_object->si_ex.lpAttributeList);
+ }
+ if (conpty_object->pty != NULL) {
+ pClosePseudoConsole(conpty_object->pty);
+ }
+ }
+ xfree(conpty_object);
+}
diff --git a/src/nvim/os/pty_conpty_win.h b/src/nvim/os/pty_conpty_win.h
new file mode 100644
index 0000000000..c243db4fa5
--- /dev/null
+++ b/src/nvim/os/pty_conpty_win.h
@@ -0,0 +1,22 @@
+#ifndef NVIM_OS_PTY_CONPTY_WIN_H
+#define NVIM_OS_PTY_CONPTY_WIN_H
+
+#ifndef HPCON
+# define HPCON VOID *
+#endif
+
+extern HRESULT (WINAPI *pCreatePseudoConsole) // NOLINT(whitespace/parens)
+ (COORD, HANDLE, HANDLE, DWORD, HPCON *);
+extern HRESULT (WINAPI *pResizePseudoConsole)(HPCON, COORD);
+extern void (WINAPI *pClosePseudoConsole)(HPCON);
+
+typedef struct conpty {
+ HPCON pty;
+ STARTUPINFOEXW si_ex;
+} conpty_t;
+
+#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_win.c b/src/nvim/os/pty_process_win.c
index 183219bd3e..6f7100e846 100644
--- a/src/nvim/os/pty_process_win.c
+++ b/src/nvim/os/pty_process_win.c
@@ -12,6 +12,7 @@
#include "nvim/memory.h"
#include "nvim/mbyte.h" // for utf8_to_utf16, utf16_to_utf8
#include "nvim/os/pty_process_win.h"
+#include "nvim/os/pty_conpty_win.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "os/pty_process_win.c.generated.h"
@@ -23,6 +24,11 @@ static void CALLBACK pty_process_finish1(void *context, BOOLEAN unused)
PtyProcess *ptyproc = (PtyProcess *)context;
Process *proc = (Process *)ptyproc;
+ if (ptyproc->type == kConpty
+ && ptyproc->object.conpty != NULL) {
+ os_conpty_free(ptyproc->object.conpty);
+ ptyproc->object.conpty = NULL;
+ }
uv_timer_init(&proc->loop->uv, &ptyproc->wait_eof_timer);
ptyproc->wait_eof_timer.data = (void *)ptyproc;
uv_timer_start(&ptyproc->wait_eof_timer, wait_eof_timer_cb, 200, 200);
@@ -38,6 +44,7 @@ int pty_process_spawn(PtyProcess *ptyproc)
winpty_config_t *cfg = NULL;
winpty_spawn_config_t *spawncfg = NULL;
winpty_t *winpty_object = NULL;
+ conpty_t *conpty_object = NULL;
char *in_name = NULL;
char *out_name = NULL;
HANDLE process_handle = NULL;
@@ -49,29 +56,39 @@ int pty_process_spawn(PtyProcess *ptyproc)
assert(proc->err.closed);
- cfg = winpty_config_new(WINPTY_FLAG_ALLOW_CURPROC_DESKTOP_CREATION, &err);
- if (cfg == NULL) {
- emsg = "winpty_config_new failed";
- goto cleanup;
+ if (os_has_conpty_working()) {
+ if ((conpty_object =
+ os_conpty_init(&in_name, &out_name,
+ ptyproc->width, ptyproc->height)) != NULL) {
+ ptyproc->type = kConpty;
+ }
}
- winpty_config_set_initial_size(cfg, ptyproc->width, ptyproc->height);
- winpty_object = winpty_open(cfg, &err);
- if (winpty_object == NULL) {
- emsg = "winpty_open failed";
- goto cleanup;
- }
+ if (ptyproc->type == kWinpty) {
+ cfg = winpty_config_new(WINPTY_FLAG_ALLOW_CURPROC_DESKTOP_CREATION, &err);
+ if (cfg == NULL) {
+ emsg = "winpty_config_new failed";
+ goto cleanup;
+ }
- status = utf16_to_utf8(winpty_conin_name(winpty_object), -1, &in_name);
- if (status != 0) {
- emsg = "utf16_to_utf8(winpty_conin_name) failed";
- goto cleanup;
- }
+ winpty_config_set_initial_size(cfg, ptyproc->width, ptyproc->height);
+ winpty_object = winpty_open(cfg, &err);
+ if (winpty_object == NULL) {
+ emsg = "winpty_open failed";
+ goto cleanup;
+ }
- status = utf16_to_utf8(winpty_conout_name(winpty_object), -1, &out_name);
- if (status != 0) {
- emsg = "utf16_to_utf8(winpty_conout_name) failed";
- goto cleanup;
+ status = utf16_to_utf8(winpty_conin_name(winpty_object), -1, &in_name);
+ if (status != 0) {
+ emsg = "utf16_to_utf8(winpty_conin_name) failed";
+ goto cleanup;
+ }
+
+ status = utf16_to_utf8(winpty_conout_name(winpty_object), -1, &out_name);
+ if (status != 0) {
+ emsg = "utf16_to_utf8(winpty_conout_name) failed";
+ goto cleanup;
+ }
}
if (!proc->in.closed) {
@@ -107,32 +124,45 @@ int pty_process_spawn(PtyProcess *ptyproc)
goto cleanup;
}
- spawncfg = winpty_spawn_config_new(
- WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN,
- NULL, // Optional application name
- cmd_line,
- cwd,
- NULL, // Optional environment variables
- &err);
- if (spawncfg == NULL) {
- emsg = "winpty_spawn_config_new failed";
- goto cleanup;
- }
+ if (ptyproc->type == kConpty) {
+ if (!os_conpty_spawn(conpty_object,
+ &process_handle,
+ NULL,
+ cmd_line,
+ cwd,
+ NULL)) {
+ emsg = "os_conpty_spawn failed";
+ status = (int)GetLastError();
+ goto cleanup;
+ }
+ } else {
+ spawncfg = winpty_spawn_config_new(
+ WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN,
+ NULL, // Optional application name
+ cmd_line,
+ cwd,
+ NULL, // Optional environment variables
+ &err);
+ if (spawncfg == NULL) {
+ emsg = "winpty_spawn_config_new failed";
+ goto cleanup;
+ }
- DWORD win_err = 0;
- if (!winpty_spawn(winpty_object,
- spawncfg,
- &process_handle,
- NULL, // Optional thread handle
- &win_err,
- &err)) {
- if (win_err) {
- status = (int)win_err;
- emsg = "failed to spawn process";
- } else {
- emsg = "winpty_spawn failed";
+ DWORD win_err = 0;
+ if (!winpty_spawn(winpty_object,
+ spawncfg,
+ &process_handle,
+ NULL, // Optional thread handle
+ &win_err,
+ &err)) {
+ if (win_err) {
+ status = (int)win_err;
+ emsg = "failed to spawn process";
+ } else {
+ emsg = "winpty_spawn failed";
+ }
+ goto cleanup;
}
- goto cleanup;
}
proc->pid = (int)GetProcessId(process_handle);
@@ -152,9 +182,12 @@ int pty_process_spawn(PtyProcess *ptyproc)
uv_run(&proc->loop->uv, UV_RUN_ONCE);
}
- ptyproc->winpty_object = winpty_object;
+ (ptyproc->type == kConpty) ?
+ (void *)(ptyproc->object.conpty = conpty_object) :
+ (void *)(ptyproc->object.winpty = winpty_object);
ptyproc->process_handle = process_handle;
winpty_object = NULL;
+ conpty_object = NULL;
process_handle = NULL;
cleanup:
@@ -171,6 +204,7 @@ cleanup:
winpty_config_free(cfg);
winpty_spawn_config_free(spawncfg);
winpty_free(winpty_object);
+ os_conpty_free(conpty_object);
xfree(in_name);
xfree(out_name);
if (process_handle != NULL) {
@@ -192,8 +226,11 @@ void pty_process_resize(PtyProcess *ptyproc, uint16_t width,
uint16_t height)
FUNC_ATTR_NONNULL_ALL
{
- if (ptyproc->winpty_object != NULL) {
- winpty_set_size(ptyproc->winpty_object, width, height, NULL);
+ if (ptyproc->type == kConpty
+ && ptyproc->object.conpty != NULL) {
+ os_conpty_set_size(ptyproc->object.conpty, width, height);
+ } else if (ptyproc->object.winpty != NULL) {
+ winpty_set_size(ptyproc->object.winpty, width, height, NULL);
}
}
@@ -212,9 +249,10 @@ void pty_process_close(PtyProcess *ptyproc)
void pty_process_close_master(PtyProcess *ptyproc)
FUNC_ATTR_NONNULL_ALL
{
- if (ptyproc->winpty_object != NULL) {
- winpty_free(ptyproc->winpty_object);
- ptyproc->winpty_object = NULL;
+ if (ptyproc->type == kWinpty
+ && ptyproc->object.winpty != NULL) {
+ winpty_free(ptyproc->object.winpty);
+ ptyproc->object.winpty = NULL;
}
}
diff --git a/src/nvim/os/pty_process_win.h b/src/nvim/os/pty_process_win.h
index 1a4019e654..8ad5ba7286 100644
--- a/src/nvim/os/pty_process_win.h
+++ b/src/nvim/os/pty_process_win.h
@@ -6,12 +6,22 @@
#include "nvim/event/process.h"
#include "nvim/lib/queue.h"
+#include "nvim/os/pty_conpty_win.h"
+
+typedef enum {
+ kWinpty,
+ kConpty
+} PtyType;
typedef struct pty_process {
Process process;
char *term_name;
uint16_t width, height;
- winpty_t *winpty_object;
+ union {
+ winpty_t *winpty;
+ conpty_t *conpty;
+ } object;
+ PtyType type;
HANDLE finish_wait;
HANDLE process_handle;
uv_timer_t wait_eof_timer;
@@ -30,7 +40,8 @@ static inline PtyProcess pty_process_init(Loop *loop, void *data)
rv.term_name = NULL;
rv.width = 80;
rv.height = 24;
- rv.winpty_object = NULL;
+ rv.object.winpty = NULL;
+ rv.type = kWinpty;
rv.finish_wait = NULL;
rv.process_handle = NULL;
return rv;
diff --git a/src/nvim/os/shell.c b/src/nvim/os/shell.c
index 6956410401..6294d5e4e2 100644
--- a/src/nvim/os/shell.c
+++ b/src/nvim/os/shell.c
@@ -9,13 +9,17 @@
#include <uv.h>
#include "nvim/ascii.h"
+#include "nvim/fileio.h"
#include "nvim/lib/kvec.h"
#include "nvim/log.h"
#include "nvim/event/loop.h"
#include "nvim/event/libuv_process.h"
#include "nvim/event/rstream.h"
+#include "nvim/ex_cmds.h"
+#include "nvim/misc1.h"
#include "nvim/os/shell.h"
#include "nvim/os/signal.h"
+#include "nvim/path.h"
#include "nvim/types.h"
#include "nvim/main.h"
#include "nvim/vim.h"
@@ -32,6 +36,8 @@
#define NS_1_SECOND 1000000000U // 1 second, in nanoseconds
#define OUT_DATA_THRESHOLD 1024 * 10U // 10KB, "a few screenfuls" of data.
+#define SHELL_SPECIAL (char_u *)"\t \"&'$;<>()\\|"
+
typedef struct {
char *data;
size_t cap, len;
@@ -41,6 +47,498 @@ typedef struct {
# include "os/shell.c.generated.h"
#endif
+static void save_patterns(int num_pat, char_u **pat, int *num_file,
+ char_u ***file)
+{
+ *file = xmalloc((size_t)num_pat * sizeof(char_u *));
+ for (int i = 0; i < num_pat; i++) {
+ char_u *s = vim_strsave(pat[i]);
+ // Be compatible with expand_filename(): halve the number of
+ // backslashes.
+ backslash_halve(s);
+ (*file)[i] = s;
+ }
+ *num_file = num_pat;
+}
+
+static bool have_wildcard(int num, char_u **file)
+{
+ for (int i = 0; i < num; i++) {
+ if (path_has_wildcard(file[i])) {
+ return true;
+ }
+ }
+ return false;
+}
+
+static bool have_dollars(int num, char_u **file)
+{
+ for (int i = 0; i < num; i++) {
+ if (vim_strchr(file[i], '$') != NULL) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/// Performs wildcard pattern matching using the shell.
+///
+/// @param num_pat is the number of input patterns.
+/// @param pat is an array of pointers to input patterns.
+/// @param[out] num_file is pointer to number of matched file names.
+/// Set to the number of pointers in *file.
+/// @param[out] file is pointer to array of pointers to matched file names.
+/// Memory pointed to by the initial value of *file will
+/// not be freed.
+/// Set to NULL if FAIL is returned. Otherwise points to
+/// allocated memory.
+/// @param flags is a combination of EW_* flags used in
+/// expand_wildcards().
+/// If matching fails but EW_NOTFOUND is set in flags or
+/// there are no wildcards, the patterns from pat are
+/// copied into *file.
+///
+/// @returns OK for success or FAIL for error.
+int os_expand_wildcards(int num_pat, char_u **pat, int *num_file,
+ char_u ***file, int flags)
+ FUNC_ATTR_NONNULL_ARG(3)
+ FUNC_ATTR_NONNULL_ARG(4)
+{
+ int i;
+ size_t len;
+ char_u *p;
+ bool dir;
+ char_u *extra_shell_arg = NULL;
+ ShellOpts shellopts = kShellOptExpand | kShellOptSilent;
+ int j;
+ char_u *tempname;
+ char_u *command;
+ FILE *fd;
+ char_u *buffer;
+#define STYLE_ECHO 0 // use "echo", the default
+#define STYLE_GLOB 1 // use "glob", for csh
+#define STYLE_VIMGLOB 2 // use "vimglob", for Posix sh
+#define STYLE_PRINT 3 // use "print -N", for zsh
+#define STYLE_BT 4 // `cmd` expansion, execute the pattern directly
+ int shell_style = STYLE_ECHO;
+ int check_spaces;
+ static bool did_find_nul = false;
+ bool ampersent = false;
+ // vimglob() function to define for Posix shell
+ static char *sh_vimglob_func =
+ "vimglob() { while [ $# -ge 1 ]; do echo \"$1\"; shift; done }; vimglob >";
+
+ bool is_fish_shell =
+#if defined(UNIX)
+ STRNCMP(invocation_path_tail(p_sh, NULL), "fish", 4) == 0;
+#else
+ false;
+#endif
+
+ *num_file = 0; // default: no files found
+ *file = NULL;
+
+ // If there are no wildcards, just copy the names to allocated memory.
+ // Saves a lot of time, because we don't have to start a new shell.
+ if (!have_wildcard(num_pat, pat)) {
+ save_patterns(num_pat, pat, num_file, file);
+ return OK;
+ }
+
+ // Don't allow any shell command in the sandbox.
+ if (sandbox != 0 && check_secure()) {
+ return FAIL;
+ }
+
+ // Don't allow the use of backticks in secure and restricted mode.
+ if (secure || restricted) {
+ for (i = 0; i < num_pat; i++) {
+ if (vim_strchr(pat[i], '`') != NULL
+ && (check_restricted() || check_secure())) {
+ return FAIL;
+ }
+ }
+ }
+
+ // get a name for the temp file
+ if ((tempname = vim_tempname()) == NULL) {
+ EMSG(_(e_notmp));
+ return FAIL;
+ }
+
+ // Let the shell expand the patterns and write the result into the temp
+ // file.
+ // STYLE_BT: NL separated
+ // If expanding `cmd` execute it directly.
+ // STYLE_GLOB: NUL separated
+ // If we use *csh, "glob" will work better than "echo".
+ // STYLE_PRINT: NL or NUL separated
+ // If we use *zsh, "print -N" will work better than "glob".
+ // STYLE_VIMGLOB: NL separated
+ // If we use *sh*, we define "vimglob()".
+ // STYLE_ECHO: space separated.
+ // A shell we don't know, stay safe and use "echo".
+ if (num_pat == 1 && *pat[0] == '`'
+ && (len = STRLEN(pat[0])) > 2
+ && *(pat[0] + len - 1) == '`') {
+ shell_style = STYLE_BT;
+ } else if ((len = STRLEN(p_sh)) >= 3) {
+ if (STRCMP(p_sh + len - 3, "csh") == 0) {
+ shell_style = STYLE_GLOB;
+ } else if (STRCMP(p_sh + len - 3, "zsh") == 0) {
+ shell_style = STYLE_PRINT;
+ }
+ }
+ if (shell_style == STYLE_ECHO
+ && strstr((char *)path_tail(p_sh), "sh") != NULL) {
+ shell_style = STYLE_VIMGLOB;
+ }
+
+ // Compute the length of the command. We need 2 extra bytes: for the
+ // optional '&' and for the NUL.
+ // Worst case: "unset nonomatch; print -N >" plus two is 29
+ len = STRLEN(tempname) + 29;
+ if (shell_style == STYLE_VIMGLOB) {
+ len += STRLEN(sh_vimglob_func);
+ }
+
+ for (i = 0; i < num_pat; i++) {
+ // Count the length of the patterns in the same way as they are put in
+ // "command" below.
+ len++; // add space
+ for (j = 0; pat[i][j] != NUL; j++) {
+ if (vim_strchr(SHELL_SPECIAL, pat[i][j]) != NULL) {
+ len++; // may add a backslash
+ }
+ len++;
+ }
+ }
+
+ if (is_fish_shell) {
+ len += sizeof("egin;"" end") - 1;
+ }
+
+ command = xmalloc(len);
+
+ // Build the shell command:
+ // - Set $nonomatch depending on EW_NOTFOUND (hopefully the shell
+ // recognizes this).
+ // - Add the shell command to print the expanded names.
+ // - Add the temp file name.
+ // - Add the file name patterns.
+ if (shell_style == STYLE_BT) {
+ // change `command; command& ` to (command; command )
+ if (is_fish_shell) {
+ STRCPY(command, "begin; ");
+ } else {
+ STRCPY(command, "(");
+ }
+ STRCAT(command, pat[0] + 1); // exclude first backtick
+ p = command + STRLEN(command) - 1;
+ if (is_fish_shell) {
+ *p-- = ';';
+ STRCAT(command, " end");
+ } else {
+ *p-- = ')'; // remove last backtick
+ }
+ while (p > command && ascii_iswhite(*p)) {
+ p--;
+ }
+ if (*p == '&') { // remove trailing '&'
+ ampersent = true;
+ *p = ' ';
+ }
+ STRCAT(command, ">");
+ } else {
+ if (flags & EW_NOTFOUND) {
+ STRCPY(command, "set nonomatch; ");
+ } else {
+ STRCPY(command, "unset nonomatch; ");
+ }
+ if (shell_style == STYLE_GLOB) {
+ STRCAT(command, "glob >");
+ } else if (shell_style == STYLE_PRINT) {
+ STRCAT(command, "print -N >");
+ } else if (shell_style == STYLE_VIMGLOB) {
+ STRCAT(command, sh_vimglob_func);
+ } else {
+ STRCAT(command, "echo >");
+ }
+ }
+
+ STRCAT(command, tempname);
+
+ if (shell_style != STYLE_BT) {
+ for (i = 0; i < num_pat; i++) {
+ // Put a backslash before special
+ // characters, except inside ``.
+ bool intick = false;
+
+ p = command + STRLEN(command);
+ *p++ = ' ';
+ for (j = 0; pat[i][j] != NUL; j++) {
+ if (pat[i][j] == '`') {
+ intick = !intick;
+ } else if (pat[i][j] == '\\' && pat[i][j + 1] != NUL) {
+ // Remove a backslash, take char literally. But keep
+ // backslash inside backticks, before a special character
+ // and before a backtick.
+ if (intick
+ || vim_strchr(SHELL_SPECIAL, pat[i][j + 1]) != NULL
+ || pat[i][j + 1] == '`') {
+ *p++ = '\\';
+ }
+ j++;
+ } else if (!intick
+ && ((flags & EW_KEEPDOLLAR) == 0 || pat[i][j] != '$')
+ && vim_strchr(SHELL_SPECIAL, pat[i][j]) != NULL) {
+ // Put a backslash before a special character, but not
+ // when inside ``. And not for $var when EW_KEEPDOLLAR is
+ // set.
+ *p++ = '\\';
+ }
+
+ // Copy one character.
+ *p++ = pat[i][j];
+ }
+ *p = NUL;
+ }
+ }
+
+ if (flags & EW_SILENT) {
+ shellopts |= kShellOptHideMess;
+ }
+
+ if (ampersent) {
+ STRCAT(command, "&"); // put the '&' after the redirection
+ }
+
+ // Using zsh -G: If a pattern has no matches, it is just deleted from
+ // the argument list, otherwise zsh gives an error message and doesn't
+ // expand any other pattern.
+ if (shell_style == STYLE_PRINT) {
+ extra_shell_arg = (char_u *)"-G"; // Use zsh NULL_GLOB option
+
+ // If we use -f then shell variables set in .cshrc won't get expanded.
+ // vi can do it, so we will too, but it is only necessary if there is a "$"
+ // in one of the patterns, otherwise we can still use the fast option.
+ } else if (shell_style == STYLE_GLOB && !have_dollars(num_pat, pat)) {
+ extra_shell_arg = (char_u *)"-f"; // Use csh fast option
+ }
+
+ // execute the shell command
+ i = call_shell(command, shellopts, extra_shell_arg);
+
+ // When running in the background, give it some time to create the temp
+ // file, but don't wait for it to finish.
+ if (ampersent) {
+ os_delay(10L, true);
+ }
+
+ xfree(command);
+
+ if (i) { // os_call_shell() failed
+ os_remove((char *)tempname);
+ xfree(tempname);
+ // With interactive completion, the error message is not printed.
+ if (!(flags & EW_SILENT)) {
+ msg_putchar('\n'); // clear bottom line quickly
+ cmdline_row = Rows - 1; // continue on last line
+ MSG(_(e_wildexpand));
+ msg_start(); // don't overwrite this message
+ }
+
+ // If a `cmd` expansion failed, don't list `cmd` as a match, even when
+ // EW_NOTFOUND is given
+ if (shell_style == STYLE_BT) {
+ return FAIL;
+ }
+ goto notfound;
+ }
+
+ // read the names from the file into memory
+ fd = fopen((char *)tempname, READBIN);
+ if (fd == NULL) {
+ // Something went wrong, perhaps a file name with a special char.
+ if (!(flags & EW_SILENT)) {
+ MSG(_(e_wildexpand));
+ msg_start(); // don't overwrite this message
+ }
+ xfree(tempname);
+ goto notfound;
+ }
+ int fseek_res = fseek(fd, 0L, SEEK_END);
+ if (fseek_res < 0) {
+ xfree(tempname);
+ fclose(fd);
+ return FAIL;
+ }
+ int64_t templen = ftell(fd); // get size of temp file
+ if (templen < 0) {
+ xfree(tempname);
+ fclose(fd);
+ return FAIL;
+ }
+#if SIZEOF_LONG_LONG > SIZEOF_SIZE_T
+ assert(templen <= (long long)SIZE_MAX); // NOLINT(runtime/int)
+#endif
+ len = (size_t)templen;
+ fseek(fd, 0L, SEEK_SET);
+ buffer = xmalloc(len + 1);
+ // fread() doesn't terminate buffer with NUL;
+ // appropriate termination (not always NUL) is done below.
+ size_t readlen = fread((char *)buffer, 1, len, fd);
+ fclose(fd);
+ os_remove((char *)tempname);
+ if (readlen != len) {
+ // unexpected read error
+ EMSG2(_(e_notread), tempname);
+ xfree(tempname);
+ xfree(buffer);
+ return FAIL;
+ }
+ xfree(tempname);
+
+ // file names are separated with Space
+ if (shell_style == STYLE_ECHO) {
+ buffer[len] = '\n'; // make sure the buffer ends in NL
+ p = buffer;
+ for (i = 0; *p != '\n'; i++) { // count number of entries
+ while (*p != ' ' && *p != '\n') {
+ p++;
+ }
+ p = skipwhite(p); // skip to next entry
+ }
+ // file names are separated with NL
+ } else if (shell_style == STYLE_BT || shell_style == STYLE_VIMGLOB) {
+ buffer[len] = NUL; // make sure the buffer ends in NUL
+ p = buffer;
+ for (i = 0; *p != NUL; i++) { // count number of entries
+ while (*p != '\n' && *p != NUL) {
+ p++;
+ }
+ if (*p != NUL) {
+ p++;
+ }
+ p = skipwhite(p); // skip leading white space
+ }
+ // file names are separated with NUL
+ } else {
+ // Some versions of zsh use spaces instead of NULs to separate
+ // results. Only do this when there is no NUL before the end of the
+ // buffer, otherwise we would never be able to use file names with
+ // embedded spaces when zsh does use NULs.
+ // When we found a NUL once, we know zsh is OK, set did_find_nul and
+ // don't check for spaces again.
+ check_spaces = false;
+ if (shell_style == STYLE_PRINT && !did_find_nul) {
+ // If there is a NUL, set did_find_nul, else set check_spaces
+ buffer[len] = NUL;
+ if (len && (int)STRLEN(buffer) < (int)len) {
+ did_find_nul = true;
+ } else {
+ check_spaces = true;
+ }
+ }
+
+ // Make sure the buffer ends with a NUL. For STYLE_PRINT there
+ // already is one, for STYLE_GLOB it needs to be added.
+ if (len && buffer[len - 1] == NUL) {
+ len--;
+ } else {
+ buffer[len] = NUL;
+ }
+ for (p = buffer; p < buffer + len; p++) {
+ if (*p == NUL || (*p == ' ' && check_spaces)) { // count entry
+ i++;
+ *p = NUL;
+ }
+ }
+ if (len) {
+ i++; // count last entry
+ }
+ }
+ assert(buffer[len] == NUL || buffer[len] == '\n');
+
+ if (i == 0) {
+ // Can happen when using /bin/sh and typing ":e $NO_SUCH_VAR^I".
+ // /bin/sh will happily expand it to nothing rather than returning an
+ // error; and hey, it's good to check anyway -- webb.
+ xfree(buffer);
+ goto notfound;
+ }
+ *num_file = i;
+ *file = xmalloc(sizeof(char_u *) * (size_t)i);
+
+ // Isolate the individual file names.
+ p = buffer;
+ for (i = 0; i < *num_file; i++) {
+ (*file)[i] = p;
+ // Space or NL separates
+ if (shell_style == STYLE_ECHO || shell_style == STYLE_BT
+ || shell_style == STYLE_VIMGLOB) {
+ while (!(shell_style == STYLE_ECHO && *p == ' ')
+ && *p != '\n' && *p != NUL) {
+ p++;
+ }
+ if (p == buffer + len) { // last entry
+ *p = NUL;
+ } else {
+ *p++ = NUL;
+ p = skipwhite(p); // skip to next entry
+ }
+ } else { // NUL separates
+ while (*p && p < buffer + len) { // skip entry
+ p++;
+ }
+ p++; // skip NUL
+ }
+ }
+
+ // Move the file names to allocated memory.
+ for (j = 0, i = 0; i < *num_file; i++) {
+ // Require the files to exist. Helps when using /bin/sh
+ if (!(flags & EW_NOTFOUND) && !os_path_exists((*file)[i])) {
+ continue;
+ }
+
+ // check if this entry should be included
+ dir = (os_isdir((*file)[i]));
+ if ((dir && !(flags & EW_DIR)) || (!dir && !(flags & EW_FILE))) {
+ continue;
+ }
+
+ // Skip files that are not executable if we check for that.
+ if (!dir && (flags & EW_EXEC)
+ && !os_can_exe((char *)(*file)[i], NULL, !(flags & EW_SHELLCMD))) {
+ continue;
+ }
+
+ p = xmalloc(STRLEN((*file)[i]) + 1 + dir);
+ STRCPY(p, (*file)[i]);
+ if (dir) {
+ add_pathsep((char *)p); // add '/' to a directory name
+ }
+ (*file)[j++] = p;
+ }
+ xfree(buffer);
+ *num_file = j;
+
+ if (*num_file == 0) { // rejected all entries
+ XFREE_CLEAR(*file);
+ goto notfound;
+ }
+
+ return OK;
+
+notfound:
+ if (flags & EW_NOTFOUND) {
+ save_patterns(num_pat, pat, num_file, file);
+ return OK;
+ }
+ return FAIL;
+}
+
/// Builds the argument vector for running the user-configured 'shell' (p_sh)
/// with an optional command prefixed by 'shellcmdflag' (p_shcf). E.g.:
///
@@ -423,10 +921,11 @@ static bool out_data_decide_throttle(size_t size)
pulse_msg[1] = (tick > 1) ? '.' : ' ';
pulse_msg[2] = (tick > 2) ? '.' : ' ';
if (visit == 1) {
- msg_putchar('\n');
+ msg_puts("...\n");
}
msg_putchar('\r'); // put cursor at start of line
msg_puts(pulse_msg);
+ msg_putchar('\r');
ui_flush();
return true;
}
diff --git a/src/nvim/os/signal.c b/src/nvim/os/signal.c
index ba6226ef9d..bfe230b521 100644
--- a/src/nvim/os/signal.c
+++ b/src/nvim/os/signal.c
@@ -50,22 +50,13 @@ void signal_init(void)
signal_watcher_init(&main_loop, &shup, NULL);
signal_watcher_init(&main_loop, &squit, NULL);
signal_watcher_init(&main_loop, &sterm, NULL);
-#ifdef SIGPIPE
- signal_watcher_start(&spipe, on_signal, SIGPIPE);
-#endif
- signal_watcher_start(&shup, on_signal, SIGHUP);
-#ifdef SIGQUIT
- signal_watcher_start(&squit, on_signal, SIGQUIT);
-#endif
- signal_watcher_start(&sterm, on_signal, SIGTERM);
#ifdef SIGPWR
signal_watcher_init(&main_loop, &spwr, NULL);
- signal_watcher_start(&spwr, on_signal, SIGPWR);
#endif
#ifdef SIGUSR1
signal_watcher_init(&main_loop, &susr1, NULL);
- signal_watcher_start(&susr1, on_signal, SIGUSR1);
#endif
+ signal_start();
}
void signal_teardown(void)
@@ -83,11 +74,33 @@ void signal_teardown(void)
#endif
}
+void signal_start(void)
+{
+#ifdef SIGPIPE
+ signal_watcher_start(&spipe, on_signal, SIGPIPE);
+#endif
+ signal_watcher_start(&shup, on_signal, SIGHUP);
+#ifdef SIGQUIT
+ signal_watcher_start(&squit, on_signal, SIGQUIT);
+#endif
+ signal_watcher_start(&sterm, on_signal, SIGTERM);
+#ifdef SIGPWR
+ signal_watcher_start(&spwr, on_signal, SIGPWR);
+#endif
+#ifdef SIGUSR1
+ signal_watcher_start(&susr1, on_signal, SIGUSR1);
+#endif
+}
+
void signal_stop(void)
{
+#ifdef SIGPIPE
signal_watcher_stop(&spipe);
+#endif
signal_watcher_stop(&shup);
+#ifdef SIGQUIT
signal_watcher_stop(&squit);
+#endif
signal_watcher_stop(&sterm);
#ifdef SIGPWR
signal_watcher_stop(&spwr);
@@ -144,6 +157,7 @@ static void deadly_signal(int signum)
{
// Set the v:dying variable.
set_vim_var_nr(VV_DYING, 1);
+ v_dying = 1;
WLOG("got signal %d (%s)", signum, signal_name(signum));
diff --git a/src/nvim/os/time.c b/src/nvim/os/time.c
index 4dd0614fe2..346e40e02e 100644
--- a/src/nvim/os/time.c
+++ b/src/nvim/os/time.c
@@ -2,9 +2,6 @@
// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
#include <assert.h>
-#include <stdint.h>
-#include <stdbool.h>
-#include <time.h>
#include <limits.h>
#include <uv.h>
@@ -13,7 +10,7 @@
#include "nvim/os/time.h"
#include "nvim/os/input.h"
#include "nvim/event/loop.h"
-#include "nvim/vim.h"
+#include "nvim/os/os.h"
#include "nvim/main.h"
static uv_mutex_t delay_mutex;
@@ -112,6 +109,10 @@ void os_microdelay(uint64_t us, bool ignoreinput)
uv_mutex_unlock(&delay_mutex);
}
+// Cache of the current timezone name as retrieved from TZ, or an empty string
+// where unset, up to 64 octets long including trailing null byte.
+static char tz_cache[64];
+
/// Portable version of POSIX localtime_r()
///
/// @return NULL in case of error
@@ -120,6 +121,19 @@ struct tm *os_localtime_r(const time_t *restrict clock,
{
#ifdef UNIX
// POSIX provides localtime_r() as a thread-safe version of localtime().
+ //
+ // Check to see if the environment variable TZ has changed since the last run.
+ // Call tzset(3) to update the global timezone variables if it has.
+ // POSIX standard doesn't require localtime_r() implementations to do that
+ // as it does with localtime(), and we don't want to call tzset() every time.
+ const char *tz = os_getenv("TZ");
+ if (tz == NULL) {
+ tz = "";
+ }
+ if (strncmp(tz_cache, tz, sizeof(tz_cache) - 1) != 0) {
+ tzset();
+ xstrlcpy(tz_cache, tz, sizeof(tz_cache));
+ }
return localtime_r(clock, result); // NOLINT(runtime/threadsafe_fn)
#else
// Windows version of localtime() is thread-safe.
@@ -144,6 +158,39 @@ struct tm *os_localtime(struct tm *result) FUNC_ATTR_NONNULL_ALL
return os_localtime_r(&rawtime, result);
}
+/// Portable version of POSIX ctime_r()
+///
+/// @param clock[in]
+/// @param result[out] Pointer to a 'char' where the result should be placed
+/// @param result_len length of result buffer
+/// @return human-readable string of current local time
+char *os_ctime_r(const time_t *restrict clock, char *restrict result,
+ size_t result_len)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET
+{
+ struct tm clock_local;
+ struct tm *clock_local_ptr = os_localtime_r(clock, &clock_local);
+ // MSVC returns NULL for an invalid value of seconds.
+ if (clock_local_ptr == NULL) {
+ snprintf(result, result_len, "%s\n", _("(Invalid)"));
+ } else {
+ strftime(result, result_len, "%a %b %d %H:%M:%S %Y\n", clock_local_ptr);
+ }
+ return result;
+}
+
+/// Gets the current Unix timestamp and adjusts it to local time.
+///
+/// @param result[out] Pointer to a 'char' where the result should be placed
+/// @param result_len length of result buffer
+/// @return human-readable string of current local time
+char *os_ctime(char *result, size_t result_len)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET
+{
+ time_t rawtime = time(NULL);
+ return os_ctime_r(&rawtime, result, result_len);
+}
+
/// Obtains the current Unix timestamp.
///
/// @return Seconds since epoch.
diff --git a/src/nvim/os/tty.c b/src/nvim/os/tty.c
index bd5b9b4506..c80ef99084 100644
--- a/src/nvim/os/tty.c
+++ b/src/nvim/os/tty.c
@@ -6,6 +6,7 @@
//
#include "nvim/os/os.h"
+#include "nvim/os/tty.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "os/tty.c.generated.h"
@@ -27,7 +28,7 @@ void os_tty_guess_term(const char **term, int out_fd)
if (winpty) {
// Force TERM=win32con when running in winpty.
*term = "win32con";
- uv_set_vterm_state(UV_UNSUPPORTED);
+ uv_tty_set_vterm_state(UV_TTY_UNSUPPORTED);
return;
}
@@ -54,7 +55,7 @@ void os_tty_guess_term(const char **term, int out_fd)
}
if (conemu_ansi) {
- uv_set_vterm_state(UV_SUPPORTED);
+ uv_tty_set_vterm_state(UV_TTY_SUPPORTED);
}
}
#endif
diff --git a/src/nvim/os/users.c b/src/nvim/os/users.c
index c6463c2c92..9374693550 100644
--- a/src/nvim/os/users.c
+++ b/src/nvim/os/users.c
@@ -13,6 +13,25 @@
#ifdef HAVE_PWD_H
# include <pwd.h>
#endif
+#ifdef WIN32
+# include <lm.h>
+#endif
+
+// Add a user name to the list of users in garray_T *users.
+// Do nothing if user name is NULL or empty.
+static void add_user(garray_T *users, char *user, bool need_copy)
+{
+ char *user_copy = (user != NULL && need_copy)
+ ? xstrdup(user) : user;
+
+ if (user_copy == NULL || *user_copy == NUL) {
+ if (need_copy) {
+ xfree(user);
+ }
+ return;
+ }
+ GA_APPEND(char *, users, user_copy);
+}
// Initialize users garray and fill it with os usernames.
// Return Ok for success, FAIL for failure.
@@ -24,16 +43,66 @@ int os_get_usernames(garray_T *users)
ga_init(users, sizeof(char *), 20);
# if defined(HAVE_GETPWENT) && defined(HAVE_PWD_H)
- struct passwd *pw;
+ {
+ struct passwd *pw;
+
+ setpwent();
+ while ((pw = getpwent()) != NULL) {
+ add_user(users, pw->pw_name, true);
+ }
+ endpwent();
+ }
+# elif defined(WIN32)
+ {
+ DWORD nusers = 0, ntotal = 0, i;
+ PUSER_INFO_0 uinfo;
+
+ if (NetUserEnum(NULL, 0, 0, (LPBYTE *)&uinfo, MAX_PREFERRED_LENGTH,
+ &nusers, &ntotal, NULL) == NERR_Success) {
+ for (i = 0; i < nusers; i++) {
+ char *user;
+ int conversion_result = utf16_to_utf8(uinfo[i].usri0_name, -1, &user);
+ if (conversion_result != 0) {
+ EMSG2("utf16_to_utf8 failed: %d", conversion_result);
+ break;
+ }
+ add_user(users, user, false);
+ }
+
+ NetApiBufferFree(uinfo);
+ }
+ }
+# endif
+# if defined(HAVE_GETPWNAM)
+ {
+ const char *user_env = os_getenv("USER");
+
+ // The $USER environment variable may be a valid remote user name (NIS,
+ // LDAP) not already listed by getpwent(), as getpwent() only lists
+ // local user names. If $USER is not already listed, check whether it
+ // is a valid remote user name using getpwnam() and if it is, add it to
+ // the list of user names.
+
+ if (user_env != NULL && *user_env != NUL) {
+ int i;
+
+ for (i = 0; i < users->ga_len; i++) {
+ char *local_user = ((char **)users->ga_data)[i];
+
+ if (STRCMP(local_user, user_env) == 0) {
+ break;
+ }
+ }
+
+ if (i == users->ga_len) {
+ struct passwd *pw = getpwnam(user_env); // NOLINT
- setpwent();
- while ((pw = getpwent()) != NULL) {
- // pw->pw_name shouldn't be NULL but just in case...
- if (pw->pw_name != NULL) {
- GA_APPEND(char *, users, xstrdup(pw->pw_name));
+ if (pw != NULL) {
+ add_user(users, pw->pw_name, true);
+ }
+ }
}
}
- endpwent();
# endif
return OK;
diff --git a/src/nvim/os_unix.c b/src/nvim/os_unix.c
index ded575529f..be4bd9709b 100644
--- a/src/nvim/os_unix.c
+++ b/src/nvim/os_unix.c
@@ -1,13 +1,6 @@
// This is an open source non-commercial project. Dear PVS-Studio, please check
// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-/*
- * os_unix.c -- code for all flavors of Unix (BSD, SYSV, SVR4, POSIX, ...)
- *
- * A lot of this file was originally written by Juergen Weigert and later
- * changed beyond recognition.
- */
-
#include <assert.h>
#include <errno.h>
#include <inttypes.h>
@@ -79,527 +72,3 @@ void mch_free_acl(vim_acl_T aclent)
return;
}
#endif
-
-void mch_exit(int r)
- FUNC_ATTR_NORETURN
-{
- exiting = true;
-
- ui_flush();
- ui_call_stop();
- ml_close_all(true); // remove all memfiles
-
- if (!event_teardown() && r == 0) {
- r = 1; // Exit with error if main_loop did not teardown gracefully.
- }
- if (input_global_fd() >= 0) {
- stream_set_blocking(input_global_fd(), true); // normalize stream (#2598)
- }
-
- ILOG("Nvim exit: %d", r);
-
-#ifdef EXITFREE
- free_all_mem();
-#endif
-
- exit(r);
-}
-
-#define SHELL_SPECIAL (char_u *)"\t \"&'$;<>()\\|"
-
-/// Does wildcard pattern matching using the shell.
-///
-/// @param num_pat is the number of input patterns.
-/// @param pat is an array of pointers to input patterns.
-/// @param[out] num_file is pointer to number of matched file names.
-/// Set to the number of pointers in *file.
-/// @param[out] file is pointer to array of pointers to matched file names.
-/// Memory pointed to by the initial value of *file will
-/// not be freed.
-/// Set to NULL if FAIL is returned. Otherwise points to
-/// allocated memory.
-/// @param flags is a combination of EW_* flags used in
-/// expand_wildcards().
-/// If matching fails but EW_NOTFOUND is set in flags or
-/// there are no wildcards, the patterns from pat are
-/// copied into *file.
-///
-/// @returns OK for success or FAIL for error.
-int mch_expand_wildcards(int num_pat, char_u **pat, int *num_file,
- char_u ***file, int flags) FUNC_ATTR_NONNULL_ARG(3)
- FUNC_ATTR_NONNULL_ARG(4)
-{
- int i;
- size_t len;
- char_u *p;
- bool dir;
- char_u *extra_shell_arg = NULL;
- ShellOpts shellopts = kShellOptExpand | kShellOptSilent;
- int j;
- char_u *tempname;
- char_u *command;
- FILE *fd;
- char_u *buffer;
-#define STYLE_ECHO 0 /* use "echo", the default */
-#define STYLE_GLOB 1 /* use "glob", for csh */
-#define STYLE_VIMGLOB 2 /* use "vimglob", for Posix sh */
-#define STYLE_PRINT 3 /* use "print -N", for zsh */
-#define STYLE_BT 4 /* `cmd` expansion, execute the pattern
- * directly */
- int shell_style = STYLE_ECHO;
- int check_spaces;
- static bool did_find_nul = false;
- bool ampersent = false;
- // vimglob() function to define for Posix shell
- static char *sh_vimglob_func =
- "vimglob() { while [ $# -ge 1 ]; do echo \"$1\"; shift; done }; vimglob >";
-
- bool is_fish_shell =
-#if defined(UNIX)
- STRNCMP(invocation_path_tail(p_sh, NULL), "fish", 4) == 0;
-#else
- false;
-#endif
-
- *num_file = 0; // default: no files found
- *file = NULL;
-
- // If there are no wildcards, just copy the names to allocated memory.
- // Saves a lot of time, because we don't have to start a new shell.
- if (!have_wildcard(num_pat, pat)) {
- save_patterns(num_pat, pat, num_file, file);
- return OK;
- }
-
- // Don't allow any shell command in the sandbox.
- if (sandbox != 0 && check_secure()) {
- return FAIL;
- }
-
- // Don't allow the use of backticks in secure and restricted mode.
- if (secure || restricted) {
- for (i = 0; i < num_pat; i++) {
- if (vim_strchr(pat[i], '`') != NULL
- && (check_restricted() || check_secure())) {
- return FAIL;
- }
- }
- }
-
- // get a name for the temp file
- if ((tempname = vim_tempname()) == NULL) {
- EMSG(_(e_notmp));
- return FAIL;
- }
-
- // Let the shell expand the patterns and write the result into the temp
- // file.
- // STYLE_BT: NL separated
- // If expanding `cmd` execute it directly.
- // STYLE_GLOB: NUL separated
- // If we use *csh, "glob" will work better than "echo".
- // STYLE_PRINT: NL or NUL separated
- // If we use *zsh, "print -N" will work better than "glob".
- // STYLE_VIMGLOB: NL separated
- // If we use *sh*, we define "vimglob()".
- // STYLE_ECHO: space separated.
- // A shell we don't know, stay safe and use "echo".
- if (num_pat == 1 && *pat[0] == '`'
- && (len = STRLEN(pat[0])) > 2
- && *(pat[0] + len - 1) == '`') {
- shell_style = STYLE_BT;
- } else if ((len = STRLEN(p_sh)) >= 3) {
- if (STRCMP(p_sh + len - 3, "csh") == 0) {
- shell_style = STYLE_GLOB;
- } else if (STRCMP(p_sh + len - 3, "zsh") == 0) {
- shell_style = STYLE_PRINT;
- }
- }
- if (shell_style == STYLE_ECHO && strstr((char *)path_tail(p_sh),
- "sh") != NULL)
- shell_style = STYLE_VIMGLOB;
-
- // Compute the length of the command. We need 2 extra bytes: for the
- // optional '&' and for the NUL.
- // Worst case: "unset nonomatch; print -N >" plus two is 29
- len = STRLEN(tempname) + 29;
- if (shell_style == STYLE_VIMGLOB)
- len += STRLEN(sh_vimglob_func);
-
- for (i = 0; i < num_pat; i++) {
- // Count the length of the patterns in the same way as they are put in
- // "command" below.
- len++; // add space
- for (j = 0; pat[i][j] != NUL; j++) {
- if (vim_strchr(SHELL_SPECIAL, pat[i][j]) != NULL) {
- len++; // may add a backslash
- }
- len++;
- }
- }
-
- if (is_fish_shell) {
- len += sizeof("egin;"" end") - 1;
- }
-
- command = xmalloc(len);
-
- // Build the shell command:
- // - Set $nonomatch depending on EW_NOTFOUND (hopefully the shell
- // recognizes this).
- // - Add the shell command to print the expanded names.
- // - Add the temp file name.
- // - Add the file name patterns.
- if (shell_style == STYLE_BT) {
- // change `command; command& ` to (command; command )
- if (is_fish_shell) {
- STRCPY(command, "begin; ");
- } else {
- STRCPY(command, "(");
- }
- STRCAT(command, pat[0] + 1); // exclude first backtick
- p = command + STRLEN(command) - 1;
- if (is_fish_shell) {
- *p-- = ';';
- STRCAT(command, " end");
- } else {
- *p-- = ')'; // remove last backtick
- }
- while (p > command && ascii_iswhite(*p)) {
- p--;
- }
- if (*p == '&') { // remove trailing '&'
- ampersent = true;
- *p = ' ';
- }
- STRCAT(command, ">");
- } else {
- if (flags & EW_NOTFOUND)
- STRCPY(command, "set nonomatch; ");
- else
- STRCPY(command, "unset nonomatch; ");
- if (shell_style == STYLE_GLOB)
- STRCAT(command, "glob >");
- else if (shell_style == STYLE_PRINT)
- STRCAT(command, "print -N >");
- else if (shell_style == STYLE_VIMGLOB)
- STRCAT(command, sh_vimglob_func);
- else
- STRCAT(command, "echo >");
- }
-
- STRCAT(command, tempname);
-
- if (shell_style != STYLE_BT) {
- for (i = 0; i < num_pat; i++) {
- // Put a backslash before special
- // characters, except inside ``.
- bool intick = false;
-
- p = command + STRLEN(command);
- *p++ = ' ';
- for (j = 0; pat[i][j] != NUL; j++) {
- if (pat[i][j] == '`') {
- intick = !intick;
- } else if (pat[i][j] == '\\' && pat[i][j + 1] != NUL) {
- // Remove a backslash, take char literally. But keep
- // backslash inside backticks, before a special character
- // and before a backtick.
- if (intick
- || vim_strchr(SHELL_SPECIAL, pat[i][j + 1]) != NULL
- || pat[i][j + 1] == '`') {
- *p++ = '\\';
- }
- j++;
- } else if (!intick
- && ((flags & EW_KEEPDOLLAR) == 0 || pat[i][j] != '$')
- && vim_strchr(SHELL_SPECIAL, pat[i][j]) != NULL) {
- // Put a backslash before a special character, but not
- // when inside ``. And not for $var when EW_KEEPDOLLAR is
- // set.
- *p++ = '\\';
- }
-
- // Copy one character.
- *p++ = pat[i][j];
- }
- *p = NUL;
- }
- }
-
- if (flags & EW_SILENT) {
- shellopts |= kShellOptHideMess;
- }
-
- if (ampersent) {
- STRCAT(command, "&"); // put the '&' after the redirection
- }
-
- // Using zsh -G: If a pattern has no matches, it is just deleted from
- // the argument list, otherwise zsh gives an error message and doesn't
- // expand any other pattern.
- if (shell_style == STYLE_PRINT) {
- extra_shell_arg = (char_u *)"-G"; // Use zsh NULL_GLOB option
-
- // If we use -f then shell variables set in .cshrc won't get expanded.
- // vi can do it, so we will too, but it is only necessary if there is a "$"
- // in one of the patterns, otherwise we can still use the fast option.
- } else if (shell_style == STYLE_GLOB && !have_dollars(num_pat, pat)) {
- extra_shell_arg = (char_u *)"-f"; // Use csh fast option
- }
-
- // execute the shell command
- i = call_shell(
- command,
- shellopts,
- extra_shell_arg
- );
-
- // When running in the background, give it some time to create the temp
- // file, but don't wait for it to finish.
- if (ampersent) {
- os_delay(10L, true);
- }
-
- xfree(command);
-
- if (i) { // os_call_shell() failed
- os_remove((char *)tempname);
- xfree(tempname);
- // With interactive completion, the error message is not printed.
- if (!(flags & EW_SILENT)) {
- msg_putchar('\n'); // clear bottom line quickly
- cmdline_row = Rows - 1; // continue on last line
- MSG(_(e_wildexpand));
- msg_start(); // don't overwrite this message
- }
-
- // If a `cmd` expansion failed, don't list `cmd` as a match, even when
- // EW_NOTFOUND is given
- if (shell_style == STYLE_BT) {
- return FAIL;
- }
- goto notfound;
- }
-
- // read the names from the file into memory
- fd = fopen((char *)tempname, READBIN);
- if (fd == NULL) {
- // Something went wrong, perhaps a file name with a special char.
- if (!(flags & EW_SILENT)) {
- MSG(_(e_wildexpand));
- msg_start(); // don't overwrite this message
- }
- xfree(tempname);
- goto notfound;
- }
- int fseek_res = fseek(fd, 0L, SEEK_END);
- if (fseek_res < 0) {
- xfree(tempname);
- fclose(fd);
- return FAIL;
- }
- int64_t templen = ftell(fd); // get size of temp file
- if (templen < 0) {
- xfree(tempname);
- fclose(fd);
- return FAIL;
- }
-#if SIZEOF_LONG_LONG > SIZEOF_SIZE_T
- assert(templen <= (long long)SIZE_MAX);
-#endif
- len = (size_t)templen;
- fseek(fd, 0L, SEEK_SET);
- buffer = xmalloc(len + 1);
- // fread() doesn't terminate buffer with NUL;
- // appropiate termination (not always NUL) is done below.
- size_t readlen = fread((char *)buffer, 1, len, fd);
- fclose(fd);
- os_remove((char *)tempname);
- if (readlen != len) {
- // unexpected read error
- EMSG2(_(e_notread), tempname);
- xfree(tempname);
- xfree(buffer);
- return FAIL;
- }
- xfree(tempname);
-
- // file names are separated with Space
- if (shell_style == STYLE_ECHO) {
- buffer[len] = '\n'; // make sure the buffer ends in NL
- p = buffer;
- for (i = 0; *p != '\n'; i++) { // count number of entries
- while (*p != ' ' && *p != '\n') {
- p++;
- }
- p = skipwhite(p); // skip to next entry
- }
- // file names are separated with NL
- } else if (shell_style == STYLE_BT || shell_style == STYLE_VIMGLOB) {
- buffer[len] = NUL; // make sure the buffer ends in NUL
- p = buffer;
- for (i = 0; *p != NUL; i++) { // count number of entries
- while (*p != '\n' && *p != NUL) {
- p++;
- }
- if (*p != NUL) {
- p++;
- }
- p = skipwhite(p); // skip leading white space
- }
- // file names are separated with NUL
- } else {
- // Some versions of zsh use spaces instead of NULs to separate
- // results. Only do this when there is no NUL before the end of the
- // buffer, otherwise we would never be able to use file names with
- // embedded spaces when zsh does use NULs.
- // When we found a NUL once, we know zsh is OK, set did_find_nul and
- // don't check for spaces again.
- check_spaces = false;
- if (shell_style == STYLE_PRINT && !did_find_nul) {
- // If there is a NUL, set did_find_nul, else set check_spaces
- buffer[len] = NUL;
- if (len && (int)STRLEN(buffer) < (int)len)
- did_find_nul = true;
- else
- check_spaces = true;
- }
-
- // Make sure the buffer ends with a NUL. For STYLE_PRINT there
- // already is one, for STYLE_GLOB it needs to be added.
- if (len && buffer[len - 1] == NUL) {
- len--;
- } else {
- buffer[len] = NUL;
- }
- i = 0;
- for (p = buffer; p < buffer + len; p++) {
- if (*p == NUL || (*p == ' ' && check_spaces)) { // count entry
- i++;
- *p = NUL;
- }
- }
- if (len) {
- i++; // count last entry
- }
- }
- assert(buffer[len] == NUL || buffer[len] == '\n');
-
- if (i == 0) {
- // Can happen when using /bin/sh and typing ":e $NO_SUCH_VAR^I".
- // /bin/sh will happily expand it to nothing rather than returning an
- // error; and hey, it's good to check anyway -- webb.
- xfree(buffer);
- goto notfound;
- }
- *num_file = i;
- *file = xmalloc(sizeof(char_u *) * (size_t)i);
-
- // Isolate the individual file names.
- p = buffer;
- for (i = 0; i < *num_file; ++i) {
- (*file)[i] = p;
- // Space or NL separates
- if (shell_style == STYLE_ECHO || shell_style == STYLE_BT
- || shell_style == STYLE_VIMGLOB) {
- while (!(shell_style == STYLE_ECHO && *p == ' ')
- && *p != '\n' && *p != NUL) {
- p++;
- }
- if (p == buffer + len) { // last entry
- *p = NUL;
- } else {
- *p++ = NUL;
- p = skipwhite(p); // skip to next entry
- }
- } else { // NUL separates
- while (*p && p < buffer + len) { // skip entry
- p++;
- }
- p++; // skip NUL
- }
- }
-
- // Move the file names to allocated memory.
- for (j = 0, i = 0; i < *num_file; i++) {
- // Require the files to exist. Helps when using /bin/sh
- if (!(flags & EW_NOTFOUND) && !os_path_exists((*file)[i])) {
- continue;
- }
-
- // check if this entry should be included
- dir = (os_isdir((*file)[i]));
- if ((dir && !(flags & EW_DIR)) || (!dir && !(flags & EW_FILE)))
- continue;
-
- // Skip files that are not executable if we check for that.
- if (!dir && (flags & EW_EXEC)
- && !os_can_exe((char *)(*file)[i], NULL, !(flags & EW_SHELLCMD))) {
- continue;
- }
-
- p = xmalloc(STRLEN((*file)[i]) + 1 + dir);
- STRCPY(p, (*file)[i]);
- if (dir) {
- add_pathsep((char *)p); // add '/' to a directory name
- }
- (*file)[j++] = p;
- }
- xfree(buffer);
- *num_file = j;
-
- if (*num_file == 0) { // rejected all entries
- XFREE_CLEAR(*file);
- goto notfound;
- }
-
- return OK;
-
-notfound:
- if (flags & EW_NOTFOUND) {
- save_patterns(num_pat, pat, num_file, file);
- return OK;
- }
- return FAIL;
-
-}
-
-
-static void save_patterns(int num_pat, char_u **pat, int *num_file,
- char_u ***file)
-{
- int i;
- char_u *s;
-
- *file = xmalloc((size_t)num_pat * sizeof(char_u *));
-
- for (i = 0; i < num_pat; i++) {
- s = vim_strsave(pat[i]);
- // Be compatible with expand_filename(): halve the number of
- // backslashes.
- backslash_halve(s);
- (*file)[i] = s;
- }
- *num_file = num_pat;
-}
-
-static bool have_wildcard(int num, char_u **file)
-{
- int i;
-
- for (i = 0; i < num; i++)
- if (path_has_wildcard(file[i]))
- return true;
- return false;
-}
-
-static bool have_dollars(int num, char_u **file)
-{
- int i;
-
- for (i = 0; i < num; i++)
- if (vim_strchr(file[i], '$') != NULL)
- return true;
- return false;
-}
diff --git a/src/nvim/path.c b/src/nvim/path.c
index 1c787e3a1d..31318f6bea 100644
--- a/src/nvim/path.c
+++ b/src/nvim/path.c
@@ -51,9 +51,10 @@
/// expanded.
/// @param s2 Second file name.
/// @param checkname When both files don't exist, only compare their names.
+/// @param expandenv Whether to expand environment variables in file names.
/// @return Enum of type FileComparison. @see FileComparison.
FileComparison path_full_compare(char_u *const s1, char_u *const s2,
- const bool checkname)
+ const bool checkname, const bool expandenv)
{
assert(s1 && s2);
char_u exp1[MAXPATHL];
@@ -61,7 +62,11 @@ FileComparison path_full_compare(char_u *const s1, char_u *const s2,
char_u full2[MAXPATHL];
FileID file_id_1, file_id_2;
- expand_env(s1, exp1, MAXPATHL);
+ if (expandenv) {
+ expand_env(s1, exp1, MAXPATHL);
+ } else {
+ xstrlcpy((char *)exp1, (const char *)s1, MAXPATHL - 1);
+ }
bool id_ok_1 = os_fileid((char *)exp1, &file_id_1);
bool id_ok_2 = os_fileid((char *)s2, &file_id_2);
if (!id_ok_1 && !id_ok_2) {
@@ -333,9 +338,9 @@ int path_fnamencmp(const char *const fname1, const char *const fname2,
&& (p_fic ? (c1 != c2 && CH_FOLD(c1) != CH_FOLD(c2)) : c1 != c2)) {
break;
}
- len -= (size_t)MB_PTR2LEN((const char_u *)p1);
- p1 += MB_PTR2LEN((const char_u *)p1);
- p2 += MB_PTR2LEN((const char_u *)p2);
+ len -= (size_t)utfc_ptr2len((const char_u *)p1);
+ p1 += utfc_ptr2len((const char_u *)p1);
+ p2 += utfc_ptr2len((const char_u *)p2);
}
return c1 - c2;
#else
@@ -1115,10 +1120,22 @@ static bool has_env_var(char_u *p)
static bool has_special_wildchar(char_u *p)
{
for (; *p; MB_PTR_ADV(p)) {
- // Allow for escaping
- if (*p == '\\' && p[1] != NUL) {
+ // Disallow line break characters.
+ if (*p == '\r' || *p == '\n') {
+ break;
+ }
+ // Allow for escaping.
+ if (*p == '\\' && p[1] != NUL && p[1] != '\r' && p[1] != '\n') {
p++;
} else if (vim_strchr((char_u *)SPECIAL_WILDCHAR, *p) != NULL) {
+ // A { must be followed by a matching }.
+ if (*p == '{' && vim_strchr(p, '}') == NULL) {
+ continue;
+ }
+ // A quote and backtick must be followed by another one.
+ if ((*p == '`' || *p == '\'') && vim_strchr(p, *p) == NULL) {
+ continue;
+ }
return true;
}
}
@@ -1161,7 +1178,7 @@ int gen_expand_wildcards(int num_pat, char_u **pat, int *num_file,
*/
if (recursive)
#ifdef SPECIAL_WILDCHAR
- return mch_expand_wildcards(num_pat, pat, num_file, file, flags);
+ return os_expand_wildcards(num_pat, pat, num_file, file, flags);
#else
return FAIL;
#endif
@@ -1176,7 +1193,7 @@ int gen_expand_wildcards(int num_pat, char_u **pat, int *num_file,
for (int i = 0; i < num_pat; i++) {
if (has_special_wildchar(pat[i])
&& !(vim_backtick(pat[i]) && pat[i][1] == '=')) {
- return mch_expand_wildcards(num_pat, pat, num_file, file, flags);
+ return os_expand_wildcards(num_pat, pat, num_file, file, flags);
}
}
#endif
@@ -1203,7 +1220,7 @@ int gen_expand_wildcards(int num_pat, char_u **pat, int *num_file,
}
} else {
// First expand environment variables, "~/" and "~user/".
- if (has_env_var(p) || *p == '~') {
+ if ((has_env_var(p) && !(flags & EW_NOTENV)) || *p == '~') {
p = expand_env_save_opt(p, true);
if (p == NULL)
p = pat[i];
@@ -1216,8 +1233,8 @@ int gen_expand_wildcards(int num_pat, char_u **pat, int *num_file,
else if (has_env_var(p) || *p == '~') {
xfree(p);
ga_clear_strings(&ga);
- i = mch_expand_wildcards(num_pat, pat, num_file, file,
- flags | EW_KEEPDOLLAR);
+ i = os_expand_wildcards(num_pat, pat, num_file, file,
+ flags | EW_KEEPDOLLAR);
recursive = false;
return i;
}
@@ -1905,16 +1922,16 @@ int pathcmp(const char *p, const char *q, int maxlen)
: c1 - c2; // no match
}
- i += MB_PTR2LEN((char_u *)p + i);
- j += MB_PTR2LEN((char_u *)q + j);
+ i += utfc_ptr2len((char_u *)p + i);
+ j += utfc_ptr2len((char_u *)q + j);
}
if (s == NULL) { // "i" or "j" ran into "maxlen"
return 0;
}
c1 = PTR2CHAR((char_u *)s + i);
- c2 = PTR2CHAR((char_u *)s + i + MB_PTR2LEN((char_u *)s + i));
- /* ignore a trailing slash, but not "//" or ":/" */
+ c2 = PTR2CHAR((char_u *)s + i + utfc_ptr2len((char_u *)s + i));
+ // ignore a trailing slash, but not "//" or ":/"
if (c2 == NUL
&& i > 0
&& !after_pathsep((char *)s, (char *)s + i)
@@ -1923,10 +1940,12 @@ int pathcmp(const char *p, const char *q, int maxlen)
#else
&& c1 == '/'
#endif
- )
- return 0; /* match with trailing slash */
- if (s == q)
- return -1; /* no match */
+ ) {
+ return 0; // match with trailing slash
+ }
+ if (s == q) {
+ return -1; // no match
+ }
return 1;
}
diff --git a/src/nvim/path.h b/src/nvim/path.h
index 4e466d1b71..15abd19646 100644
--- a/src/nvim/path.h
+++ b/src/nvim/path.h
@@ -20,11 +20,12 @@
#define EW_KEEPDOLLAR 0x800 /* do not escape $, $var is expanded */
/* Note: mostly EW_NOTFOUND and EW_SILENT are mutually exclusive: EW_NOTFOUND
* is used when executing commands and EW_SILENT for interactive expanding. */
-#define EW_ALLLINKS 0x1000 // also links not pointing to existing file
-#define EW_SHELLCMD 0x2000 // called from expand_shellcmd(), don't check
- // if executable is in $PATH
-#define EW_DODOT 0x4000 // also files starting with a dot
-#define EW_EMPTYOK 0x8000 // no matches is not an error
+#define EW_ALLLINKS 0x1000 // also links not pointing to existing file
+#define EW_SHELLCMD 0x2000 // called from expand_shellcmd(), don't check
+ // if executable is in $PATH
+#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
/// Return value for the comparison of two files. Also @see path_full_compare.
typedef enum file_comparison {
diff --git a/src/nvim/po/af.po b/src/nvim/po/af.po
index 61ad72d7e0..79048eac39 100644
--- a/src/nvim/po/af.po
+++ b/src/nvim/po/af.po
@@ -100,7 +100,7 @@ msgid "E88: Cannot go before first buffer"
msgstr "E88: Kan nie vóór eerste buffer gaan nie"
#, fuzzy, c-format
-#~ msgid "E89: %s will be killed(add ! to override)"
+#~ msgid "E89: %s will be killed (add ! to override)"
#~ msgstr "E189: \"%s\" bestaan (gebruik ! om te dwing)"
#, c-format
diff --git a/src/nvim/po/check.vim b/src/nvim/po/check.vim
index eae27ef74d..650c6155e2 100644
--- a/src/nvim/po/check.vim
+++ b/src/nvim/po/check.vim
@@ -47,6 +47,17 @@ let wsv = winsaveview()
let error = 0
while 1
+ let lnum = line('.')
+ if getline(lnum) =~ 'msgid "Text;.*;"'
+ if getline(lnum + 1) !~ '^msgstr "\([^;]\+;\)\+"'
+ echomsg 'Mismatching ; in line ' . (lnum + 1)
+ echomsg 'Did you forget the trailing semicolon?'
+ if error == 0
+ let error = lnum + 1
+ endif
+ endif
+ endif
+
if getline(line('.') - 1) !~ "no-c-format"
" go over the "msgid" and "msgid_plural" lines
let prevfromline = 'foobar'
diff --git a/src/nvim/po/fi.po b/src/nvim/po/fi.po
index 4612988c95..f568a34b3c 100644
--- a/src/nvim/po/fi.po
+++ b/src/nvim/po/fi.po
@@ -323,7 +323,7 @@ msgid "E88: Cannot go before first buffer"
msgstr "E88: Ensimmäisen puskurin ohi ei voi edetä"
#, fuzzy, c-format
-#~ msgid "E89: %s will be killed(add ! to override)"
+#~ msgid "E89: %s will be killed (add ! to override)"
#~ msgstr "E189: %s on jo olemassa (lisää komentoon ! ohittaaksesi)"
#, fuzzy, c-format
diff --git a/src/nvim/po/uk.po b/src/nvim/po/uk.po
index 211d38e53a..19ea8e897a 100644
--- a/src/nvim/po/uk.po
+++ b/src/nvim/po/uk.po
@@ -87,8 +87,8 @@ msgid "E88: Cannot go before first buffer"
msgstr "E88: Це вже найперший буфер"
#, c-format
-msgid "E89: %s will be killed(add ! to override)"
-msgstr "E89: «%s» буде вбито(! щоб не зважати)"
+msgid "E89: %s will be killed (add ! to override)"
+msgstr "E89: «%s» буде вбито (! щоб не зважати)"
#, c-format
msgid ""
diff --git a/src/nvim/popupmnu.c b/src/nvim/popupmnu.c
index 7a7f8a9d75..c712762bdf 100644
--- a/src/nvim/popupmnu.c
+++ b/src/nvim/popupmnu.c
@@ -38,7 +38,8 @@ static int pum_width; // width of displayed pum items
static int pum_base_width; // width of pum items base
static int pum_kind_width; // width of pum items kind column
static int pum_extra_width; // width of extra stuff
-static int pum_scrollbar; // TRUE when scrollbar present
+static int pum_scrollbar; // one when scrollbar present, else zero
+static bool pum_rl; // true when popupmenu is drawn 'rightleft'
static int pum_anchor_grid; // grid where position is defined
static int pum_row; // top row of pum
@@ -54,7 +55,6 @@ static bool pum_invalid = false; // the screen was just cleared
# include "popupmnu.c.generated.h"
#endif
#define PUM_DEF_HEIGHT 10
-#define PUM_DEF_WIDTH 15
static void pum_compute_size(void)
{
@@ -63,9 +63,12 @@ static void pum_compute_size(void)
pum_kind_width = 0;
pum_extra_width = 0;
for (int i = 0; i < pum_size; i++) {
- int w = vim_strsize(pum_array[i].pum_text);
- if (pum_base_width < w) {
- pum_base_width = w;
+ int w;
+ if (pum_array[i].pum_text != NULL) {
+ w = vim_strsize(pum_array[i].pum_text);
+ if (pum_base_width < w) {
+ pum_base_width = w;
+ }
}
if (pum_array[i].pum_kind != NULL) {
w = vim_strsize(pum_array[i].pum_kind) + 1;
@@ -84,7 +87,7 @@ static void pum_compute_size(void)
/// Show the popup menu with items "array[size]".
/// "array" must remain valid until pum_undisplay() is called!
-/// When possible the leftmost character is aligned with screen column "col".
+/// When possible the leftmost character is aligned with cursor column.
/// The menu appears above the screen line "row" or at "row" + "height" - 1.
///
/// @param array
@@ -97,13 +100,12 @@ static void pum_compute_size(void)
void pum_display(pumitem_T *array, int size, int selected, bool array_changed,
int cmd_startcol)
{
- int def_width;
int context_lines;
int above_row;
int below_row;
int redo_count = 0;
- int row;
- int col;
+ int pum_win_row;
+ int cursor_col;
if (!pum_is_visible) {
// To keep the code simple, we only allow changing the
@@ -112,6 +114,8 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed,
|| (State == CMDLINE && ui_has(kUIWildmenu));
}
+ pum_rl = (curwin->w_p_rl && State != CMDLINE);
+
do {
// Mark the pum as visible already here,
// to avoid that must_redraw is set when 'cursorcolumn' is on.
@@ -123,23 +127,23 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed,
// wildoptions=pum
if (State == CMDLINE) {
- row = ui_has(kUICmdline) ? 0 : cmdline_row;
- col = cmd_startcol;
+ pum_win_row = ui_has(kUICmdline) ? 0 : cmdline_row;
+ cursor_col = cmd_startcol;
pum_anchor_grid = ui_has(kUICmdline) ? -1 : DEFAULT_GRID_HANDLE;
} else {
// anchor position: the start of the completed word
- row = curwin->w_wrow;
- if (curwin->w_p_rl) {
- col = curwin->w_width - curwin->w_wcol - 1;
+ pum_win_row = curwin->w_wrow;
+ if (pum_rl) {
+ cursor_col = curwin->w_width - curwin->w_wcol - 1;
} else {
- col = curwin->w_wcol;
+ cursor_col = curwin->w_wcol;
}
pum_anchor_grid = (int)curwin->w_grid.handle;
if (!ui_has(kUIMultigrid)) {
pum_anchor_grid = (int)default_grid.handle;
- row += curwin->w_winrow;
- col += curwin->w_wincol;
+ pum_win_row += curwin->w_winrow;
+ cursor_col += curwin->w_wincol;
}
}
@@ -154,14 +158,15 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed,
ADD(item, STRING_OBJ(cstr_to_string((char *)array[i].pum_info)));
ADD(arr, ARRAY_OBJ(item));
}
- ui_call_popupmenu_show(arr, selected, row, col, pum_anchor_grid);
+ ui_call_popupmenu_show(arr, selected, pum_win_row, cursor_col,
+ pum_anchor_grid);
} else {
ui_call_popupmenu_select(selected);
return;
}
}
- def_width = PUM_DEF_WIDTH;
+ int def_width = (int)p_pw;
win_T *pvwin = NULL;
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
@@ -190,11 +195,11 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed,
pum_height = (int)p_ph;
}
- // Put the pum below "row" if possible. If there are few lines decide on
- // where there is more room.
- if (row + 2 >= below_row - pum_height
- && row - above_row > (below_row - above_row) / 2) {
- // pum above "row"
+ // Put the pum below "pum_win_row" if possible.
+ // If there are few lines decide on where there is more room.
+ if (pum_win_row + 2 >= below_row - pum_height
+ && pum_win_row - above_row > (below_row - above_row) / 2) {
+ // pum above "pum_win_row"
pum_above = true;
// Leave two lines of context if possible
@@ -204,12 +209,12 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed,
context_lines = curwin->w_wrow - curwin->w_cline_row;
}
- if (row >= size + context_lines) {
- pum_row = row - size - context_lines;
+ if (pum_win_row >= size + context_lines) {
+ pum_row = pum_win_row - size - context_lines;
pum_height = size;
} else {
pum_row = 0;
- pum_height = row - context_lines;
+ pum_height = pum_win_row - context_lines;
}
if ((p_ph > 0) && (pum_height > p_ph)) {
@@ -217,7 +222,7 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed,
pum_height = (int)p_ph;
}
} else {
- // pum below "row"
+ // pum below "pum_win_row"
pum_above = false;
// Leave two lines of context if possible
@@ -228,7 +233,7 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed,
+ curwin->w_cline_height - curwin->w_wrow;
}
- pum_row = row + context_lines;
+ pum_row = pum_win_row + context_lines;
if (size > below_row - pum_row) {
pum_height = below_row - pum_row;
} else {
@@ -245,16 +250,10 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed,
return;
}
- // If there is a preview window above, avoid drawing over it.
- // Do keep at least 10 entries.
- if (pvwin != NULL && pum_row < above_row && pum_height > 10) {
- if (row - above_row < 10) {
- pum_row = row - 10;
- pum_height = 10;
- } else {
- pum_row = above_row;
- pum_height = row - above_row;
- }
+ // If there is a preview window above avoid drawing over it.
+ if (pvwin != NULL && pum_row < above_row && pum_height > above_row) {
+ pum_row = above_row;
+ pum_height = pum_win_row - above_row;
}
if (pum_external) {
return;
@@ -277,12 +276,14 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed,
def_width = max_width;
}
- if ((((col < Columns - PUM_DEF_WIDTH) || (col < Columns - max_width))
- && !curwin->w_p_rl)
- || (curwin->w_p_rl && ((col > PUM_DEF_WIDTH) || (col > max_width)))) {
- // align pum column with "col"
- pum_col = col;
- if (curwin->w_p_rl) {
+ if ((((cursor_col < Columns - p_pw) || (cursor_col < Columns - max_width))
+ && !pum_rl)
+ || (pum_rl && ((cursor_col > p_pw) || (cursor_col > max_width)))) {
+ // align pum with "cursor_col"
+ pum_col = cursor_col;
+
+ // start with the maximum space available
+ if (pum_rl) {
pum_width = pum_col - pum_scrollbar + 1;
} else {
assert(Columns - pum_col - pum_scrollbar >= INT_MIN
@@ -291,16 +292,62 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed,
}
if ((pum_width > max_width + pum_kind_width + pum_extra_width + 1)
- && (pum_width > PUM_DEF_WIDTH)) {
+ && (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;
- if (pum_width < PUM_DEF_WIDTH) {
- pum_width = PUM_DEF_WIDTH;
+ 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))) {
+ // align pum edge with "cursor_col"
+ if (pum_rl && W_ENDCOL(curwin) < max_width + pum_scrollbar + 1) {
+ pum_col = cursor_col + max_width + pum_scrollbar + 1;
+ if (pum_col >= Columns) {
+ pum_col = Columns - 1;
+ }
+ } else if (!pum_rl) {
+ if (curwin->w_wincol > Columns - max_width - pum_scrollbar
+ && max_width <= p_pw) {
+ // use full width to end of the screen
+ pum_col = cursor_col - max_width - pum_scrollbar;
+ if (pum_col < 0) {
+ pum_col = 0;
+ }
+ }
+ }
+
+ if (pum_rl) {
+ pum_width = pum_col - pum_scrollbar + 1;
+ } else {
+ pum_width = Columns - 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;
+ }
+ } else {
+ if (pum_width >= Columns - pum_col) {
+ pum_width = Columns - pum_col - 1;
+ }
+ }
+ } else if (pum_width > max_width + pum_kind_width + pum_extra_width + 1
+ && pum_width > p_pw) {
+ pum_width = max_width + pum_kind_width + pum_extra_width + 1;
+ if (pum_width < p_pw) {
+ pum_width = (int)p_pw;
}
}
} else if (Columns < def_width) {
// not enough room, will use what we have
- if (curwin->w_p_rl) {
+ if (pum_rl) {
assert(Columns - 1 >= INT_MIN);
pum_col = (int)(Columns - 1);
} else {
@@ -309,12 +356,12 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed,
assert(Columns - 1 >= INT_MIN);
pum_width = (int)(Columns - 1);
} else {
- if (max_width > PUM_DEF_WIDTH) {
+ if (max_width > p_pw) {
// truncate
- max_width = PUM_DEF_WIDTH;
+ max_width = (int)p_pw;
}
- if (curwin->w_p_rl) {
+ if (pum_rl) {
pum_col = max_width - 1;
} else {
assert(Columns - max_width >= INT_MIN
@@ -353,7 +400,7 @@ void pum_redraw(void)
int grid_width = pum_width;
int col_off = 0;
bool extra_space = false;
- if (curwin->w_p_rl) {
+ if (pum_rl) {
col_off = pum_width;
if (pum_col < curwin->w_wincol + curwin->w_width - 1) {
grid_width += 1;
@@ -414,7 +461,7 @@ void pum_redraw(void)
// prepend a space if there is room
if (extra_space) {
- if (curwin->w_p_rl) {
+ if (pum_rl) {
grid_putchar(&pum_grid, ' ', row, col_off + 1, attr);
} else {
grid_putchar(&pum_grid, ' ', row, col_off - 1, attr);
@@ -461,7 +508,7 @@ void pum_redraw(void)
st = (char_u *)transstr((const char *)s);
*p = saved;
- if (curwin->w_p_rl) {
+ if (pum_rl) {
char_u *rt = reverse_text(st);
char_u *rt_start = rt;
int size = vim_strsize(rt);
@@ -474,7 +521,7 @@ void pum_redraw(void)
if (size < pum_width) {
// Most left character requires 2-cells but only 1 cell
- // is available on screen. Put a '<' on the left of the
+ // is available on screen. Put a '<' on the left of the
// pum item
*(--rt) = '<';
size++;
@@ -496,7 +543,7 @@ void pum_redraw(void)
}
// Display two spaces for a Tab.
- if (curwin->w_p_rl) {
+ if (pum_rl) {
grid_puts_len(&pum_grid, (char_u *)" ", 2, row, col - 1,
attr);
col -= 2;
@@ -531,7 +578,7 @@ void pum_redraw(void)
break;
}
- if (curwin->w_p_rl) {
+ if (pum_rl) {
grid_fill(&pum_grid, row, row + 1, col_off - pum_base_width - n + 1,
col + 1, ' ', ' ', attr);
col = col_off - pum_base_width - n + 1;
@@ -543,7 +590,7 @@ void pum_redraw(void)
totwidth = pum_base_width + n;
}
- if (curwin->w_p_rl) {
+ if (pum_rl) {
grid_fill(&pum_grid, row, row + 1, col_off - pum_width + 1, col + 1,
' ', ' ', attr);
} else {
@@ -552,7 +599,7 @@ void pum_redraw(void)
}
if (pum_scrollbar > 0) {
- if (curwin->w_p_rl) {
+ if (pum_rl) {
grid_putchar(&pum_grid, ' ', row, col_off - pum_width,
i >= thumb_pos && i < thumb_pos + thumb_heigth
? attr_thumb : attr_scroll);
@@ -864,11 +911,18 @@ void pum_set_event_info(dict_T *dict)
if (!pum_visible()) {
return;
}
- tv_dict_add_nr(dict, S_LEN("height"), pum_height);
- tv_dict_add_nr(dict, S_LEN("width"), pum_width);
- tv_dict_add_nr(dict, S_LEN("row"), pum_row);
- tv_dict_add_nr(dict, S_LEN("col"), pum_col);
+ double w, h, r, c;
+ if (!ui_pum_get_pos(&w, &h, &r, &c)) {
+ w = (double)pum_width;
+ h = (double)pum_height;
+ r = (double)pum_row;
+ c = (double)pum_col;
+ }
+ tv_dict_add_float(dict, S_LEN("height"), h);
+ tv_dict_add_float(dict, S_LEN("width"), w);
+ tv_dict_add_float(dict, S_LEN("row"), r);
+ tv_dict_add_float(dict, S_LEN("col"), c);
tv_dict_add_nr(dict, S_LEN("size"), pum_size);
- tv_dict_add_special(dict, S_LEN("scrollbar"),
- pum_scrollbar ? kSpecialVarTrue : kSpecialVarFalse);
+ tv_dict_add_bool(dict, S_LEN("scrollbar"),
+ pum_scrollbar ? kBoolVarTrue : kBoolVarFalse);
}
diff --git a/src/nvim/pos.h b/src/nvim/pos.h
index 47d253e083..8e86ea08c5 100644
--- a/src/nvim/pos.h
+++ b/src/nvim/pos.h
@@ -14,6 +14,10 @@ typedef int colnr_T;
enum { MAXLNUM = 0x7fffffff };
/// Maximal column number, 31 bits
enum { MAXCOL = 0x7fffffff };
+// Minimum line number
+enum { MINLNUM = 1 };
+// minimum column number
+enum { MINCOL = 1 };
/*
* position in file or buffer
diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c
index 301f72fa79..ddce1e922d 100644
--- a/src/nvim/quickfix.c
+++ b/src/nvim/quickfix.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
-/*
- * quickfix.c: functions for quickfix mode, using a file with error messages
- */
+// quickfix.c: functions for quickfix mode, using a file with error messages
#include <assert.h>
#include <inttypes.h>
@@ -53,9 +51,7 @@ struct dir_stack_T {
char_u *dirname;
};
-/*
- * For each error the next struct is allocated and linked in a list.
- */
+// For each error the next struct is allocated and linked in a list.
typedef struct qfline_S qfline_T;
struct qfline_S {
qfline_T *qf_next; ///< pointer to next error in the list
@@ -74,12 +70,19 @@ struct qfline_S {
char_u qf_valid; ///< valid error message detected
};
-/*
- * There is a stack of error lists.
- */
+// There is a stack of error lists.
#define LISTCOUNT 10
#define INVALID_QFIDX (-1)
+/// Quickfix list type.
+typedef enum
+{
+ QFLT_QUICKFIX, ///< Quickfix list - global list
+ QFLT_LOCATION, ///< Location list - per window list
+ QFLT_INTERNAL ///< Internal - Temporary list used by
+ // getqflist()/getloclist()
+} qfltype_T;
+
/// Quickfix/Location list definition
///
/// Usually the list contains one or more entries. But an empty list can be
@@ -87,6 +90,7 @@ struct qfline_S {
/// information and entries can be added later using setqflist()/setloclist().
typedef struct qf_list_S {
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
@@ -110,16 +114,15 @@ typedef struct qf_list_S {
/// Quickfix/Location list stack definition
/// Contains a list of quickfix/location lists (qf_list_T)
struct qf_info_S {
- /*
- * Count of references to this list. Used only for location lists.
- * When a location list window reference this list, qf_refcount
- * will be 2. Otherwise, qf_refcount will be 1. When qf_refcount
- * reaches 0, the list is freed.
- */
+ // Count of references to this list. Used only for location lists.
+ // When a location list window reference this list, qf_refcount
+ // will be 2. Otherwise, qf_refcount will be 1. When qf_refcount
+ // reaches 0, the list is freed.
int qf_refcount;
- int qf_listcount; /* current number of lists */
- int qf_curlist; /* current error list */
+ int qf_listcount; // current number of lists
+ int qf_curlist; // current error list
qf_list_T qf_lists[LISTCOUNT];
+ qfltype_T qfl_type; // type of list
};
static qf_info_T ql_info; // global quickfix list
@@ -127,33 +130,38 @@ static unsigned last_qf_id = 0; // Last Used quickfix list id
#define FMT_PATTERNS 11 // maximum number of % recognized
-/*
- * Structure used to hold the info of one part of 'errorformat'
- */
+// Structure used to hold the info of one part of 'errorformat'
typedef struct efm_S efm_T;
struct efm_S {
- regprog_T *prog; /* pre-formatted part of 'errorformat' */
- efm_T *next; /* pointer to next (NULL if last) */
- char_u addr[FMT_PATTERNS]; /* indices of used % patterns */
- char_u prefix; /* prefix of this format line: */
- /* 'D' enter directory */
- /* 'X' leave directory */
- /* 'A' start of multi-line message */
- /* 'E' error message */
- /* 'W' warning message */
- /* 'I' informational message */
- /* 'C' continuation line */
- /* 'Z' end of multi-line message */
- /* 'G' general, unspecific message */
- /* 'P' push file (partial) message */
- /* 'Q' pop/quit file (partial) message */
- /* 'O' overread (partial) message */
- char_u flags; /* additional flags given in prefix */
- /* '-' do not include this line */
- /* '+' include whole line in message */
- int conthere; /* %> used */
+ regprog_T *prog; // pre-formatted part of 'errorformat'
+ efm_T *next; // pointer to next (NULL if last)
+ char_u addr[FMT_PATTERNS]; // indices of used % patterns
+ char_u prefix; // prefix of this format line:
+ // 'D' enter directory
+ // 'X' leave directory
+ // 'A' start of multi-line message
+ // 'E' error message
+ // 'W' warning message
+ // 'I' informational message
+ // 'C' continuation line
+ // 'Z' end of multi-line message
+ // 'G' general, unspecific message
+ // 'P' push file (partial) message
+ // 'Q' pop/quit file (partial) message
+ // 'O' overread (partial) message
+ char_u flags; // additional flags given in prefix
+ // '-' do not include this line
+ // '+' include whole line in message
+ int conthere; // %> used
};
+/// List of location lists to be deleted.
+/// Used to delay the deletion of locations lists by autocmds.
+typedef struct qf_delq_S {
+ struct qf_delq_S *next;
+ qf_info_T *qi;
+} qf_delq_T;
+
enum {
QF_FAIL = 0,
QF_OK = 1,
@@ -163,6 +171,8 @@ enum {
QF_MULTISCAN = 5,
};
+/// State information used to parse lines and add entries to a quickfix/location
+/// list.
typedef struct {
char_u *linebuf;
size_t linelen;
@@ -196,14 +206,19 @@ typedef struct {
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "quickfix.c.generated.h"
#endif
-/* Quickfix window check helper macro */
+
+static char_u *e_no_more_items = (char_u *)N_("E553: No more items");
+
+// Quickfix window check helper macro
#define IS_QF_WINDOW(wp) (bt_quickfix(wp->w_buffer) && wp->w_llist_ref == NULL)
-/* Location list window check helper macro */
+// Location list window check helper macro
#define IS_LL_WINDOW(wp) (bt_quickfix(wp->w_buffer) && wp->w_llist_ref != NULL)
// Quickfix and location list stack check helper macros
-#define IS_QF_STACK(qi) (qi == &ql_info)
-#define IS_LL_STACK(qi) (qi != &ql_info)
+#define IS_QF_STACK(qi) (qi->qfl_type == QFLT_QUICKFIX)
+#define IS_LL_STACK(qi) (qi->qfl_type == QFLT_LOCATION)
+#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'
@@ -211,6 +226,14 @@ typedef struct {
//
#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
+// Quickfix item index starts from 1, so i below starts at 1
+#define FOR_ALL_QFL_ITEMS(qfl, qfp, i) \
+ for (i = 1, qfp = qfl->qf_start; /* NOLINT(readability/braces) */ \
+ !got_int && i <= qfl->qf_count && qfp != NULL; \
+ i++, qfp = qfp->qf_next)
+
+
// Looking up a buffer can be slow if there are many. Remember the last one
// to make this a lot faster if there are multiple matches in the same file.
static char_u *qf_last_bufname = NULL;
@@ -218,6 +241,50 @@ static bufref_T qf_last_bufref = { NULL, 0, 0 };
static char *e_loc_list_changed = N_("E926: Current location list was changed");
+// Counter to prevent autocmds from freeing up location lists when they are
+// still being used.
+static int quickfix_busy = 0;
+static qf_delq_T *qf_delq_head = NULL;
+
+/// Process the next line from a file/buffer/list/string and add it
+/// to the quickfix list 'qfl'.
+static int qf_init_process_nextline(qf_list_T *qfl,
+ efm_T *fmt_first,
+ qfstate_T *state,
+ qffields_T *fields)
+{
+ int status;
+
+ // Get the next line from a file/buffer/list/string
+ status = qf_get_nextline(state);
+ if (status != QF_OK) {
+ return status;
+ }
+
+ status = qf_parse_line(qfl, state->linebuf, state->linelen,
+ fmt_first, fields);
+ if (status != QF_OK) {
+ return status;
+ }
+
+ return qf_add_entry(qfl,
+ qfl->qf_directory,
+ (*fields->namebuf || qfl->qf_directory != NULL)
+ ? fields->namebuf
+ : ((qfl->qf_currfile != NULL && fields->valid)
+ ? qfl->qf_currfile : (char_u *)NULL),
+ fields->module,
+ 0,
+ fields->errmsg,
+ fields->lnum,
+ fields->col,
+ fields->use_viscol,
+ fields->pattern,
+ fields->enr,
+ fields->type,
+ fields->valid);
+}
+
/// Read the errorfile "efile" into memory, line by line, building the error
/// list. Set the error list's title to qf_title.
///
@@ -229,8 +296,9 @@ static char *e_loc_list_changed = N_("E926: Current location list was changed");
/// @params enc If non-NULL, encoding used to parse errors
///
/// @returns -1 for error, number of errors for success.
-int qf_init(win_T *wp, char_u *efile, char_u *errorformat, int newlist,
- char_u *qf_title, char_u *enc)
+int qf_init(win_T *wp, const char_u *restrict efile,
+ char_u *restrict errorformat, int newlist,
+ const char_u *restrict qf_title, char_u *restrict enc)
{
qf_info_T *qi = &ql_info;
@@ -264,15 +332,151 @@ static struct fmtpattern
{ 'o', ".\\+" }
};
+/// Convert an errorformat pattern to a regular expression pattern.
+/// See fmt_pat definition above for the list of supported patterns. The
+/// pattern specifier is supplied in "efmpat". The converted pattern is stored
+/// in "regpat". Returns a pointer to the location after the pattern.
+static char_u * efmpat_to_regpat(
+ const char_u *efmpat,
+ char_u *regpat,
+ efm_T *efminfo,
+ int idx,
+ int round,
+ char_u *errmsg,
+ size_t errmsglen)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (efminfo->addr[idx]) {
+ // Each errorformat pattern can occur only once
+ snprintf((char *)errmsg, errmsglen,
+ _("E372: Too many %%%c in format string"), *efmpat);
+ EMSG(errmsg);
+ return NULL;
+ }
+ if ((idx && idx < 6
+ && vim_strchr((char_u *)"DXOPQ", efminfo->prefix) != NULL)
+ || (idx == 6
+ && vim_strchr((char_u *)"OPQ", efminfo->prefix) == NULL)) {
+ snprintf((char *)errmsg, errmsglen,
+ _("E373: Unexpected %%%c in format string"), *efmpat);
+ EMSG(errmsg);
+ return NULL;
+ }
+ efminfo->addr[idx] = (char_u)++round;
+ *regpat++ = '\\';
+ *regpat++ = '(';
+#ifdef BACKSLASH_IN_FILENAME
+ if (*efmpat == 'f') {
+ // Also match "c:" in the file name, even when
+ // checking for a colon next: "%f:".
+ // "\%(\a:\)\="
+ STRCPY(regpat, "\\%(\\a:\\)\\=");
+ regpat += 10;
+ }
+#endif
+ if (*efmpat == 'f' && efmpat[1] != NUL) {
+ if (efmpat[1] != '\\' && efmpat[1] != '%') {
+ // A file name may contain spaces, but this isn't
+ // in "\f". For "%f:%l:%m" there may be a ":" in
+ // the file name. Use ".\{-1,}x" instead (x is
+ // the next character), the requirement that :999:
+ // follows should work.
+ STRCPY(regpat, ".\\{-1,}");
+ regpat += 7;
+ } else {
+ // File name followed by '\\' or '%': include as
+ // many file name chars as possible.
+ STRCPY(regpat, "\\f\\+");
+ regpat += 4;
+ }
+ } else {
+ char_u *srcptr = (char_u *)fmt_pat[idx].pattern;
+ while ((*regpat = *srcptr++) != NUL) {
+ regpat++;
+ }
+ }
+ *regpat++ = '\\';
+ *regpat++ = ')';
+
+ return regpat;
+}
+
+/// Convert a scanf like format in 'errorformat' to a regular expression.
+/// Returns a pointer to the location after the pattern.
+static char_u * scanf_fmt_to_regpat(
+ const char_u **pefmp,
+ const char_u *efm,
+ int len,
+ char_u *regpat,
+ char_u *errmsg,
+ size_t errmsglen)
+ FUNC_ATTR_NONNULL_ALL
+{
+ const char_u *efmp = *pefmp;
+
+ if (*efmp == '[' || *efmp == '\\') {
+ if ((*regpat++ = *efmp) == '[') { // %*[^a-z0-9] etc.
+ if (efmp[1] == '^') {
+ *regpat++ = *++efmp;
+ }
+ if (efmp < efm + len) {
+ *regpat++ = *++efmp; // could be ']'
+ while (efmp < efm + len && (*regpat++ = *++efmp) != ']') {
+ }
+ if (efmp == efm + len) {
+ EMSG(_("E374: Missing ] in format string"));
+ return NULL;
+ }
+ }
+ } else if (efmp < efm + len) { // %*\D, %*\s etc.
+ *regpat++ = *++efmp;
+ }
+ *regpat++ = '\\';
+ *regpat++ = '+';
+ } else {
+ // TODO(vim): scanf()-like: %*ud, %*3c, %*f, ... ?
+ snprintf((char *)errmsg, errmsglen,
+ _("E375: Unsupported %%%c in format string"), *efmp);
+ EMSG(errmsg);
+ return NULL;
+ }
+
+ *pefmp = efmp;
+
+ return regpat;
+}
+
+/// Analyze/parse an errorformat prefix.
+static const char_u *efm_analyze_prefix(const char_u *efmp, efm_T *efminfo,
+ char_u *errmsg, size_t errmsglen)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (vim_strchr((char_u *)"+-", *efmp) != NULL) {
+ efminfo->flags = *efmp++;
+ }
+ if (vim_strchr((char_u *)"DXAEWICZGOPQ", *efmp) != NULL) {
+ efminfo->prefix = *efmp;
+ } else {
+ snprintf((char *)errmsg, errmsglen,
+ _("E376: Invalid %%%c in format string prefix"), *efmp);
+ EMSG(errmsg);
+ return NULL;
+ }
+
+ return efmp;
+}
+
+
// Converts a 'errorformat' string to regular expression pattern
-static int efm_to_regpat(char_u *efm, int len, efm_T *fmt_ptr,
- char_u *regpat, char_u *errmsg)
+static int efm_to_regpat(const char_u *efm, int len, efm_T *fmt_ptr,
+ char_u *regpat, char_u *errmsg, size_t errmsglen)
+ FUNC_ATTR_NONNULL_ALL
{
// Build regexp pattern from current 'errorformat' option
char_u *ptr = regpat;
*ptr++ = '^';
int round = 0;
- for (char_u *efmp = efm; efmp < efm + len; efmp++) {
+ for (const char_u *efmp = efm; efmp < efm + len; efmp++) {
if (*efmp == '%') {
efmp++;
int idx;
@@ -282,90 +486,17 @@ static int efm_to_regpat(char_u *efm, int len, efm_T *fmt_ptr,
}
}
if (idx < FMT_PATTERNS) {
- if (fmt_ptr->addr[idx]) {
- snprintf((char *)errmsg, CMDBUFFSIZE + 1,
- _("E372: Too many %%%c in format string"), *efmp);
- EMSG(errmsg);
- return -1;
- }
- if ((idx
- && idx < 6
- && vim_strchr((char_u *)"DXOPQ", fmt_ptr->prefix) != NULL)
- || (idx == 6
- && vim_strchr((char_u *)"OPQ", fmt_ptr->prefix) == NULL)) {
- snprintf((char *)errmsg, CMDBUFFSIZE + 1,
- _("E373: Unexpected %%%c in format string"), *efmp);
- EMSG(errmsg);
- return -1;
+ ptr = efmpat_to_regpat(efmp, ptr, fmt_ptr, idx, round, errmsg,
+ errmsglen);
+ if (ptr == NULL) {
+ return FAIL;
}
round++;
- fmt_ptr->addr[idx] = (char_u)round;
- *ptr++ = '\\';
- *ptr++ = '(';
-#ifdef BACKSLASH_IN_FILENAME
- if (*efmp == 'f') {
- // Also match "c:" in the file name, even when
- // checking for a colon next: "%f:".
- // "\%(\a:\)\="
- STRCPY(ptr, "\\%(\\a:\\)\\=");
- ptr += 10;
- }
-#endif
- if (*efmp == 'f' && efmp[1] != NUL) {
- if (efmp[1] != '\\' && efmp[1] != '%') {
- // A file name may contain spaces, but this isn't
- // in "\f". For "%f:%l:%m" there may be a ":" in
- // the file name. Use ".\{-1,}x" instead (x is
- // the next character), the requirement that :999:
- // follows should work.
- STRCPY(ptr, ".\\{-1,}");
- ptr += 7;
- } else {
- // File name followed by '\\' or '%': include as
- // many file name chars as possible.
- STRCPY(ptr, "\\f\\+");
- ptr += 4;
- }
- } else {
- char_u *srcptr = (char_u *)fmt_pat[idx].pattern;
- while ((*ptr = *srcptr++) != NUL) {
- ptr++;
- }
- }
- *ptr++ = '\\';
- *ptr++ = ')';
} else if (*efmp == '*') {
- if (*++efmp == '[' || *efmp == '\\') {
- if ((*ptr++ = *efmp) == '[') { // %*[^a-z0-9] etc.
- if (efmp[1] == '^') {
- *ptr++ = *++efmp;
- }
- if (efmp < efm + len) {
- efmp++;
- *ptr++ = *efmp; // could be ']'
- while (efmp < efm + len) {
- efmp++;
- if ((*ptr++ = *efmp) == ']') {
- break;
- }
- }
- if (efmp == efm + len) {
- EMSG(_("E374: Missing ] in format string"));
- return -1;
- }
- }
- } else if (efmp < efm + len) { // %*\D, %*\s etc.
- efmp++;
- *ptr++ = *efmp;
- }
- *ptr++ = '\\';
- *ptr++ = '+';
- } else {
- // TODO(vim): scanf()-like: %*ud, %*3c, %*f, ... ?
- snprintf((char *)errmsg, CMDBUFFSIZE + 1,
- _("E375: Unsupported %%%c in format string"), *efmp);
- EMSG(errmsg);
- return -1;
+ efmp++;
+ ptr = scanf_fmt_to_regpat(&efmp, efm, len, ptr, errmsg, errmsglen);
+ if (ptr == NULL) {
+ return FAIL;
}
} else if (vim_strchr((char_u *)"%\\.^$~[", *efmp) != NULL) {
*ptr++ = *efmp; // regexp magic characters
@@ -374,22 +505,17 @@ static int efm_to_regpat(char_u *efm, int len, efm_T *fmt_ptr,
} else if (*efmp == '>') {
fmt_ptr->conthere = true;
} else if (efmp == efm + 1) { // analyse prefix
- if (vim_strchr((char_u *)"+-", *efmp) != NULL) {
- fmt_ptr->flags = *efmp++;
- }
- if (vim_strchr((char_u *)"DXAEWICZGOPQ", *efmp) != NULL) {
- fmt_ptr->prefix = *efmp;
- } else {
- snprintf((char *)errmsg, CMDBUFFSIZE + 1,
- _("E376: Invalid %%%c in format string prefix"), *efmp);
- EMSG(errmsg);
- return -1;
+ // prefix is allowed only at the beginning of the errorformat
+ // option part
+ efmp = efm_analyze_prefix(efmp, fmt_ptr, errmsg, errmsglen);
+ if (efmp == NULL) {
+ return FAIL;
}
} else {
snprintf((char *)errmsg, CMDBUFFSIZE + 1,
_("E377: Invalid %%%c in format string"), *efmp);
EMSG(errmsg);
- return -1;
+ return FAIL;
}
} else { // copy normal character
if (*efmp == '\\' && efmp + 1 < efm + len) {
@@ -405,7 +531,7 @@ static int efm_to_regpat(char_u *efm, int len, efm_T *fmt_ptr,
*ptr++ = '$';
*ptr = NUL;
- return 0;
+ return OK;
}
static efm_T *fmt_start = NULL; // cached across qf_parse_line() calls
@@ -421,7 +547,42 @@ static void free_efm_list(efm_T **efm_first)
fmt_start = NULL;
}
-// Parse 'errorformat' option
+/// Compute the size of the buffer used to convert a 'errorformat' pattern into
+/// a regular expression pattern.
+static size_t efm_regpat_bufsz(char_u *efm)
+{
+ size_t sz;
+
+ sz = (FMT_PATTERNS * 3) + (STRLEN(efm) << 2);
+ for (int i = FMT_PATTERNS - 1; i >= 0; ) {
+ sz += STRLEN(fmt_pat[i--].pattern);
+ }
+#ifdef BACKSLASH_IN_FILENAME
+ sz += 12; // "%f" can become twelve chars longer (see efm_to_regpat)
+#else
+ sz += 2; // "%f" can become two chars longer
+#endif
+
+ return sz;
+}
+
+/// Return the length of a 'errorformat' option part (separated by ",").
+static int efm_option_part_len(char_u *efm)
+{
+ int len;
+
+ for (len = 0; efm[len] != NUL && efm[len] != ','; len++) {
+ if (efm[len] == '\\' && efm[len + 1] != NUL) {
+ len++;
+ }
+ }
+
+ return len;
+}
+
+/// Parse the 'errorformat' option. Multiple parts in the 'errorformat' option
+/// are parsed and converted to regular expressions. Returns information about
+/// the parsed 'errorformat' option.
static efm_T * parse_efm_option(char_u *efm)
{
efm_T *fmt_ptr = NULL;
@@ -433,16 +594,8 @@ static efm_T * parse_efm_option(char_u *efm)
char_u *errmsg = xmalloc(errmsglen);
// Get some space to modify the format string into.
- size_t i = (FMT_PATTERNS * 3) + (STRLEN(efm) << 2);
- for (int round = FMT_PATTERNS - 1; round >= 0; ) {
- i += STRLEN(fmt_pat[round--].pattern);
- }
-#ifdef BACKSLASH_IN_FILENAME
- i += 12; // "%f" can become twelve chars longer (see efm_to_regpat)
-#else
- i += 2; // "%f" can become two chars longer
-#endif
- char_u *fmtstr = xmalloc(i);
+ size_t sz = efm_regpat_bufsz(efm);
+ char_u *fmtstr = xmalloc(sz);
while (efm[0] != NUL) {
// Allocate a new eformat structure and put it at the end of the list
@@ -455,13 +608,9 @@ static efm_T * parse_efm_option(char_u *efm)
fmt_last = fmt_ptr;
// Isolate one part in the 'errorformat' option
- for (len = 0; efm[len] != NUL && efm[len] != ','; len++) {
- if (efm[len] == '\\' && efm[len + 1] != NUL) {
- len++;
- }
- }
+ len = efm_option_part_len(efm);
- if (efm_to_regpat(efm, len, fmt_ptr, fmtstr, errmsg) == -1) {
+ if (efm_to_regpat(efm, len, fmt_ptr, fmtstr, errmsg, errmsglen) == FAIL) {
goto parse_efm_error;
}
if ((fmt_ptr->prog = vim_regcomp(fmtstr, RE_MAGIC + RE_STRING)) == NULL) {
@@ -487,6 +636,7 @@ parse_efm_end:
return fmt_first;
}
+/// Allocate more memory for the line buffer used for parsing lines.
static char_u *qf_grow_linebuf(qfstate_T *state, size_t newsz)
{
// If the line exceeds LINE_MAXLEN exclude the last
@@ -728,25 +878,41 @@ static int qf_get_nextline(qfstate_T *state)
return QF_OK;
}
-// Returns true if the specified quickfix/location list is empty.
-static bool qf_list_empty(const qf_info_T *qi, int qf_idx)
+/// Returns true if the specified quickfix/location stack is empty
+static bool qf_stack_empty(const qf_info_T *qi)
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
- if (qi == NULL || qf_idx < 0 || qf_idx >= LISTCOUNT) {
- return true;
- }
- return qi->qf_lists[qf_idx].qf_count <= 0;
+ return qi == NULL || qi->qf_listcount <= 0;
+}
+
+/// Returns true if the specified quickfix/location list is empty.
+static bool qf_list_empty(qf_list_T *qfl)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ return qfl == NULL || qfl->qf_count <= 0;
+}
+
+/// Returns true if the specified quickfix/location list is not empty and
+/// has valid entries.
+static bool qf_list_has_valid_entries(qf_list_T *qfl)
+{
+ return !qf_list_empty(qfl) && !qfl->qf_nonevalid;
+}
+
+/// Return a pointer to a list in the specified quickfix stack
+static qf_list_T * qf_get_list(qf_info_T *qi, int idx)
+{
+ return &qi->qf_lists[idx];
}
/// Parse a line and get the quickfix fields.
/// Return the QF_ status.
-static int qf_parse_line(qf_info_T *qi, int qf_idx, char_u *linebuf,
+static int qf_parse_line(qf_list_T *qfl, char_u *linebuf,
size_t linelen, efm_T *fmt_first, qffields_T *fields)
{
efm_T *fmt_ptr;
int idx = 0;
char_u *tail = NULL;
- qf_list_T *qfl = &qi->qf_lists[qf_idx];
int status;
restofline:
@@ -802,7 +968,7 @@ restofline:
qfl->qf_multiignore = false; // reset continuation
} else if (vim_strchr((char_u *)"CZ", idx) != NULL) {
// continuation of multi-line msg
- status = qf_parse_multiline_pfx(qi, qf_idx, idx, qfl, fields);
+ status = qf_parse_multiline_pfx(idx, qfl, fields);
if (status != QF_OK) {
return status;
}
@@ -825,6 +991,79 @@ restofline:
return QF_OK;
}
+// Allocate the fields used for parsing lines and populating a quickfix list.
+static void qf_alloc_fields(qffields_T *pfields)
+ FUNC_ATTR_NONNULL_ALL
+{
+ pfields->namebuf = xmalloc(CMDBUFFSIZE + 1);
+ pfields->module = xmalloc(CMDBUFFSIZE + 1);
+ pfields->errmsglen = CMDBUFFSIZE + 1;
+ pfields->errmsg = xmalloc(pfields->errmsglen);
+ pfields->pattern = xmalloc(CMDBUFFSIZE + 1);
+}
+
+// Free the fields used for parsing lines and populating a quickfix list.
+static void qf_free_fields(qffields_T *pfields)
+ FUNC_ATTR_NONNULL_ALL
+{
+ xfree(pfields->namebuf);
+ xfree(pfields->module);
+ xfree(pfields->errmsg);
+ xfree(pfields->pattern);
+}
+
+// Setup the state information used for parsing lines and populating a
+// quickfix list.
+static int qf_setup_state(
+ qfstate_T *pstate,
+ char_u *restrict enc,
+ const char_u *restrict efile,
+ typval_T *tv,
+ buf_T *buf,
+ linenr_T lnumfirst,
+ linenr_T lnumlast)
+ FUNC_ATTR_NONNULL_ARG(1)
+{
+ pstate->vc.vc_type = CONV_NONE;
+ if (enc != NULL && *enc != NUL) {
+ convert_setup(&pstate->vc, enc, p_enc);
+ }
+
+ if (efile != NULL
+ && (pstate->fd = os_fopen((const char *)efile, "r")) == NULL) {
+ EMSG2(_(e_openerrf), efile);
+ return FAIL;
+ }
+
+ if (tv != NULL) {
+ if (tv->v_type == VAR_STRING) {
+ pstate->p_str = tv->vval.v_string;
+ } else if (tv->v_type == VAR_LIST) {
+ pstate->p_li = tv_list_first(tv->vval.v_list);
+ }
+ pstate->tv = tv;
+ }
+ pstate->buf = buf;
+ pstate->buflnum = lnumfirst;
+ pstate->lnumlast = lnumlast;
+
+ return OK;
+}
+
+// Cleanup the state information used for parsing lines and populating a
+// quickfix list.
+static void qf_cleanup_state(qfstate_T *pstate)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (pstate->fd != NULL) {
+ fclose(pstate->fd);
+ }
+ xfree(pstate->growbuf);
+ if (pstate->vc.vc_type != CONV_NONE) {
+ convert_setup(&pstate->vc, NULL, NULL);
+ }
+}
+
// Read the errorfile "efile" into memory, line by line, building the error
// list.
// Alternative: when "efile" is NULL read errors from buffer "buf".
@@ -837,19 +1076,20 @@ static int
qf_init_ext(
qf_info_T *qi,
int qf_idx,
- char_u *efile,
+ const char_u *restrict efile,
buf_T *buf,
typval_T *tv,
- char_u *errorformat,
- int newlist, // TRUE: start a new error list
+ char_u *restrict errorformat,
+ bool newlist, // true: start a new error list
linenr_T lnumfirst, // first line number to use
linenr_T lnumlast, // last line number to use
- char_u *qf_title,
- char_u *enc
+ const char_u *restrict qf_title,
+ char_u *restrict enc
)
+ FUNC_ATTR_NONNULL_ARG(1)
{
- qfstate_T state;
- qffields_T fields;
+ qfstate_T state = { 0 };
+ qffields_T fields = { 0 };
qfline_T *old_last = NULL;
bool adding = false;
static efm_T *fmt_first = NULL;
@@ -861,21 +1101,9 @@ qf_init_ext(
// Do not used the cached buffer, it may have been wiped out.
XFREE_CLEAR(qf_last_bufname);
- memset(&state, 0, sizeof(state));
- memset(&fields, 0, sizeof(fields));
- state.vc.vc_type = CONV_NONE;
- if (enc != NULL && *enc != NUL) {
- convert_setup(&state.vc, enc, p_enc);
- }
-
- fields.namebuf = xmalloc(CMDBUFFSIZE + 1);
- fields.module = xmalloc(CMDBUFFSIZE + 1);
- fields.errmsglen = CMDBUFFSIZE + 1;
- fields.errmsg = xmalloc(fields.errmsglen);
- fields.pattern = xmalloc(CMDBUFFSIZE + 1);
-
- if (efile != NULL && (state.fd = os_fopen((char *)efile, "r")) == NULL) {
- EMSG2(_(e_openerrf), efile);
+ qf_alloc_fields(&fields);
+ if (qf_setup_state(&state, enc, efile, tv, buf,
+ lnumfirst, lnumlast) == FAIL) {
goto qf_init_end;
}
@@ -886,12 +1114,12 @@ qf_init_ext(
} else {
// Adding to existing list, use last entry.
adding = true;
- if (qi->qf_lists[qf_idx].qf_count > 0) {
+ if (!qf_list_empty(qf_get_list(qi, qf_idx) )) {
old_last = qi->qf_lists[qf_idx].qf_last;
}
}
- qf_list_T *qfl = &qi->qf_lists[qf_idx];
+ qf_list_T *qfl = qf_get_list(qi, qf_idx);
// Use the local value of 'errorformat' if it's set.
if (errorformat == p_efm && tv == NULL && buf && *buf->b_p_efm != NUL) {
@@ -918,63 +1146,21 @@ qf_init_ext(
goto error2;
}
- /*
- * got_int is reset here, because it was probably set when killing the
- * ":make" command, but we still want to read the errorfile then.
- */
- got_int = FALSE;
+ // got_int is reset here, because it was probably set when killing the
+ // ":make" command, but we still want to read the errorfile then.
+ got_int = false;
- if (tv != NULL) {
- if (tv->v_type == VAR_STRING) {
- state.p_str = tv->vval.v_string;
- } else if (tv->v_type == VAR_LIST) {
- state.p_list = tv->vval.v_list;
- state.p_li = tv_list_first(tv->vval.v_list);
- }
- state.tv = tv;
- }
- state.buf = buf;
- state.buflnum = lnumfirst;
- state.lnumlast = lnumlast;
-
- /*
- * Read the lines in the error file one by one.
- * Try to recognize one of the error formats in each line.
- */
+ // Read the lines in the error file one by one.
+ // Try to recognize one of the error formats in each line.
while (!got_int) {
- // Get the next line from a file/buffer/list/string
- status = qf_get_nextline(&state);
+ status = qf_init_process_nextline(qfl, fmt_first, &state, &fields);
if (status == QF_END_OF_INPUT) { // end of input
break;
}
-
- status = qf_parse_line(qi, qf_idx, state.linebuf, state.linelen,
- fmt_first, &fields);
if (status == QF_FAIL) {
goto error2;
}
- if (status == QF_IGNORE_LINE) {
- continue;
- }
- if (qf_add_entry(qi,
- qf_idx,
- qfl->qf_directory,
- (*fields.namebuf || qfl->qf_directory)
- ? fields.namebuf : ((qfl->qf_currfile && fields.valid)
- ? qfl->qf_currfile : (char_u *)NULL),
- fields.module,
- 0,
- fields.errmsg,
- fields.lnum,
- fields.col,
- fields.use_viscol,
- fields.pattern,
- fields.enr,
- fields.type,
- fields.valid) == FAIL) {
- goto error2;
- }
line_breakcheck();
}
if (state.fd == NULL || !ferror(state.fd)) {
@@ -997,45 +1183,34 @@ qf_init_ext(
error2:
if (!adding) {
// Error when creating a new list. Free the new list
- qf_free(qi, qi->qf_curlist);
+ qf_free(qfl);
qi->qf_listcount--;
if (qi->qf_curlist > 0) {
qi->qf_curlist--;
}
}
qf_init_end:
- if (state.fd != NULL) {
- fclose(state.fd);
- }
- xfree(fields.namebuf);
- xfree(fields.module);
- xfree(fields.errmsg);
- xfree(fields.pattern);
- xfree(state.growbuf);
-
if (qf_idx == qi->qf_curlist) {
qf_update_buffer(qi, old_last);
}
-
- if (state.vc.vc_type != CONV_NONE) {
- convert_setup(&state.vc, NULL, NULL);
- }
+ qf_cleanup_state(&state);
+ qf_free_fields(&fields);
return retval;
}
/// Set the title of the specified quickfix list. Frees the previous title.
/// Prepends ':' to the title.
-static void qf_store_title(qf_info_T *qi, int qf_idx, const char_u *title)
+static void qf_store_title(qf_list_T *qfl, const char_u *title)
FUNC_ATTR_NONNULL_ARG(1)
{
- XFREE_CLEAR(qi->qf_lists[qf_idx].qf_title);
+ XFREE_CLEAR(qfl->qf_title);
if (title != NULL) {
size_t len = STRLEN(title) + 1;
char_u *p = xmallocz(len);
- qi->qf_lists[qf_idx].qf_title = p;
+ qfl->qf_title = p;
xstrlcpy((char *)p, (const char *)title, len + 1);
}
}
@@ -1053,35 +1228,254 @@ static char_u * qf_cmdtitle(char_u *cmd)
return qftitle_str;
}
-// Prepare for adding a new quickfix list. If the current list is in the
-// middle of the stack, then all the following lists are freed and then
-// the new list is added.
-static void qf_new_list(qf_info_T *qi, char_u *qf_title)
+/// Return a pointer to the current list in the specified quickfix stack
+static qf_list_T * qf_get_curlist(qf_info_T *qi)
+{
+ return qf_get_list(qi, qi->qf_curlist);
+}
+
+/// Prepare for adding a new quickfix list. If the current list is in the
+/// middle of the stack, then all the following lists are freed and then
+/// the new list is added.
+static void qf_new_list(qf_info_T *qi, const char_u *qf_title)
{
int i;
+ qf_list_T *qfl;
// If the current entry is not the last entry, delete entries beyond
// the current entry. This makes it possible to browse in a tree-like
// way with ":grep'.
- while (qi->qf_listcount > qi->qf_curlist + 1)
- qf_free(qi, --qi->qf_listcount);
+ while (qi->qf_listcount > qi->qf_curlist + 1) {
+ qf_free(&qi->qf_lists[--qi->qf_listcount]);
+ }
- /*
- * When the stack is full, remove to oldest entry
- * Otherwise, add a new entry.
- */
+ // When the stack is full, remove to oldest entry
+ // Otherwise, add a new entry.
if (qi->qf_listcount == LISTCOUNT) {
- qf_free(qi, 0);
- for (i = 1; i < LISTCOUNT; ++i)
+ qf_free(&qi->qf_lists[0]);
+ for (i = 1; i < LISTCOUNT; i++) {
qi->qf_lists[i - 1] = qi->qf_lists[i];
+ }
qi->qf_curlist = LISTCOUNT - 1;
} else
qi->qf_curlist = qi->qf_listcount++;
- memset(&qi->qf_lists[qi->qf_curlist], 0, (size_t)(sizeof(qf_list_T)));
- qf_store_title(qi, qi->qf_curlist, qf_title);
- qi->qf_lists[qi->qf_curlist].qf_id = ++last_qf_id;
+ qfl = qf_get_curlist(qi);
+ memset(qfl, 0, sizeof(qf_list_T));
+ qf_store_title(qfl, qf_title);
+ qfl->qfl_type = qi->qfl_type;
+ qfl->qf_id = ++last_qf_id;
+}
+
+/// Parse the match for filename ('%f') pattern in regmatch.
+/// Return the matched value in "fields->namebuf".
+static int qf_parse_fmt_f(regmatch_T *rmp,
+ int midx,
+ qffields_T *fields,
+ int prefix)
+{
+ char_u c;
+
+ if (rmp->startp[midx] == NULL || rmp->endp[midx] == NULL) {
+ return QF_FAIL;
+ }
+
+ // Expand ~/file and $HOME/file to full path.
+ c = *rmp->endp[midx];
+ *rmp->endp[midx] = NUL;
+ expand_env(rmp->startp[midx], fields->namebuf, CMDBUFFSIZE);
+ *rmp->endp[midx] = c;
+
+ // For separate filename patterns (%O, %P and %Q), the specified file
+ // should exist.
+ if (vim_strchr((char_u *)"OPQ", prefix) != NULL
+ && !os_path_exists(fields->namebuf)) {
+ return QF_FAIL;
+ }
+
+ 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)
+{
+ if (rmp->startp[midx] == NULL) {
+ return QF_FAIL;
+ }
+ fields->enr = (int)atol((char *)rmp->startp[midx]);
+ return QF_OK;
+}
+
+/// Parse the match for line number (%l') pattern in regmatch.
+/// Return the matched value in "fields->lnum".
+static int qf_parse_fmt_l(regmatch_T *rmp, int midx, qffields_T *fields)
+{
+ if (rmp->startp[midx] == NULL) {
+ return QF_FAIL;
+ }
+ fields->lnum = atol((char *)rmp->startp[midx]);
+ return QF_OK;
+}
+
+/// Parse the match for column number ('%c') pattern in regmatch.
+/// Return the matched value in "fields->col".
+static int qf_parse_fmt_c(regmatch_T *rmp, int midx, qffields_T *fields)
+{
+ if (rmp->startp[midx] == NULL) {
+ return QF_FAIL;
+ }
+ fields->col = (int)atol((char *)rmp->startp[midx]);
+ return QF_OK;
+}
+
+/// Parse the match for error type ('%t') pattern in regmatch.
+/// Return the matched value in "fields->type".
+static int qf_parse_fmt_t(regmatch_T *rmp, int midx, qffields_T *fields)
+{
+ if (rmp->startp[midx] == NULL) {
+ return QF_FAIL;
+ }
+ fields->type = *rmp->startp[midx];
+ return QF_OK;
+}
+
+/// Parse the match for '%+' format pattern. The whole matching line is included
+/// in the error string. Return the matched line in "fields->errmsg".
+static void qf_parse_fmt_plus(const char_u *linebuf,
+ size_t linelen,
+ qffields_T *fields)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (linelen >= fields->errmsglen) {
+ // linelen + null terminator
+ fields->errmsg = xrealloc(fields->errmsg, linelen + 1);
+ fields->errmsglen = linelen + 1;
+ }
+ STRLCPY(fields->errmsg, linebuf, linelen + 1);
+}
+
+/// Parse the match for error message ('%m') pattern in regmatch.
+/// Return the matched value in "fields->errmsg".
+static int qf_parse_fmt_m(regmatch_T *rmp, int midx, qffields_T *fields)
+{
+ size_t len;
+
+ if (rmp->startp[midx] == NULL || rmp->endp[midx] == NULL) {
+ return QF_FAIL;
+ }
+ len = (size_t)(rmp->endp[midx] - rmp->startp[midx]);
+ if (len >= fields->errmsglen) {
+ // len + null terminator
+ fields->errmsg = xrealloc(fields->errmsg, len + 1);
+ fields->errmsglen = len + 1;
+ }
+ STRLCPY(fields->errmsg, rmp->startp[midx], len + 1);
+ return QF_OK;
+}
+
+/// Parse the match for rest of a single-line file message ('%r') pattern.
+/// Return the matched value in "tail".
+static int qf_parse_fmt_r(regmatch_T *rmp, int midx, char_u **tail)
+{
+ if (rmp->startp[midx] == NULL) {
+ return QF_FAIL;
+ }
+ *tail = rmp->startp[midx];
+ return QF_OK;
+}
+
+/// Parse the match for the pointer line ('%p') pattern in regmatch.
+/// Return the matched value in "fields->col".
+static int qf_parse_fmt_p(regmatch_T *rmp, int midx, qffields_T *fields)
+{
+ char_u *match_ptr;
+
+ if (rmp->startp[midx] == NULL || rmp->endp[midx] == NULL) {
+ return QF_FAIL;
+ }
+ fields->col = 0;
+ for (match_ptr = rmp->startp[midx]; match_ptr != rmp->endp[midx];
+ match_ptr++) {
+ fields->col++;
+ if (*match_ptr == TAB) {
+ fields->col += 7;
+ fields->col -= fields->col % 8;
+ }
+ }
+ fields->col++;
+ fields->use_viscol = true;
+ return QF_OK;
+}
+
+/// Parse the match for the virtual column number ('%v') pattern in regmatch.
+/// Return the matched value in "fields->col".
+static int qf_parse_fmt_v(regmatch_T *rmp, int midx, qffields_T *fields)
+{
+ if (rmp->startp[midx] == NULL) {
+ return QF_FAIL;
+ }
+ fields->col = (int)atol((char *)rmp->startp[midx]);
+ fields->use_viscol = true;
+ return QF_OK;
}
+/// Parse the match for the search text ('%s') pattern in regmatch.
+/// Return the matched value in "fields->pattern".
+static int qf_parse_fmt_s(regmatch_T *rmp, int midx, qffields_T *fields)
+{
+ size_t len;
+
+ if (rmp->startp[midx] == NULL || rmp->endp[midx] == NULL) {
+ return QF_FAIL;
+ }
+ len = (size_t)(rmp->endp[midx] - rmp->startp[midx]);
+ if (len > CMDBUFFSIZE - 5) {
+ len = CMDBUFFSIZE - 5;
+ }
+ STRCPY(fields->pattern, "^\\V");
+ xstrlcat((char *)fields->pattern, (char *)rmp->startp[midx], len + 4);
+ fields->pattern[len + 3] = '\\';
+ fields->pattern[len + 4] = '$';
+ fields->pattern[len + 5] = NUL;
+ return QF_OK;
+}
+
+/// Parse the match for the module ('%o') pattern in regmatch.
+/// Return the matched value in "fields->module".
+static int qf_parse_fmt_o(regmatch_T *rmp, int midx, qffields_T *fields)
+{
+ size_t len;
+ size_t dsize;
+
+ if (rmp->startp[midx] == NULL || rmp->endp[midx] == NULL) {
+ return QF_FAIL;
+ }
+ len = (size_t)(rmp->endp[midx] - rmp->startp[midx]);
+ dsize = STRLEN(fields->module) + len + 1;
+ if (dsize > CMDBUFFSIZE) {
+ dsize = CMDBUFFSIZE;
+ }
+ xstrlcat((char *)fields->module, (char *)rmp->startp[midx], dsize);
+ return QF_OK;
+}
+
+/// 'errorformat' format pattern parser functions.
+/// The '%f' and '%r' formats are parsed differently from other formats.
+/// See qf_parse_match() for details.
+static int (*qf_parse_fmt[FMT_PATTERNS])(regmatch_T *, int, qffields_T *) = {
+ NULL,
+ qf_parse_fmt_n,
+ qf_parse_fmt_l,
+ qf_parse_fmt_c,
+ qf_parse_fmt_t,
+ qf_parse_fmt_m,
+ NULL,
+ qf_parse_fmt_p,
+ qf_parse_fmt_v,
+ qf_parse_fmt_s,
+ qf_parse_fmt_o
+};
+
/// Parse the error format matches in 'regmatch' and set the values in 'fields'.
/// fmt_ptr contains the 'efm' format specifiers/prefixes that have a match.
/// Returns QF_OK if all the matches are successfully parsed. On failure,
@@ -1092,7 +1486,8 @@ static int qf_parse_match(char_u *linebuf, size_t linelen, efm_T *fmt_ptr,
{
char_u idx = fmt_ptr->prefix;
int i;
- size_t len;
+ int midx;
+ int status;
if ((idx == 'C' || idx == 'Z') && !qf_multiline) {
return QF_FAIL;
@@ -1106,118 +1501,26 @@ static int qf_parse_match(char_u *linebuf, size_t linelen, efm_T *fmt_ptr,
// Extract error message data from matched line.
// We check for an actual submatch, because "\[" and "\]" in
// the 'errorformat' may cause the wrong submatch to be used.
- if ((i = (int)fmt_ptr->addr[0]) > 0) { // %f
- if (regmatch->startp[i] == NULL || regmatch->endp[i] == NULL) {
- return QF_FAIL;
- }
-
- // Expand ~/file and $HOME/file to full path.
- char_u c = *regmatch->endp[i];
- *regmatch->endp[i] = NUL;
- expand_env(regmatch->startp[i], fields->namebuf, CMDBUFFSIZE);
- *regmatch->endp[i] = c;
-
- if (vim_strchr((char_u *)"OPQ", idx) != NULL
- && !os_path_exists(fields->namebuf)) {
- return QF_FAIL;
- }
- }
- if ((i = (int)fmt_ptr->addr[1]) > 0) { // %n
- if (regmatch->startp[i] == NULL) {
- return QF_FAIL;
- }
- fields->enr = (int)atol((char *)regmatch->startp[i]);
- }
- if ((i = (int)fmt_ptr->addr[2]) > 0) { // %l
- if (regmatch->startp[i] == NULL) {
- return QF_FAIL;
- }
- fields->lnum = atol((char *)regmatch->startp[i]);
- }
- if ((i = (int)fmt_ptr->addr[3]) > 0) { // %c
- if (regmatch->startp[i] == NULL) {
- return QF_FAIL;
- }
- fields->col = (int)atol((char *)regmatch->startp[i]);
- }
- if ((i = (int)fmt_ptr->addr[4]) > 0) { // %t
- if (regmatch->startp[i] == NULL) {
- return QF_FAIL;
- }
- fields->type = *regmatch->startp[i];
- }
- if (fmt_ptr->flags == '+' && !qf_multiscan) { // %+
- if (linelen >= fields->errmsglen) {
- // linelen + null terminator
- fields->errmsg = xrealloc(fields->errmsg, linelen + 1);
- fields->errmsglen = linelen + 1;
- }
- STRLCPY(fields->errmsg, linebuf, linelen + 1);
- } else if ((i = (int)fmt_ptr->addr[5]) > 0) { // %m
- if (regmatch->startp[i] == NULL || regmatch->endp[i] == NULL) {
- return QF_FAIL;
- }
- len = (size_t)(regmatch->endp[i] - regmatch->startp[i]);
- if (len >= fields->errmsglen) {
- // len + null terminator
- fields->errmsg = xrealloc(fields->errmsg, len + 1);
- fields->errmsglen = len + 1;
- }
- STRLCPY(fields->errmsg, regmatch->startp[i], len + 1);
- }
- if ((i = (int)fmt_ptr->addr[6]) > 0) { // %r
- if (regmatch->startp[i] == NULL) {
- return QF_FAIL;
- }
- *tail = regmatch->startp[i];
- }
- if ((i = (int)fmt_ptr->addr[7]) > 0) { // %p
- if (regmatch->startp[i] == NULL || regmatch->endp[i] == NULL) {
- return QF_FAIL;
- }
- fields->col = 0;
- char_u *match_ptr;
- for (match_ptr = regmatch->startp[i]; match_ptr != regmatch->endp[i];
- match_ptr++) {
- fields->col++;
- if (*match_ptr == TAB) {
- fields->col += 7;
- fields->col -= fields->col % 8;
+ for (i = 0; i < FMT_PATTERNS; i++) {
+ status = QF_OK;
+ midx = (int)fmt_ptr->addr[i];
+ if (i == 0 && midx > 0) { // %f
+ status = qf_parse_fmt_f(regmatch, midx, fields, idx);
+ } else if (i == 5) {
+ if (fmt_ptr->flags == '+' && !qf_multiscan) { // %+
+ qf_parse_fmt_plus(linebuf, linelen, fields);
+ } else if (midx > 0) { // %m
+ status = qf_parse_fmt_m(regmatch, midx, fields);
}
+ } else if (i == 6 && midx > 0) { // %r
+ status = qf_parse_fmt_r(regmatch, midx, tail);
+ } else if (midx > 0) { // others
+ status = (qf_parse_fmt[i])(regmatch, midx, fields);
}
- fields->col++;
- fields->use_viscol = true;
- }
- if ((i = (int)fmt_ptr->addr[8]) > 0) { // %v
- if (regmatch->startp[i] == NULL) {
- return QF_FAIL;
- }
- fields->col = (int)atol((char *)regmatch->startp[i]);
- fields->use_viscol = true;
- }
- if ((i = (int)fmt_ptr->addr[9]) > 0) { // %s
- if (regmatch->startp[i] == NULL || regmatch->endp[i] == NULL) {
- return QF_FAIL;
- }
- len = (size_t)(regmatch->endp[i] - regmatch->startp[i]);
- if (len > CMDBUFFSIZE - 5) {
- len = CMDBUFFSIZE - 5;
- }
- STRCPY(fields->pattern, "^\\V");
- STRNCAT(fields->pattern, regmatch->startp[i], len);
- fields->pattern[len + 3] = '\\';
- fields->pattern[len + 4] = '$';
- fields->pattern[len + 5] = NUL;
- }
- if ((i = (int)fmt_ptr->addr[10]) > 0) { // %o
- if (regmatch->startp[i] == NULL || regmatch->endp[i] == NULL) {
- return QF_FAIL;
- }
- len = (size_t)(regmatch->endp[i] - regmatch->startp[i]);
- if (len > CMDBUFFSIZE) {
- len = CMDBUFFSIZE;
+
+ if (status != QF_OK) {
+ return status;
}
- STRNCAT(fields->module, regmatch->startp[i], len);
}
return QF_OK;
@@ -1329,8 +1632,7 @@ static int qf_parse_line_nomatch(char_u *linebuf, size_t linelen,
}
/// Parse multi-line error format prefixes (%C and %Z)
-static int qf_parse_multiline_pfx(qf_info_T *qi, int qf_idx, int idx,
- qf_list_T *qfl, qffields_T *fields)
+static int qf_parse_multiline_pfx(int idx, qf_list_T *qfl, qffields_T *fields)
{
if (!qfl->qf_multiignore) {
qfline_T *qfprev = qfl->qf_last;
@@ -1361,7 +1663,7 @@ static int qf_parse_multiline_pfx(qf_info_T *qi, int qf_idx, int idx,
}
qfprev->qf_viscol = fields->use_viscol;
if (!qfprev->qf_fnum) {
- qfprev->qf_fnum = qf_get_fnum(qi, qf_idx, qfl->qf_directory,
+ qfprev->qf_fnum = qf_get_fnum(qfl, qfl->qf_directory,
*fields->namebuf || qfl->qf_directory
? fields->namebuf
: qfl->qf_currfile && fields->valid
@@ -1376,7 +1678,18 @@ static int qf_parse_multiline_pfx(qf_info_T *qi, int qf_idx, int idx,
return QF_IGNORE_LINE;
}
-/// Free a location list.
+/// Queue location list stack delete request.
+static void locstack_queue_delreq(qf_info_T *qi)
+{
+ qf_delq_T *q;
+
+ q = xmalloc(sizeof(qf_delq_T));
+ q->qi = qi;
+ q->next = qf_delq_head;
+ qf_delq_head = q;
+}
+
+/// Free a location list stack
static void ll_free_all(qf_info_T **pqi)
{
int i;
@@ -1385,14 +1698,21 @@ static void ll_free_all(qf_info_T **pqi)
qi = *pqi;
if (qi == NULL)
return;
- *pqi = NULL; /* Remove reference to this list */
+ *pqi = NULL; // Remove reference to this list
qi->qf_refcount--;
if (qi->qf_refcount < 1) {
- /* No references to this location list */
- for (i = 0; i < qi->qf_listcount; ++i)
- qf_free(qi, i);
- xfree(qi);
+ // No references to this location list.
+ // If the location list is still in use, then queue the delete request
+ // to be processed later.
+ if (quickfix_busy > 0) {
+ locstack_queue_delreq(qi);
+ } else {
+ for (i = 0; i < qi->qf_listcount; i++) {
+ qf_free(qf_get_list(qi, i));
+ }
+ xfree(qi);
+ }
}
}
@@ -1403,19 +1723,64 @@ void qf_free_all(win_T *wp)
qf_info_T *qi = &ql_info;
if (wp != NULL) {
- /* location list */
+ // location list
ll_free_all(&wp->w_llist);
ll_free_all(&wp->w_llist_ref);
- } else
- /* quickfix list */
- for (i = 0; i < qi->qf_listcount; ++i)
- qf_free(qi, i);
+ } else {
+ // quickfix list
+ for (i = 0; i < qi->qf_listcount; i++) {
+ qf_free(qf_get_list(qi, i));
+ }
+ }
}
+/// Delay freeing of location list stacks when the quickfix code is running.
+/// Used to avoid problems with autocmds freeing location list stacks when the
+/// quickfix code is still referencing the stack.
+/// Must always call decr_quickfix_busy() exactly once after this.
+static void incr_quickfix_busy(void)
+{
+ quickfix_busy++;
+}
+
+/// Safe to free location list stacks. Process any delayed delete requests.
+static void decr_quickfix_busy(void)
+{
+ quickfix_busy--;
+ if (quickfix_busy == 0) {
+ // No longer referencing the location lists. Process all the pending
+ // delete requests.
+ while (qf_delq_head != NULL) {
+ qf_delq_T *q = qf_delq_head;
+
+ qf_delq_head = q->next;
+ ll_free_all(&q->qi);
+ xfree(q);
+ }
+ }
+#ifdef ABORT_ON_INTERNAL_ERROR
+ if (quickfix_busy < 0) {
+ EMSG("quickfix_busy has become negative");
+ abort();
+ }
+#endif
+}
+
+#if defined(EXITFREE)
+void check_quickfix_busy(void)
+{
+ if (quickfix_busy != 0) {
+ EMSGN("quickfix_busy not zero on exit: %ld", (long)quickfix_busy);
+# ifdef ABORT_ON_INTERNAL_ERROR
+ abort();
+# endif
+ }
+}
+#endif
+
/// Add an entry to the end of the list of errors.
///
-/// @param qi quickfix list
-/// @param qf_idx list index
+/// @param qfl quickfix list entry
/// @param dir optional directory name
/// @param fname file name or NULL
/// @param module module name or NULL
@@ -1429,8 +1794,8 @@ void qf_free_all(win_T *wp)
/// @param type type character
/// @param valid valid entry
///
-/// @returns OK or FAIL.
-static int qf_add_entry(qf_info_T *qi, int qf_idx, char_u *dir, char_u *fname,
+/// @returns QF_OK or QF_FAIL.
+static int qf_add_entry(qf_list_T *qfl, char_u *dir, char_u *fname,
char_u *module, int bufnum, char_u *mesg, long lnum,
int col, char_u vis_col, char_u *pattern, int nr,
char_u type, char_u valid)
@@ -1444,10 +1809,10 @@ static int qf_add_entry(qf_info_T *qi, int qf_idx, char_u *dir, char_u *fname,
qfp->qf_fnum = bufnum;
if (buf != NULL) {
buf->b_has_qf_entry |=
- IS_QF_STACK(qi) ? BUF_HAS_QF_ENTRY : BUF_HAS_LL_ENTRY;
+ IS_QF_LIST(qfl) ? BUF_HAS_QF_ENTRY : BUF_HAS_LL_ENTRY;
}
} else {
- qfp->qf_fnum = qf_get_fnum(qi, qf_idx, dir, fname);
+ qfp->qf_fnum = qf_get_fnum(qfl, dir, fname);
}
qfp->qf_text = vim_strsave(mesg);
qfp->qf_lnum = lnum;
@@ -1470,12 +1835,12 @@ static int qf_add_entry(qf_info_T *qi, int qf_idx, char_u *dir, char_u *fname,
qfp->qf_type = (char_u)type;
qfp->qf_valid = valid;
- lastp = &qi->qf_lists[qf_idx].qf_last;
- if (qi->qf_lists[qf_idx].qf_count == 0) {
+ lastp = &qfl->qf_last;
+ if (qf_list_empty(qfl)) {
// first element in the list
- qi->qf_lists[qf_idx].qf_start = qfp;
- qi->qf_lists[qf_idx].qf_ptr = qfp;
- qi->qf_lists[qf_idx].qf_index = 0;
+ qfl->qf_start = qfp;
+ qfl->qf_ptr = qfp;
+ qfl->qf_index = 0;
qfp->qf_prev = NULL;
} else {
assert(*lastp);
@@ -1485,164 +1850,211 @@ static int qf_add_entry(qf_info_T *qi, int qf_idx, char_u *dir, char_u *fname,
qfp->qf_next = NULL;
qfp->qf_cleared = false;
*lastp = qfp;
- qi->qf_lists[qf_idx].qf_count++;
- if (qi->qf_lists[qf_idx].qf_index == 0 && qfp->qf_valid) {
+ qfl->qf_count++;
+ if (qfl->qf_index == 0 && qfp->qf_valid) {
// first valid entry
- qi->qf_lists[qf_idx].qf_index = qi->qf_lists[qf_idx].qf_count;
- qi->qf_lists[qf_idx].qf_ptr = qfp;
+ qfl->qf_index = qfl->qf_count;
+ qfl->qf_ptr = qfp;
}
- return OK;
+ return QF_OK;
}
-/*
- * Allocate a new location list
- */
-static qf_info_T *ll_new_list(void)
+/// Allocate a new quickfix/location list stack
+static qf_info_T *qf_alloc_stack(qfltype_T qfltype)
+ FUNC_ATTR_NONNULL_RET
{
qf_info_T *qi = xcalloc(1, sizeof(qf_info_T));
qi->qf_refcount++;
+ qi->qfl_type = qfltype;
return qi;
}
-/*
- * Return the location list for window 'wp'.
- * If not present, allocate a location list
- */
+/// Return the location list stack for window 'wp'.
+/// If not present, allocate a location list stack
static qf_info_T *ll_get_or_alloc_list(win_T *wp)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET
{
- if (IS_LL_WINDOW(wp))
- /* For a location list window, use the referenced location list */
+ if (IS_LL_WINDOW(wp)) {
+ // For a location list window, use the referenced location list
return wp->w_llist_ref;
+ }
- /*
- * For a non-location list window, w_llist_ref should not point to a
- * location list.
- */
+ // For a non-location list window, w_llist_ref should not point to a
+ // location list.
ll_free_all(&wp->w_llist_ref);
- if (wp->w_llist == NULL)
- wp->w_llist = ll_new_list(); /* new location list */
+ if (wp->w_llist == NULL) {
+ wp->w_llist = qf_alloc_stack(QFLT_LOCATION); // new location list
+ }
return wp->w_llist;
}
-/*
- * Copy the location list from window "from" to window "to".
- */
-void copy_loclist(win_T *from, win_T *to)
+/// Get the quickfix/location list stack to use for the specified Ex command.
+/// For a location list command, returns the stack for the current window. If
+/// the location list is not found, then returns NULL and prints an error
+/// message if 'print_emsg' is TRUE.
+static qf_info_T * qf_cmd_get_stack(exarg_T *eap, int print_emsg)
+{
+ qf_info_T *qi = &ql_info;
+
+ if (is_loclist_cmd(eap->cmdidx)) {
+ qi = GET_LOC_LIST(curwin);
+ if (qi == NULL) {
+ if (print_emsg) {
+ EMSG(_(e_loclist));
+ }
+ return NULL;
+ }
+ }
+
+ return qi;
+}
+
+/// Get the quickfix/location list stack to use for the specified Ex command.
+/// For a location list command, returns the stack for the current window.
+/// If the location list is not present, then allocates a new one.
+/// For a location list command, sets 'pwinp' to curwin.
+static qf_info_T *qf_cmd_get_or_alloc_stack(const exarg_T *eap, win_T **pwinp)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET
+{
+ qf_info_T *qi = &ql_info;
+
+ if (is_loclist_cmd(eap->cmdidx)) {
+ qi = ll_get_or_alloc_list(curwin);
+ *pwinp = curwin;
+ }
+
+ return qi;
+}
+
+/// Copy location list entries from 'from_qfl' to 'to_qfl'.
+static int copy_loclist_entries(const qf_list_T *from_qfl, qf_list_T *to_qfl)
+ FUNC_ATTR_NONNULL_ALL
{
- qf_info_T *qi;
- int idx;
int i;
+ qfline_T *from_qfp;
+
+ // copy all the location entries in this list
+ FOR_ALL_QFL_ITEMS(from_qfl, from_qfp, i) {
+ if (qf_add_entry(to_qfl,
+ NULL,
+ NULL,
+ from_qfp->qf_module,
+ 0,
+ from_qfp->qf_text,
+ from_qfp->qf_lnum,
+ from_qfp->qf_col,
+ from_qfp->qf_viscol,
+ from_qfp->qf_pattern,
+ from_qfp->qf_nr,
+ 0,
+ from_qfp->qf_valid) == QF_FAIL) {
+ return FAIL;
+ }
+
+ // qf_add_entry() will not set the qf_num field, as the
+ // directory and file names are not supplied. So the qf_fnum
+ // field is copied here.
+ qfline_T *const prevp = to_qfl->qf_last;
+ prevp->qf_fnum = from_qfp->qf_fnum; // file number
+ prevp->qf_type = from_qfp->qf_type; // error type
+ if (from_qfl->qf_ptr == from_qfp) {
+ to_qfl->qf_ptr = prevp; // current location
+ }
+ }
+
+ return OK;
+}
+
+/// Copy the specified location list 'from_qfl' to 'to_qfl'.
+static int copy_loclist(const qf_list_T *from_qfl, qf_list_T *to_qfl)
+ FUNC_ATTR_NONNULL_ALL
+{
+ // 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_count = 0;
+ to_qfl->qf_index = 0;
+ to_qfl->qf_start = NULL;
+ to_qfl->qf_last = NULL;
+ to_qfl->qf_ptr = NULL;
+ if (from_qfl->qf_title != NULL) {
+ to_qfl->qf_title = vim_strsave(from_qfl->qf_title);
+ } else {
+ to_qfl->qf_title = NULL;
+ }
+ if (from_qfl->qf_ctx != NULL) {
+ to_qfl->qf_ctx = xcalloc(1, sizeof(*to_qfl->qf_ctx));
+ tv_copy(from_qfl->qf_ctx, to_qfl->qf_ctx);
+ } else {
+ to_qfl->qf_ctx = NULL;
+ }
+
+ if (from_qfl->qf_count) {
+ if (copy_loclist_entries(from_qfl, to_qfl) == FAIL) {
+ return FAIL;
+ }
+ }
+
+ to_qfl->qf_index = from_qfl->qf_index; // current index in the list
- /*
- * When copying from a location list window, copy the referenced
- * location list. For other windows, copy the location list for
- * that window.
- */
- if (IS_LL_WINDOW(from))
+ // Assign a new ID for the location list
+ to_qfl->qf_id = ++last_qf_id;
+ to_qfl->qf_changedtick = 0L;
+
+ // When no valid entries are present in the list, qf_ptr points to
+ // the first item in the list
+ if (to_qfl->qf_nonevalid) {
+ to_qfl->qf_ptr = to_qfl->qf_start;
+ to_qfl->qf_index = 1;
+ }
+
+ return OK;
+}
+
+// Copy the location list stack 'from' window to 'to' window.
+void copy_loclist_stack(win_T *from, win_T *to)
+ FUNC_ATTR_NONNULL_ALL
+{
+ qf_info_T *qi;
+
+ // When copying from a location list window, copy the referenced
+ // location list. For other windows, copy the location list for
+ // that window.
+ if (IS_LL_WINDOW(from)) {
qi = from->w_llist_ref;
- else
+ } else {
qi = from->w_llist;
+ }
- if (qi == NULL) /* no location list to copy */
+ if (qi == NULL) { // no location list to copy
return;
+ }
- /* allocate a new location list */
- to->w_llist = ll_new_list();
+ // allocate a new location list
+ to->w_llist = qf_alloc_stack(QFLT_LOCATION);
to->w_llist->qf_listcount = qi->qf_listcount;
- /* Copy the location lists one at a time */
- for (idx = 0; idx < qi->qf_listcount; idx++) {
- qf_list_T *from_qfl;
- qf_list_T *to_qfl;
-
+ // Copy the location lists one at a time
+ for (int idx = 0; idx < qi->qf_listcount; idx++) {
to->w_llist->qf_curlist = idx;
- from_qfl = &qi->qf_lists[idx];
- to_qfl = &to->w_llist->qf_lists[idx];
-
- /* Some of the fields are populated by qf_add_entry() */
- to_qfl->qf_nonevalid = from_qfl->qf_nonevalid;
- to_qfl->qf_count = 0;
- to_qfl->qf_index = 0;
- to_qfl->qf_start = NULL;
- to_qfl->qf_last = NULL;
- to_qfl->qf_ptr = NULL;
- if (from_qfl->qf_title != NULL)
- to_qfl->qf_title = vim_strsave(from_qfl->qf_title);
- else
- to_qfl->qf_title = NULL;
-
- if (from_qfl->qf_ctx != NULL) {
- to_qfl->qf_ctx = xcalloc(1, sizeof(typval_T));
- tv_copy(from_qfl->qf_ctx, to_qfl->qf_ctx);
- } else {
- to_qfl->qf_ctx = NULL;
- }
-
- if (from_qfl->qf_count) {
- qfline_T *from_qfp;
- qfline_T *prevp;
-
- // copy all the location entries in this list
- for (i = 0, from_qfp = from_qfl->qf_start;
- i < from_qfl->qf_count && from_qfp != NULL;
- i++, from_qfp = from_qfp->qf_next) {
- if (qf_add_entry(to->w_llist,
- to->w_llist->qf_curlist,
- NULL,
- NULL,
- from_qfp->qf_module,
- 0,
- from_qfp->qf_text,
- from_qfp->qf_lnum,
- from_qfp->qf_col,
- from_qfp->qf_viscol,
- from_qfp->qf_pattern,
- from_qfp->qf_nr,
- 0,
- from_qfp->qf_valid) == FAIL) {
- qf_free_all(to);
- return;
- }
- /*
- * qf_add_entry() will not set the qf_num field, as the
- * directory and file names are not supplied. So the qf_fnum
- * field is copied here.
- */
- prevp = to->w_llist->qf_lists[to->w_llist->qf_curlist].qf_last;
- prevp->qf_fnum = from_qfp->qf_fnum; // file number
- prevp->qf_type = from_qfp->qf_type; // error type
- if (from_qfl->qf_ptr == from_qfp) {
- to_qfl->qf_ptr = prevp; // current location
- }
- }
- }
-
- to_qfl->qf_index = from_qfl->qf_index; /* current index in the list */
-
- // Assign a new ID for the location list
- to_qfl->qf_id = ++last_qf_id;
- to_qfl->qf_changedtick = 0L;
-
- /* When no valid entries are present in the list, qf_ptr points to
- * the first item in the list */
- if (to_qfl->qf_nonevalid) {
- to_qfl->qf_ptr = to_qfl->qf_start;
- to_qfl->qf_index = 1;
+ if (copy_loclist(qf_get_list(qi, idx),
+ qf_get_list(to->w_llist, idx)) == FAIL) {
+ qf_free_all(to);
+ return;
}
}
- to->w_llist->qf_curlist = qi->qf_curlist; /* current list */
+ to->w_llist->qf_curlist = qi->qf_curlist; // current list
}
-// Get buffer number for file "directory/fname".
-// Also sets the b_has_qf_entry flag.
-static int qf_get_fnum(qf_info_T *qi, int qf_idx, char_u *directory,
- char_u *fname)
+/// Get buffer number for file "directory/fname".
+/// Also sets the b_has_qf_entry flag.
+static int qf_get_fnum(qf_list_T *qfl, char_u *directory, char_u *fname )
{
char_u *ptr = NULL;
char_u *bufname;
@@ -1665,7 +2077,7 @@ static int qf_get_fnum(qf_info_T *qi, int qf_idx, char_u *directory,
// directory change.
if (!os_path_exists(ptr)) {
xfree(ptr);
- directory = qf_guess_filepath(qi, qf_idx, fname);
+ directory = qf_guess_filepath(qfl, fname);
if (directory) {
ptr = (char_u *)concat_fnames((char *)directory, (char *)fname, true);
} else {
@@ -1693,7 +2105,7 @@ static int qf_get_fnum(qf_info_T *qi, int qf_idx, char_u *directory,
return 0;
}
buf->b_has_qf_entry =
- IS_QF_STACK(qi) ? BUF_HAS_QF_ENTRY : BUF_HAS_LL_ENTRY;
+ IS_QF_LIST(qfl) ? BUF_HAS_QF_ENTRY : BUF_HAS_LL_ENTRY;
return buf->b_fnum;
}
@@ -1704,22 +2116,21 @@ static char_u *qf_push_dir(char_u *dirbuf, struct dir_stack_T **stackptr,
{
struct dir_stack_T *ds_ptr;
- /* allocate new stack element and hook it in */
+ // allocate new stack element and hook it in
struct dir_stack_T *ds_new = xmalloc(sizeof(struct dir_stack_T));
ds_new->next = *stackptr;
*stackptr = ds_new;
- /* store directory on the stack */
+ // store directory on the stack
if (vim_isAbsName(dirbuf)
|| (*stackptr)->next == NULL
- || (*stackptr && is_file_stack))
+ || (*stackptr && is_file_stack)) {
(*stackptr)->dirname = vim_strsave(dirbuf);
- else {
- /* Okay we don't have an absolute path.
- * dirbuf must be a subdir of one of the directories on the stack.
- * Let's search...
- */
+ } else {
+ // Okay we don't have an absolute path.
+ // dirbuf must be a subdir of one of the directories on the stack.
+ // Let's search...
ds_new = (*stackptr)->next;
(*stackptr)->dirname = NULL;
while (ds_new) {
@@ -1732,7 +2143,7 @@ static char_u *qf_push_dir(char_u *dirbuf, struct dir_stack_T **stackptr,
ds_new = ds_new->next;
}
- /* clean up all dirs we already left */
+ // clean up all dirs we already left
while ((*stackptr)->next != ds_new) {
ds_ptr = (*stackptr)->next;
(*stackptr)->next = (*stackptr)->next->next;
@@ -1740,7 +2151,7 @@ static char_u *qf_push_dir(char_u *dirbuf, struct dir_stack_T **stackptr,
xfree(ds_ptr);
}
- /* Nothing found -> it must be on top level */
+ // Nothing found -> it must be on top level
if (ds_new == NULL) {
xfree((*stackptr)->dirname);
(*stackptr)->dirname = vim_strsave(dirbuf);
@@ -1758,18 +2169,16 @@ static char_u *qf_push_dir(char_u *dirbuf, struct dir_stack_T **stackptr,
}
-/*
- * pop dirbuf from the directory stack and return previous directory or NULL if
- * stack is empty
- */
+// pop dirbuf from the directory stack and return previous directory or NULL if
+// stack is empty
static char_u *qf_pop_dir(struct dir_stack_T **stackptr)
{
struct dir_stack_T *ds_ptr;
- /* TODO: Should we check if dirbuf is the directory on top of the stack?
- * What to do if it isn't? */
+ // TODO(vim): Should we check if dirbuf is the directory on top of the stack?
+ // What to do if it isn't?
- /* pop top element and free it */
+ // pop top element and free it
if (*stackptr != NULL) {
ds_ptr = *stackptr;
*stackptr = (*stackptr)->next;
@@ -1777,13 +2186,11 @@ static char_u *qf_pop_dir(struct dir_stack_T **stackptr)
xfree(ds_ptr);
}
- /* return NEW top element as current dir or NULL if stack is empty*/
+ // return NEW top element as current dir or NULL if stack is empty
return *stackptr ? (*stackptr)->dirname : NULL;
}
-/*
- * clean up directory stack
- */
+// clean up directory stack
static void qf_clean_dir_stack(struct dir_stack_T **stackptr)
{
struct dir_stack_T *ds_ptr;
@@ -1795,32 +2202,29 @@ static void qf_clean_dir_stack(struct dir_stack_T **stackptr)
}
}
-/*
- * Check in which directory of the directory stack the given file can be
- * found.
- * Returns a pointer to the directory name or NULL if not found.
- * Cleans up intermediate directory entries.
- *
- * TODO: How to solve the following problem?
- * If we have the this directory tree:
- * ./
- * ./aa
- * ./aa/bb
- * ./bb
- * ./bb/x.c
- * and make says:
- * making all in aa
- * making all in bb
- * x.c:9: Error
- * Then qf_push_dir thinks we are in ./aa/bb, but we are in ./bb.
- * qf_guess_filepath will return NULL.
- */
-static char_u *qf_guess_filepath(qf_info_T *qi, int qf_idx, char_u *filename)
+/// Check in which directory of the directory stack the given file can be
+/// found.
+/// Returns a pointer to the directory name or NULL if not found.
+/// Cleans up intermediate directory entries.
+///
+/// TODO(vim): How to solve the following problem?
+/// If we have this directory tree:
+/// ./
+/// ./aa
+/// ./aa/bb
+/// ./bb
+/// ./bb/x.c
+/// and make says:
+/// making all in aa
+/// making all in bb
+/// x.c:9: Error
+/// Then qf_push_dir thinks we are in ./aa/bb, but we are in ./bb.
+/// qf_guess_filepath will return NULL.
+static char_u *qf_guess_filepath(qf_list_T *qfl, char_u *filename)
{
struct dir_stack_T *ds_ptr;
struct dir_stack_T *ds_tmp;
char_u *fullname;
- qf_list_T *qfl = &qi->qf_lists[qf_idx];
// no dirs on the stack - there's nothing we can do
if (qfl->qf_dir_stack == NULL) {
@@ -1876,24 +2280,21 @@ static bool qflist_valid(win_T *wp, unsigned int qf_id)
/// When loading a file from the quickfix, the autocommands may modify it.
/// This may invalidate the current quickfix entry. This function checks
-/// whether a entry is still present in the quickfix.
+/// whether an entry is still present in the quickfix list.
/// Similar to location list.
-static bool is_qf_entry_present(qf_info_T *qi, qfline_T *qf_ptr)
+static bool is_qf_entry_present(qf_list_T *qfl, qfline_T *qf_ptr)
{
- qf_list_T *qfl;
qfline_T *qfp;
int i;
- qfl = &qi->qf_lists[qi->qf_curlist];
-
// Search for the entry in the current list
- for (i = 0, qfp = qfl->qf_start; i < qfl->qf_count; i++, qfp = qfp->qf_next) {
- if (qfp == NULL || qfp == qf_ptr) {
+ FOR_ALL_QFL_ITEMS(qfl, qfp, i) {
+ if (qfp == qf_ptr) {
break;
}
}
- if (i == qfl->qf_count) { // Entry is not found
+ if (i > qfl->qf_count) { // Entry is not found
return false;
}
@@ -1902,20 +2303,19 @@ static bool is_qf_entry_present(qf_info_T *qi, qfline_T *qf_ptr)
/// Get the next valid entry in the current quickfix/location list. The search
/// starts from the current entry. Returns NULL on failure.
-static qfline_T *get_next_valid_entry(qf_info_T *qi, qfline_T *qf_ptr,
+static qfline_T *get_next_valid_entry(qf_list_T *qfl, qfline_T *qf_ptr,
int *qf_index, int dir)
{
int idx = *qf_index;
int old_qf_fnum = qf_ptr->qf_fnum;
do {
- if (idx == qi->qf_lists[qi->qf_curlist].qf_count
- || qf_ptr->qf_next == NULL) {
+ if (idx == qfl->qf_count || qf_ptr->qf_next == NULL) {
return NULL;
}
idx++;
qf_ptr = qf_ptr->qf_next;
- } while ((!qi->qf_lists[qi->qf_curlist].qf_nonevalid && !qf_ptr->qf_valid)
+ } while ((!qfl->qf_nonevalid && !qf_ptr->qf_valid)
|| (dir == FORWARD_FILE && qf_ptr->qf_fnum == old_qf_fnum));
*qf_index = idx;
@@ -1924,7 +2324,7 @@ static qfline_T *get_next_valid_entry(qf_info_T *qi, qfline_T *qf_ptr,
/// Get the previous valid entry in the current quickfix/location list. The
/// search starts from the current entry. Returns NULL on failure.
-static qfline_T *get_prev_valid_entry(qf_info_T *qi, qfline_T *qf_ptr,
+static qfline_T *get_prev_valid_entry(qf_list_T *qfl, qfline_T *qf_ptr,
int *qf_index, int dir)
{
int idx = *qf_index;
@@ -1936,7 +2336,7 @@ static qfline_T *get_prev_valid_entry(qf_info_T *qi, qfline_T *qf_ptr,
}
idx--;
qf_ptr = qf_ptr->qf_prev;
- } while ((!qi->qf_lists[qi->qf_curlist].qf_nonevalid && !qf_ptr->qf_valid)
+ } while ((!qfl->qf_nonevalid && !qf_ptr->qf_valid)
|| (dir == BACKWARD_FILE && qf_ptr->qf_fnum == old_qf_fnum));
*qf_index = idx;
@@ -1947,12 +2347,11 @@ static qfline_T *get_prev_valid_entry(qf_info_T *qi, qfline_T *qf_ptr,
/// the quickfix list.
/// dir == FORWARD or FORWARD_FILE: next valid entry
/// dir == BACKWARD or BACKWARD_FILE: previous valid entry
-static qfline_T *get_nth_valid_entry(qf_info_T *qi, int errornr,
+static qfline_T *get_nth_valid_entry(qf_list_T *qfl, int errornr,
qfline_T *qf_ptr, int *qf_index, int dir)
{
qfline_T *prev_qf_ptr;
int prev_index;
- static char_u *e_no_more_items = (char_u *)N_("E553: No more items");
char_u *err = e_no_more_items;
while (errornr--) {
@@ -1960,9 +2359,9 @@ static qfline_T *get_nth_valid_entry(qf_info_T *qi, int errornr,
prev_index = *qf_index;
if (dir == FORWARD || dir == FORWARD_FILE) {
- qf_ptr = get_next_valid_entry(qi, qf_ptr, qf_index, dir);
+ qf_ptr = get_next_valid_entry(qfl, qf_ptr, qf_index, dir);
} else {
- qf_ptr = get_prev_valid_entry(qi, qf_ptr, qf_index, dir);
+ qf_ptr = get_prev_valid_entry(qfl, qf_ptr, qf_index, dir);
}
if (qf_ptr == NULL) {
@@ -1982,7 +2381,7 @@ static qfline_T *get_nth_valid_entry(qf_info_T *qi, int errornr,
}
/// Get n'th (errornr) quickfix entry
-static qfline_T *get_nth_entry(qf_info_T *qi, int errornr, qfline_T *qf_ptr,
+static qfline_T *get_nth_entry(qf_list_T *qfl, int errornr, qfline_T *qf_ptr,
int *cur_qfidx)
{
int qf_idx = *cur_qfidx;
@@ -1995,7 +2394,7 @@ static qfline_T *get_nth_entry(qf_info_T *qi, int errornr, qfline_T *qf_ptr,
// New error number is greater than the current error number
while (errornr > qf_idx
- && qf_idx < qi->qf_lists[qi->qf_curlist].qf_count
+ && qf_idx < qfl->qf_count
&& qf_ptr->qf_next != NULL) {
qf_idx++;
qf_ptr = qf_ptr->qf_next;
@@ -2005,6 +2404,25 @@ static qfline_T *get_nth_entry(qf_info_T *qi, int errornr, qfline_T *qf_ptr,
return qf_ptr;
}
+// Find a window displaying a Vim help file.
+static win_T *qf_find_help_win(void)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
+ if (bt_help(wp->w_buffer)) {
+ return wp;
+ }
+ }
+ return NULL;
+}
+
+/// Set the location list for the specified window to 'qi'.
+static void win_set_loclist(win_T *wp, qf_info_T *qi)
+{
+ wp->w_llist = qi;
+ qi->qf_refcount++;
+}
+
/// Find a help window or open one.
static int jump_to_help_window(qf_info_T *qi, int *opened_window)
{
@@ -2013,12 +2431,7 @@ static int jump_to_help_window(qf_info_T *qi, int *opened_window)
if (cmdmod.tab != 0) {
wp = NULL;
} else {
- FOR_ALL_WINDOWS_IN_TAB(wp2, curtab) {
- if (bt_help(wp2->w_buffer)) {
- wp = wp2;
- break;
- }
- }
+ wp = qf_find_help_win();
}
if (wp != NULL && wp->w_buffer->b_nwindows > 0) {
@@ -2049,8 +2462,7 @@ static int jump_to_help_window(qf_info_T *qi, int *opened_window)
if (IS_LL_STACK(qi)) { // not a quickfix list
// The new window should use the supplied location list
- curwin->w_llist = qi;
- qi->qf_refcount++;
+ win_set_loclist(curwin, qi);
}
}
@@ -2061,146 +2473,197 @@ static int jump_to_help_window(qf_info_T *qi, int *opened_window)
return OK;
}
-/// Find a suitable window for opening a file (qf_fnum) and jump to it.
-/// If the file is already opened in a window, jump to it.
-static int qf_jump_to_usable_window(int qf_fnum, int *opened_window)
+// Find a non-quickfix window using the given location list.
+// Returns NULL if a matching window is not found.
+static win_T *qf_find_win_with_loclist(const qf_info_T *ll)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
- win_T *usable_win_ptr = NULL;
- int usable_win;
- int flags;
- win_T *win;
- win_T *altwin;
+ FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
+ if (wp->w_llist == ll && !bt_quickfix(wp->w_buffer)) {
+ return wp;
+ }
+ }
+ return NULL;
+}
- usable_win = 0;
+// Find a window containing a normal buffer
+static win_T *qf_find_win_with_normal_buf(void)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
+ if (bt_normal(wp->w_buffer)) {
+ return wp;
+ }
+ }
+ return NULL;
+}
- qf_info_T *ll_ref = curwin->w_llist_ref;
+// Go to a window in any tabpage containing the specified file. Returns true
+// if successfully jumped to the window. Otherwise returns FALSE.
+static bool qf_goto_tabwin_with_file(int fnum)
+{
+ FOR_ALL_TAB_WINDOWS(tp, wp) {
+ if (wp->w_buffer->b_fnum == fnum) {
+ goto_tabpage_win(tp, wp);
+ return true;
+ }
+ }
+ return false;
+}
+
+// Create a new window to show a file above the quickfix window. Called when
+// only the quickfix window is present.
+static int qf_open_new_file_win(qf_info_T *ll_ref)
+{
+ int flags = WSP_ABOVE;
if (ll_ref != NULL) {
- // Find a window using the same location list that is not a
- // quickfix window.
- FOR_ALL_WINDOWS_IN_TAB(usable_win_ptr2, curtab) {
- if (usable_win_ptr2->w_llist == ll_ref
- && !bt_quickfix(usable_win_ptr2->w_buffer)) {
- usable_win_ptr = usable_win_ptr2;
- usable_win = 1;
+ flags |= WSP_NEWLOC;
+ }
+ if (win_split(0, flags) == FAIL) {
+ return FAIL; // not enough room for window
+ }
+ p_swb = empty_option; // don't split again
+ swb_flags = 0;
+ RESET_BINDING(curwin);
+ if (ll_ref != NULL) {
+ // The new window should use the location list from the
+ // location list window
+ win_set_loclist(curwin, ll_ref);
+ }
+ return OK;
+}
+
+// Go to a window that shows the right buffer. If the window is not found, go
+// to the window just above the location list window. This is used for opening
+// a file from a location window and not from a quickfix window. If some usable
+// window is previously found, then it is supplied in 'use_win'.
+static void qf_goto_win_with_ll_file(win_T *use_win, int qf_fnum,
+ qf_info_T *ll_ref)
+{
+ win_T *win = use_win;
+
+ if (win == NULL) {
+ // Find the window showing the selected file
+ FOR_ALL_WINDOWS_IN_TAB(win2, curtab) {
+ if (win2->w_buffer->b_fnum == qf_fnum) {
+ win = win2;
break;
}
}
+ if (win == NULL) {
+ // Find a previous usable window
+ win = curwin;
+ do {
+ if (bt_normal(win->w_buffer)) {
+ break;
+ }
+ if (win->w_prev == NULL) {
+ win = lastwin; // wrap around the top
+ } else {
+ win = win->w_prev; // go to previous window
+ }
+ } while (win != curwin);
+ }
+ }
+ win_goto(win);
+
+ // If the location list for the window is not set, then set it
+ // to the location list from the location window
+ if (win->w_llist == NULL && ll_ref != NULL) {
+ // The new window should use the location list from the
+ // location list window
+ win_set_loclist(win, ll_ref);
+ }
+}
+
+// Go to a window that shows the specified file. If a window is not found, go
+// to the window just above the quickfix window. This is used for opening a
+// file from a quickfix window and not from a location window.
+static void qf_goto_win_with_qfl_file(int qf_fnum)
+{
+ win_T *win = curwin;
+ win_T *altwin = NULL;
+ for (;;) {
+ if (win->w_buffer->b_fnum == qf_fnum) {
+ break;
+ }
+ if (win->w_prev == NULL) {
+ win = lastwin; // wrap around the top
+ } else {
+ win = win->w_prev; // go to previous window
+ }
+
+ if (IS_QF_WINDOW(win)) {
+ // Didn't find it, go to the window before the quickfix
+ // window, unless 'switchbuf' contains 'uselast': in this case we
+ // try to jump to the previously used window first.
+ if ((swb_flags & SWB_USELAST) && win_valid(prevwin)) {
+ win = prevwin;
+ } else if (altwin != NULL) {
+ win = altwin;
+ } else if (curwin->w_prev != NULL) {
+ win = curwin->w_prev;
+ } else {
+ win = curwin->w_next;
+ }
+ break;
+ }
+
+ // Remember a usable window.
+ if (altwin == NULL
+ && !win->w_p_pvw
+ && bt_normal(win->w_buffer)) {
+ altwin = win;
+ }
+ }
+
+ win_goto(win);
+}
+
+// Find a suitable window for opening a file (qf_fnum) from the
+// quickfix/location list and jump to it. If the file is already opened in a
+// window, jump to it. Otherwise open a new window to display the file. This is
+// called from either a quickfix or a location list window.
+static int qf_jump_to_usable_window(int qf_fnum, int *opened_window)
+{
+ win_T *usable_win_ptr = NULL;
+ bool usable_win = false;
+
+ qf_info_T *ll_ref = curwin->w_llist_ref;
+ if (ll_ref != NULL) {
+ // Find a non-quickfix window with this location list
+ usable_win_ptr = qf_find_win_with_loclist(ll_ref);
+ if (usable_win_ptr != NULL) {
+ usable_win = true;
+ }
}
if (!usable_win) {
// Locate a window showing a normal buffer
- FOR_ALL_WINDOWS_IN_TAB(win2, curtab) {
- if (win2->w_buffer->b_p_bt[0] == NUL) {
- usable_win = 1;
- break;
- }
+ win_T *win = qf_find_win_with_normal_buf();
+ if (win != NULL) {
+ usable_win = true;
}
}
// If no usable window is found and 'switchbuf' contains "usetab"
// then search in other tabs.
if (!usable_win && (swb_flags & SWB_USETAB)) {
- FOR_ALL_TAB_WINDOWS(tp, wp) {
- if (wp->w_buffer->b_fnum == qf_fnum) {
- goto_tabpage_win(tp, wp);
- usable_win = 1;
- goto win_found;
- }
- }
+ usable_win = qf_goto_tabwin_with_file(qf_fnum);
}
-win_found:
// If there is only one window and it is the quickfix window, create a
// new one above the quickfix window.
if ((ONE_WINDOW && bt_quickfix(curbuf)) || !usable_win) {
- flags = WSP_ABOVE;
- if (ll_ref != NULL) {
- flags |= WSP_NEWLOC;
- }
- if (win_split(0, flags) == FAIL) {
- return FAIL; // not enough room for window
+ if (qf_open_new_file_win(ll_ref) != OK) {
+ return FAIL;
}
*opened_window = true; // close it when fail
- p_swb = empty_option; // don't split again
- swb_flags = 0;
- RESET_BINDING(curwin);
- if (ll_ref != NULL) {
- // The new window should use the location list from the
- // location list window
- curwin->w_llist = ll_ref;
- ll_ref->qf_refcount++;
- }
} else {
- if (curwin->w_llist_ref != NULL) {
- // In a location window
- win = usable_win_ptr;
- if (win == NULL) {
- // Find the window showing the selected file
- FOR_ALL_WINDOWS_IN_TAB(win2, curtab) {
- if (win2->w_buffer->b_fnum == qf_fnum) {
- win = win2;
- break;
- }
- }
- if (win == NULL) {
- // Find a previous usable window
- win = curwin;
- do {
- if (win->w_buffer->b_p_bt[0] == NUL) {
- break;
- }
- if (win->w_prev == NULL) {
- win = lastwin; // wrap around the top
- } else {
- win = win->w_prev; // go to previous window
- }
- } while (win != curwin);
- }
- }
- win_goto(win);
-
- // If the location list for the window is not set, then set it
- // to the location list from the location window
- if (win->w_llist == NULL) {
- win->w_llist = ll_ref;
- if (ll_ref != NULL) {
- ll_ref->qf_refcount++;
- }
- }
- } else {
- // Try to find a window that shows the right buffer.
- // Default to the window just above the quickfix buffer.
- win = curwin;
- altwin = NULL;
- for (;;) {
- if (win->w_buffer->b_fnum == qf_fnum) {
- break;
- }
- if (win->w_prev == NULL) {
- win = lastwin; // wrap around the top
- } else {
- win = win->w_prev; // go to previous window
- }
- if (IS_QF_WINDOW(win)) {
- // Didn't find it, go to the window before the quickfix window.
- if (altwin != NULL) {
- win = altwin;
- } else if (curwin->w_prev != NULL) {
- win = curwin->w_prev;
- } else {
- win = curwin->w_next;
- }
- break;
- }
-
- // Remember a usable window.
- if (altwin == NULL && !win->w_p_pvw
- && win->w_buffer->b_p_bt[0] == NUL) {
- altwin = win;
- }
- }
-
- win_goto(win);
+ if (curwin->w_llist_ref != NULL) { // In a location window
+ qf_goto_win_with_ll_file(usable_win_ptr, qf_fnum, ll_ref);
+ } else { // In a quickfix window
+ qf_goto_win_with_qfl_file(qf_fnum);
}
}
@@ -2211,6 +2674,8 @@ win_found:
static int qf_jump_edit_buffer(qf_info_T *qi, qfline_T *qf_ptr, int forceit,
win_T *oldwin, int *opened_window, int *abort)
{
+ qf_list_T *qfl = qf_get_curlist(qi);
+ qfltype_T qfl_type = qfl->qfl_type;
int retval = OK;
if (qf_ptr->qf_type == 1) {
@@ -2225,12 +2690,12 @@ static int qf_jump_edit_buffer(qf_info_T *qi, qfline_T *qf_ptr, int forceit,
oldwin == curwin ? curwin : NULL);
}
} else {
- unsigned save_qfid = qi->qf_lists[qi->qf_curlist].qf_id;
+ unsigned save_qfid = qfl->qf_id;
retval = buflist_getfile(qf_ptr->qf_fnum, (linenr_T)1,
GETF_SETMARK | GETF_SWITCH, forceit);
- if (IS_LL_STACK(qi)) {
+ if (qfl_type == QFLT_LOCATION) {
// Location list. Check whether the associated window is still
// present and the list is still valid.
if (!win_valid_any_tab(oldwin)) {
@@ -2241,8 +2706,8 @@ static int qf_jump_edit_buffer(qf_info_T *qi, qfline_T *qf_ptr, int forceit,
EMSG(_(e_loc_list_changed));
*abort = true;
}
- } else if (!is_qf_entry_present(qi, qf_ptr)) {
- if (IS_QF_STACK(qi)) {
+ } else if (!is_qf_entry_present(qfl, qf_ptr)) {
+ if (qfl_type == QFLT_QUICKFIX) {
EMSG(_("E925: Current quickfix was changed"));
} else {
EMSG(_(e_loc_list_changed));
@@ -2258,15 +2723,12 @@ static int qf_jump_edit_buffer(qf_info_T *qi, qfline_T *qf_ptr, int forceit,
return retval;
}
-/// Goto the error line in the current file using either line/column number or a
-/// search pattern.
+/// Go to the error line in the current file using either line/column number or
+/// a search pattern.
static void qf_jump_goto_line(linenr_T qf_lnum, int qf_col, char_u qf_viscol,
char_u *qf_pattern)
{
linenr_T i;
- char_u *line;
- colnr_T screen_col;
- colnr_T char_col;
if (qf_pattern == NULL) {
// Go to line with error, unless qf_lnum is 0.
@@ -2278,26 +2740,11 @@ static void qf_jump_goto_line(linenr_T qf_lnum, int qf_col, char_u qf_viscol,
curwin->w_cursor.lnum = i;
}
if (qf_col > 0) {
- curwin->w_cursor.col = qf_col - 1;
curwin->w_cursor.coladd = 0;
if (qf_viscol == true) {
- // Check each character from the beginning of the error
- // line up to the error column. For each tab character
- // found, reduce the error column value by the length of
- // a tab character.
- line = get_cursor_line_ptr();
- screen_col = 0;
- for (char_col = 0; char_col < curwin->w_cursor.col; char_col++) {
- if (*line == NUL) {
- break;
- }
- if (*line++ == '\t') {
- curwin->w_cursor.col -= 7 - (screen_col % 8);
- screen_col += 8 - (screen_col % 8);
- } else {
- screen_col++;
- }
- }
+ coladvance(qf_col - 1);
+ } else {
+ curwin->w_cursor.col = qf_col - 1;
}
curwin->w_set_curswant = true;
check_cursor();
@@ -2308,7 +2755,7 @@ static void qf_jump_goto_line(linenr_T qf_lnum, int qf_col, char_u qf_viscol,
// 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, NULL)) {
+ if (!do_search(NULL, '/', qf_pattern, (long)1, SEARCH_KEEP, NULL)) {
curwin->w_cursor = save_cursor;
}
}
@@ -2324,7 +2771,7 @@ static void qf_jump_print_msg(qf_info_T *qi, int qf_index, qfline_T *qf_ptr,
update_topline_redraw();
}
snprintf((char *)IObuff, IOSIZE, _("(%d of %d)%s%s: "), qf_index,
- qi->qf_lists[qi->qf_curlist].qf_count,
+ qf_get_curlist(qi)->qf_count,
qf_ptr->qf_cleared ? _(" (line deleted)") : "",
(char *)qf_types(qf_ptr->qf_type, qf_ptr->qf_nr));
// Add the message, skipping leading whitespace and newlines.
@@ -2354,6 +2801,7 @@ static void qf_jump_print_msg(qf_info_T *qi, int qf_index, qfline_T *qf_ptr,
/// else go to entry "errornr"
void qf_jump(qf_info_T *qi, int dir, int errornr, int forceit)
{
+ qf_list_T *qfl;
qfline_T *qf_ptr;
qfline_T *old_qf_ptr;
int qf_index;
@@ -2371,32 +2819,34 @@ void qf_jump(qf_info_T *qi, int dir, int errornr, int forceit)
if (qi == NULL)
qi = &ql_info;
- if (qi->qf_curlist >= qi->qf_listcount
- || qi->qf_lists[qi->qf_curlist].qf_count == 0) {
+ if (qf_stack_empty(qi) || qf_list_empty(qf_get_curlist(qi))) {
EMSG(_(e_quickfix));
return;
}
- qf_ptr = qi->qf_lists[qi->qf_curlist].qf_ptr;
+ qfl = qf_get_curlist(qi);
+
+ qf_ptr = qfl->qf_ptr;
old_qf_ptr = qf_ptr;
- qf_index = qi->qf_lists[qi->qf_curlist].qf_index;
+ qf_index = qfl->qf_index;
old_qf_index = qf_index;
if (dir != 0) { // next/prev valid entry
- qf_ptr = get_nth_valid_entry(qi, errornr, qf_ptr, &qf_index, dir);
+ qf_ptr = get_nth_valid_entry(qfl, errornr, qf_ptr, &qf_index, dir);
if (qf_ptr == NULL) {
qf_ptr = old_qf_ptr;
qf_index = old_qf_index;
goto theend; // The horror... the horror...
}
} else if (errornr != 0) { // go to specified number
- qf_ptr = get_nth_entry(qi, errornr, qf_ptr, &qf_index);
+ qf_ptr = get_nth_entry(qfl, errornr, qf_ptr, &qf_index);
}
- qi->qf_lists[qi->qf_curlist].qf_index = qf_index;
- if (qf_win_pos_update(qi, old_qf_index))
- /* No need to print the error message if it's visible in the error
- * window */
- print_message = FALSE;
+ qfl->qf_index = qf_index;
+ if (qf_win_pos_update(qi, old_qf_index)) {
+ // No need to print the error message if it's visible in the error
+ // window
+ print_message = false;
+ }
// For ":helpgrep" find a help window or open one.
if (qf_ptr->qf_type == 1 && (!bt_help(curwin->w_buffer) || cmdmod.tab != 0)) {
@@ -2418,10 +2868,8 @@ void qf_jump(qf_info_T *qi, int dir, int errornr, int forceit)
}
}
- /*
- * If there is a file name,
- * read the wanted file if needed, and check autowrite etc.
- */
+ // If there is a file name,
+ // read the wanted file if needed, and check autowrite etc.
old_curbuf = curbuf;
old_lnum = curwin->w_cursor.lnum;
@@ -2465,12 +2913,12 @@ failed:
}
theend:
if (qi != NULL) {
- qi->qf_lists[qi->qf_curlist].qf_ptr = qf_ptr;
- qi->qf_lists[qi->qf_curlist].qf_index = qf_index;
+ qfl->qf_ptr = qf_ptr;
+ qfl->qf_index = qf_index;
}
if (p_swb != old_swb && opened_window) {
- /* Restore old 'switchbuf' value, but not when an autocommand or
- * modeline has changed the value. */
+ // Restore old 'switchbuf' value, but not when an autocommand or
+ // modeline has changed the value.
if (p_swb == empty_option) {
p_swb = old_swb;
swb_flags = old_swb_flags;
@@ -2479,36 +2927,115 @@ theend:
}
}
-/*
- * ":clist": list all errors
- * ":llist": list all locations
- */
+
+// Highlight attributes used for displaying entries from the quickfix list.
+static int qfFileAttr;
+static int qfSepAttr;
+static int qfLineAttr;
+
+/// Display information about a single entry from the quickfix/location list.
+/// Used by ":clist/:llist" commands.
+/// 'cursel' will be set to true for the currently selected entry in the
+/// quickfix list.
+static void qf_list_entry(qfline_T *qfp, int qf_idx, bool cursel)
+{
+ char_u *fname;
+ buf_T *buf;
+
+ fname = NULL;
+ if (qfp->qf_module != NULL && *qfp->qf_module != NUL) {
+ vim_snprintf((char *)IObuff, IOSIZE, "%2d %s", qf_idx,
+ (char *)qfp->qf_module);
+ } else {
+ if (qfp->qf_fnum != 0
+ && (buf = buflist_findnr(qfp->qf_fnum)) != NULL) {
+ fname = buf->b_fname;
+ if (qfp->qf_type == 1) { // :helpgrep
+ fname = path_tail(fname);
+ }
+ }
+ if (fname == NULL) {
+ snprintf((char *)IObuff, IOSIZE, "%2d", qf_idx);
+ } else {
+ vim_snprintf((char *)IObuff, IOSIZE, "%2d %s",
+ qf_idx, (char *)fname);
+ }
+ }
+
+ // Support for filtering entries using :filter /pat/ clist
+ // Match against the module name, file name, search pattern and
+ // text of the entry.
+ bool filter_entry = true;
+ if (qfp->qf_module != NULL && *qfp->qf_module != NUL) {
+ filter_entry &= message_filtered(qfp->qf_module);
+ }
+ if (filter_entry && fname != NULL) {
+ filter_entry &= message_filtered(fname);
+ }
+ if (filter_entry && qfp->qf_pattern != NULL) {
+ filter_entry &= message_filtered(qfp->qf_pattern);
+ }
+ if (filter_entry) {
+ filter_entry &= message_filtered(qfp->qf_text);
+ }
+ if (filter_entry) {
+ return;
+ }
+
+ msg_putchar('\n');
+ msg_outtrans_attr(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 if (qfp->qf_col == 0) {
+ vim_snprintf((char *)IObuff, IOSIZE, "%" PRIdLINENR, qfp->qf_lnum);
+ } else {
+ vim_snprintf((char *)IObuff, IOSIZE, "%" PRIdLINENR " col %d",
+ qfp->qf_lnum, qfp->qf_col);
+ }
+ vim_snprintf((char *)IObuff + STRLEN(IObuff), IOSIZE, "%s",
+ (char *)qf_types(qfp->qf_type, qfp->qf_nr));
+ msg_puts_attr((const char *)IObuff, qfLineAttr);
+ msg_puts_attr(":", qfSepAttr);
+ if (qfp->qf_pattern != NULL) {
+ qf_fmt_text(qfp->qf_pattern, IObuff, IOSIZE);
+ msg_puts((const char *)IObuff);
+ msg_puts_attr(":", qfSepAttr);
+ }
+ msg_puts(" ");
+
+ // 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,
+ IObuff, IOSIZE);
+ msg_prt_line(IObuff, false);
+ ui_flush(); // show one line at a time
+}
+
+// ":clist": list all errors
+// ":llist": list all locations
void qf_list(exarg_T *eap)
{
- buf_T *buf;
- char_u *fname;
- qfline_T *qfp;
+ qf_list_T *qfl;
+ qfline_T *qfp;
int i;
int idx1 = 1;
int idx2 = -1;
char_u *arg = eap->arg;
- int qfFileAttr;
- int qfSepAttr;
- int qfLineAttr;
int all = eap->forceit; // if not :cl!, only show
// recognised errors
- qf_info_T *qi = &ql_info;
+ qf_info_T *qi;
- if (eap->cmdidx == CMD_llist) {
- qi = GET_LOC_LIST(curwin);
- if (qi == NULL) {
- EMSG(_(e_loclist));
- return;
- }
+ if ((qi = qf_cmd_get_stack(eap, true)) == NULL) {
+ return;
}
- if (qi->qf_curlist >= qi->qf_listcount
- || qi->qf_lists[qi->qf_curlist].qf_count == 0) {
+ if (qf_stack_empty(qi) || qf_list_empty(qf_get_curlist(qi))) {
EMSG(_(e_quickfix));
return;
}
@@ -2522,12 +3049,13 @@ void qf_list(exarg_T *eap)
EMSG(_(e_trailing));
return;
}
+ qfl = qf_get_curlist(qi);
if (plus) {
- i = qi->qf_lists[qi->qf_curlist].qf_index;
+ i = qfl->qf_index;
idx2 = i + idx1;
idx1 = i;
} else {
- i = qi->qf_lists[qi->qf_curlist].qf_count;
+ i = qfl->qf_count;
if (idx1 < 0) {
idx1 = (-idx1 > i) ? 0 : idx1 + i + 1;
}
@@ -2554,107 +3082,25 @@ void qf_list(exarg_T *eap)
qfLineAttr = HL_ATTR(HLF_N);
}
- if (qi->qf_lists[qi->qf_curlist].qf_nonevalid) {
+ if (qfl->qf_nonevalid) {
all = true;
}
- qfp = qi->qf_lists[qi->qf_curlist].qf_start;
- for (i = 1; !got_int && i <= qi->qf_lists[qi->qf_curlist].qf_count; ) {
+ FOR_ALL_QFL_ITEMS(qfl, qfp, i) {
if ((qfp->qf_valid || all) && idx1 <= i && i <= idx2) {
- if (got_int) {
- break;
- }
-
- fname = NULL;
- if (qfp->qf_module != NULL && *qfp->qf_module != NUL) {
- vim_snprintf((char *)IObuff, IOSIZE, "%2d %s", i,
- (char *)qfp->qf_module);
- } else {
- if (qfp->qf_fnum != 0 && (buf = buflist_findnr(qfp->qf_fnum)) != NULL) {
- fname = buf->b_fname;
- if (qfp->qf_type == 1) { // :helpgrep
- fname = path_tail(fname);
- }
- }
- if (fname == NULL) {
- snprintf((char *)IObuff, IOSIZE, "%2d", i);
- } else {
- vim_snprintf((char *)IObuff, IOSIZE, "%2d %s", i, (char *)fname);
- }
- }
-
- // Support for filtering entries using :filter /pat/ clist
- // Match against the module name, file name, search pattern and
- // text of the entry.
- bool filter_entry = true;
- if (qfp->qf_module != NULL && *qfp->qf_module != NUL) {
- filter_entry &= message_filtered(qfp->qf_module);
- }
- if (filter_entry && fname != NULL) {
- filter_entry &= message_filtered(fname);
- }
- if (filter_entry && qfp->qf_pattern != NULL) {
- filter_entry &= message_filtered(qfp->qf_pattern);
- }
- if (filter_entry) {
- filter_entry &= message_filtered(qfp->qf_text);
- }
- if (filter_entry) {
- goto next_entry;
- }
- msg_putchar('\n');
- msg_outtrans_attr(IObuff, i == qi->qf_lists[qi->qf_curlist].qf_index
- ? HL_ATTR(HLF_QFL) : qfFileAttr);
-
- if (qfp->qf_lnum != 0) {
- msg_puts_attr(":", qfSepAttr);
- }
- if (qfp->qf_lnum == 0) {
- IObuff[0] = NUL;
- } else if (qfp->qf_col == 0) {
- vim_snprintf((char *)IObuff, IOSIZE, "%" PRIdLINENR, qfp->qf_lnum);
- } else {
- vim_snprintf((char *)IObuff, IOSIZE, "%" PRIdLINENR " col %d",
- qfp->qf_lnum, qfp->qf_col);
- }
- vim_snprintf((char *)IObuff + STRLEN(IObuff), IOSIZE, "%s",
- (char *)qf_types(qfp->qf_type, qfp->qf_nr));
- msg_puts_attr((const char *)IObuff, qfLineAttr);
- msg_puts_attr(":", qfSepAttr);
- if (qfp->qf_pattern != NULL) {
- qf_fmt_text(qfp->qf_pattern, IObuff, IOSIZE);
- msg_puts((const char *)IObuff);
- msg_puts_attr(":", qfSepAttr);
- }
- msg_puts(" ");
-
- /* 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,
- IObuff, IOSIZE);
- msg_prt_line(IObuff, FALSE);
- ui_flush(); /* show one line at a time */
+ qf_list_entry(qfp, i, i == qfl->qf_index);
}
-
-next_entry:
- qfp = qfp->qf_next;
- if (qfp == NULL) {
- break;
- }
- i++;
os_breakcheck();
}
}
-/*
- * Remove newlines and leading whitespace from an error message.
- * Put the result in "buf[bufsize]".
- */
-static void qf_fmt_text(char_u *text, char_u *buf, int bufsize)
+// Remove newlines and leading whitespace from an error message.
+// Put the result in "buf[bufsize]".
+static void qf_fmt_text(const char_u *restrict text, char_u *restrict buf,
+ int bufsize)
+ FUNC_ATTR_NONNULL_ALL
{
int i;
- char_u *p = text;
+ const char_u *p = text;
for (i = 0; *p != NUL && i < bufsize - 1; ++i) {
if (*p == '\n') {
@@ -2695,23 +3141,17 @@ static void qf_msg(qf_info_T *qi, int which, char *lead)
msg(buf);
}
-/*
- * ":colder [count]": Up in the quickfix stack.
- * ":cnewer [count]": Down in the quickfix stack.
- * ":lolder [count]": Up in the location list stack.
- * ":lnewer [count]": Down in the location list stack.
- */
+/// ":colder [count]": Up in the quickfix stack.
+/// ":cnewer [count]": Down in the quickfix stack.
+/// ":lolder [count]": Up in the location list stack.
+/// ":lnewer [count]": Down in the location list stack.
void qf_age(exarg_T *eap)
{
- qf_info_T *qi = &ql_info;
+ qf_info_T *qi;
int count;
- if (eap->cmdidx == CMD_lolder || eap->cmdidx == CMD_lnewer) {
- qi = GET_LOC_LIST(curwin);
- if (qi == NULL) {
- EMSG(_(e_loclist));
- return;
- }
+ if ((qi = qf_cmd_get_stack(eap, true)) == NULL) {
+ return;
}
if (eap->addr_count != 0) {
@@ -2742,14 +3182,10 @@ void qf_age(exarg_T *eap)
/// Display the information about all the quickfix/location lists in the stack.
void qf_history(exarg_T *eap)
{
- qf_info_T *qi = &ql_info;
+ qf_info_T *qi = qf_cmd_get_stack(eap, false);
int i;
- if (eap->cmdidx == CMD_lhistory) {
- qi = GET_LOC_LIST(curwin);
- }
- if (qi == NULL || (qi->qf_listcount == 0
- && qi->qf_lists[qi->qf_curlist].qf_count == 0)) {
+ if (qf_stack_empty(qi) || qf_list_empty(qf_get_curlist(qi))) {
MSG(_("No entries"));
} else {
for (i = 0; i < qi->qf_listcount; i++) {
@@ -2760,12 +3196,11 @@ void qf_history(exarg_T *eap)
/// Free all the entries in the error list "idx". Note that other information
/// associated with the list like context and title are not freed.
-static void qf_free_items(qf_info_T *qi, int idx)
+static void qf_free_items(qf_list_T *qfl)
{
qfline_T *qfp;
qfline_T *qfpnext;
bool stop = false;
- qf_list_T *qfl = &qi->qf_lists[idx];
while (qfl->qf_count && qfl->qf_start != NULL) {
qfp = qfl->qf_start;
@@ -2806,10 +3241,9 @@ static void qf_free_items(qf_info_T *qi, int idx)
/// Free error list "idx". Frees all the entries in the quickfix list,
/// associated context information and the title.
-static void qf_free(qf_info_T *qi, int idx)
+static void qf_free(qf_list_T *qfl)
{
- qf_list_T *qfl = &qi->qf_lists[idx];
- qf_free_items(qi, idx);
+ qf_free_items(qfl);
XFREE_CLEAR(qfl->qf_title);
tv_free(qfl->qf_ctx);
@@ -2818,9 +3252,7 @@ static void qf_free(qf_info_T *qi, int idx)
qfl->qf_changedtick = 0L;
}
-/*
- * qf_mark_adjust: adjust marks
- */
+// qf_mark_adjust: adjust marks
bool qf_mark_adjust(win_T *wp, linenr_T line1, linenr_T line2, long amount,
long amount_after)
{
@@ -2841,11 +3273,10 @@ bool qf_mark_adjust(win_T *wp, linenr_T line1, linenr_T line2, long amount,
qi = wp->w_llist;
}
- for (idx = 0; idx < qi->qf_listcount; ++idx)
- if (qi->qf_lists[idx].qf_count)
- for (i = 0, qfp = qi->qf_lists[idx].qf_start;
- i < qi->qf_lists[idx].qf_count && qfp != NULL;
- i++, qfp = qfp->qf_next) {
+ for (idx = 0; idx < qi->qf_listcount; idx++) {
+ 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) {
found_one = true;
if (qfp->qf_lnum >= line1 && qfp->qf_lnum <= line2) {
@@ -2857,25 +3288,25 @@ bool qf_mark_adjust(win_T *wp, linenr_T line1, linenr_T line2, long amount,
qfp->qf_lnum += amount_after;
}
}
+ }
+ }
return found_one;
}
-/*
- * Make a nice message out of the error character and the error number:
- * char number message
- * e or E 0 " error"
- * w or W 0 " warning"
- * i or I 0 " info"
- * 0 0 ""
- * other 0 " c"
- * e or E n " error n"
- * w or W n " warning n"
- * i or I n " info n"
- * 0 n " error n"
- * other n " c n"
- * 1 x "" :helpgrep
- */
+// Make a nice message out of the error character and the error number:
+// char number message
+// e or E 0 " error"
+// w or W 0 " warning"
+// i or I 0 " info"
+// 0 0 ""
+// other 0 " c"
+// e or E n " error n"
+// w or W n " warning n"
+// i or I n " info n"
+// 0 n " error n"
+// other n " c n"
+// 1 x "" :helpgrep
static char_u *qf_types(int c, int nr)
{
static char_u buf[20];
@@ -2916,7 +3347,7 @@ void qf_view_result(bool split)
if (IS_LL_WINDOW(curwin)) {
qi = GET_LOC_LIST(curwin);
}
- if (qf_list_empty(qi, qi->qf_curlist)) {
+ if (qf_list_empty(qf_get_curlist(qi))) {
EMSG(_(e_quickfix));
return;
}
@@ -2936,83 +3367,175 @@ void qf_view_result(bool split)
do_cmdline_cmd((IS_LL_WINDOW(curwin) ? ".ll" : ".cc"));
}
-/*
- * ":cwindow": open the quickfix window if we have errors to display,
- * close it if not.
- * ":lwindow": open the location list window if we have locations to display,
- * close it if not.
- */
+// ":cwindow": open the quickfix window if we have errors to display,
+// close it if not.
+// ":lwindow": open the location list window if we have locations to display,
+// close it if not.
void ex_cwindow(exarg_T *eap)
{
- qf_info_T *qi = &ql_info;
+ qf_info_T *qi;
+ qf_list_T *qfl;
win_T *win;
- if (eap->cmdidx == CMD_lwindow) {
- qi = GET_LOC_LIST(curwin);
- if (qi == NULL)
- return;
+ if ((qi = qf_cmd_get_stack(eap, true)) == NULL) {
+ return;
}
- /* Look for an existing quickfix window. */
+ qfl = qf_get_curlist(qi);
+
+ // Look for an existing quickfix window.
win = qf_find_win(qi);
- /*
- * If a quickfix window is open but we have no errors to display,
- * close the window. If a quickfix window is not open, then open
- * it if we have errors; otherwise, leave it closed.
- */
- if (qi->qf_lists[qi->qf_curlist].qf_nonevalid
- || qi->qf_lists[qi->qf_curlist].qf_count == 0
- || qi->qf_curlist >= qi->qf_listcount) {
- if (win != NULL)
+ // If a quickfix window is open but we have no errors to display,
+ // close the window. If a quickfix window is not open, then open
+ // it if we have errors; otherwise, leave it closed.
+ if (qf_stack_empty(qi)
+ || qfl->qf_nonevalid
+ || qf_list_empty(qf_get_curlist(qi))) {
+ if (win != NULL) {
ex_cclose(eap);
- } else if (win == NULL)
+ }
+ } else if (win == NULL) {
ex_copen(eap);
+ }
}
-/*
- * ":cclose": close the window showing the list of errors.
- * ":lclose": close the window showing the location list
- */
+// ":cclose": close the window showing the list of errors.
+// ":lclose": close the window showing the location list
void ex_cclose(exarg_T *eap)
{
- win_T *win = NULL;
- qf_info_T *qi = &ql_info;
+ win_T *win = NULL;
+ qf_info_T *qi;
- if (eap->cmdidx == CMD_lclose || eap->cmdidx == CMD_lwindow) {
- qi = GET_LOC_LIST(curwin);
- if (qi == NULL)
- return;
+ if ((qi = qf_cmd_get_stack(eap, false)) == NULL) {
+ return;
}
- /* Find existing quickfix window and close it. */
+ // Find existing quickfix window and close it.
win = qf_find_win(qi);
if (win != NULL) {
win_close(win, false);
}
}
-/*
- * ":copen": open a window that shows the list of errors.
- * ":lopen": open a window that shows the location list.
- */
+// Goto a quickfix or location list window (if present).
+// Returns OK if the window is found, FAIL otherwise.
+static int qf_goto_cwindow(const qf_info_T *qi, bool resize, int sz,
+ bool vertsplit)
+{
+ win_T *const win = qf_find_win(qi);
+ if (win == NULL) {
+ return FAIL;
+ }
+
+ win_goto(win);
+ if (resize) {
+ if (vertsplit) {
+ if (sz != win->w_width) {
+ win_setwidth(sz);
+ }
+ } else if (sz != win->w_height
+ && (win->w_height + win->w_status_height + tabline_height()
+ < cmdline_row)) {
+ win_setheight(sz);
+ }
+ }
+
+ return OK;
+}
+
+// Open a new quickfix or location list window, load the quickfix buffer and
+// set the appropriate options for the window.
+// Returns FAIL if the window could not be opened.
+static int qf_open_new_cwindow(const qf_info_T *qi, int height)
+{
+ win_T *oldwin = curwin;
+ const tabpage_T *const prevtab = curtab;
+ int flags = 0;
+
+ const buf_T *const qf_buf = qf_find_buf(qi);
+
+ // The current window becomes the previous window afterwards.
+ win_T *const win = curwin;
+
+ if (IS_QF_STACK(qi) && cmdmod.split == 0) {
+ // Create the new quickfix window at the very bottom, except when
+ // :belowright or :aboveleft is used.
+ win_goto(lastwin);
+ }
+ // Default is to open the window below the current window
+ if (cmdmod.split == 0) {
+ flags = WSP_BELOW;
+ }
+ flags |= WSP_NEWLOC;
+ if (win_split(height, flags) == FAIL) {
+ return FAIL; // not enough room for window
+ }
+ RESET_BINDING(curwin);
+
+ if (IS_LL_STACK(qi)) {
+ // For the location list window, create a reference to the
+ // location list from the window 'win'.
+ curwin->w_llist_ref = win->w_llist;
+ win->w_llist->qf_refcount++;
+ }
+
+ if (oldwin != curwin) {
+ oldwin = NULL; // don't store info when in another window
+ }
+ if (qf_buf != NULL) {
+ // Use the existing quickfix buffer
+ (void)do_ecmd(qf_buf->b_fnum, NULL, NULL, NULL, ECMD_ONE,
+ ECMD_HIDE + ECMD_OLDBUF, oldwin);
+ } else {
+ // Create a new quickfix buffer
+ (void)do_ecmd(0, NULL, NULL, NULL, ECMD_ONE, ECMD_HIDE, oldwin);
+
+ // switch off 'swapfile'
+ set_option_value("swf", 0L, NULL, OPT_LOCAL);
+ set_option_value("bt", 0L, "quickfix", OPT_LOCAL);
+ set_option_value("bh", 0L, "wipe", OPT_LOCAL);
+ RESET_BINDING(curwin);
+ curwin->w_p_diff = false;
+ set_option_value("fdm", 0L, "manual", OPT_LOCAL);
+ }
+
+ // Only set the height when still in the same tab page and there is no
+ // window to the side.
+ if (curtab == prevtab && curwin->w_width == Columns) {
+ win_setheight(height);
+ }
+ curwin->w_p_wfh = true; // set 'winfixheight'
+ if (win_valid(win)) {
+ prevwin = win;
+ }
+ return OK;
+}
+
+/// Set "w:quickfix_title" if "qi" has a title.
+static void qf_set_title_var(qf_list_T *qfl)
+{
+ if (qfl->qf_title != NULL) {
+ set_internal_string_var((char_u *)"w:quickfix_title", qfl->qf_title);
+ }
+}
+
+/// ":copen": open a window that shows the list of errors.
+/// ":lopen": open a window that shows the location list.
void ex_copen(exarg_T *eap)
{
- qf_info_T *qi = &ql_info;
+ qf_info_T *qi;
+ qf_list_T *qfl;
int height;
- win_T *win;
- tabpage_T *prevtab = curtab;
- buf_T *qf_buf;
- win_T *oldwin = curwin;
+ int status = FAIL;
+ int lnum;
- if (eap->cmdidx == CMD_lopen || eap->cmdidx == CMD_lwindow) {
- qi = GET_LOC_LIST(curwin);
- if (qi == NULL) {
- EMSG(_(e_loclist));
- return;
- }
+ if ((qi = qf_cmd_get_stack(eap, true)) == NULL) {
+ return;
}
+ incr_quickfix_busy();
+
if (eap->addr_count != 0) {
assert(eap->line2 <= INT_MAX);
height = (int)eap->line2;
@@ -3021,94 +3544,33 @@ void ex_copen(exarg_T *eap)
}
reset_VIsual_and_resel(); // stop Visual mode
- /*
- * Find existing quickfix window, or open a new one.
- */
- win = qf_find_win(qi);
-
- if (win != NULL && cmdmod.tab == 0) {
- win_goto(win);
- if (eap->addr_count != 0) {
- if (cmdmod.split & WSP_VERT) {
- if (height != win->w_width) {
- win_setwidth(height);
- }
- } else {
- if (height != win->w_height) {
- win_setheight(height);
- }
- }
- }
- } else {
- int flags = 0;
-
- qf_buf = qf_find_buf(qi);
-
- /* The current window becomes the previous window afterwards. */
- win = curwin;
-
- if ((eap->cmdidx == CMD_copen || eap->cmdidx == CMD_cwindow)
- && cmdmod.split == 0)
- // Create the new quickfix window at the very bottom, except when
- // :belowright or :aboveleft is used.
- win_goto(lastwin);
- // Default is to open the window below the current window
- if (cmdmod.split == 0) {
- flags = WSP_BELOW;
- }
- flags |= WSP_NEWLOC;
- if (win_split(height, flags) == FAIL) {
- return; // not enough room for window
+ // Find an existing quickfix window, or open a new one.
+ if (cmdmod.tab == 0) {
+ status = qf_goto_cwindow(qi, eap->addr_count != 0, height,
+ cmdmod.split & WSP_VERT);
+ }
+ if (status == FAIL) {
+ if (qf_open_new_cwindow(qi, height) == FAIL) {
+ decr_quickfix_busy();
+ return;
}
- RESET_BINDING(curwin);
+ }
- if (eap->cmdidx == CMD_lopen || eap->cmdidx == CMD_lwindow) {
- /*
- * For the location list window, create a reference to the
- * location list from the window 'win'.
- */
- curwin->w_llist_ref = win->w_llist;
- win->w_llist->qf_refcount++;
- }
-
- if (oldwin != curwin)
- oldwin = NULL; /* don't store info when in another window */
- if (qf_buf != NULL)
- /* Use the existing quickfix buffer */
- (void)do_ecmd(qf_buf->b_fnum, NULL, NULL, NULL, ECMD_ONE,
- ECMD_HIDE + ECMD_OLDBUF, oldwin);
- else {
- /* Create a new quickfix buffer */
- (void)do_ecmd(0, NULL, NULL, NULL, ECMD_ONE, ECMD_HIDE, oldwin);
- // Switch off 'swapfile'.
- set_option_value("swf", 0L, NULL, OPT_LOCAL);
- set_option_value("bt", 0L, "quickfix", OPT_LOCAL);
- set_option_value("bh", 0L, "wipe", OPT_LOCAL);
- RESET_BINDING(curwin);
- curwin->w_p_diff = false;
- set_option_value("fdm", 0L, "manual", OPT_LOCAL);
- }
-
- /* Only set the height when still in the same tab page and there is no
- * window to the side. */
- if (curtab == prevtab
- && curwin->w_width == Columns
- )
- win_setheight(height);
- curwin->w_p_wfh = TRUE; /* set 'winfixheight' */
- if (win_valid(win))
- prevwin = win;
- }
-
- qf_set_title_var(qi);
+ qfl = qf_get_curlist(qi);
+ qf_set_title_var(qfl);
+ // Save the current index here, as updating the quickfix buffer may free
+ // the quickfix list
+ lnum = qfl->qf_index;
// Fill the buffer with the quickfix list.
- qf_fill_buffer(qi, curbuf, NULL);
+ qf_fill_buffer(qfl, curbuf, NULL);
+
+ decr_quickfix_busy();
- curwin->w_cursor.lnum = qi->qf_lists[qi->qf_curlist].qf_index;
+ curwin->w_cursor.lnum = lnum;
curwin->w_cursor.col = 0;
check_cursor();
- update_topline(); /* scroll to show the line */
+ update_topline(); // scroll to show the line
}
// Move the cursor in the quickfix window to "lnum".
@@ -3129,17 +3591,13 @@ static void qf_win_goto(win_T *win, linenr_T lnum)
curbuf = curwin->w_buffer;
}
-// :cbottom/:lbottom command.
+/// :cbottom/:lbottom command.
void ex_cbottom(exarg_T *eap)
{
- qf_info_T *qi = &ql_info;
+ qf_info_T *qi;
- if (eap->cmdidx == CMD_lbottom) {
- qi = GET_LOC_LIST(curwin);
- if (qi == NULL) {
- EMSG(_(e_loclist));
- return;
- }
+ if ((qi = qf_cmd_get_stack(eap, true)) == NULL) {
+ return;
}
win_T *win = qf_find_win(qi);
@@ -3149,38 +3607,32 @@ void ex_cbottom(exarg_T *eap)
}
}
-/*
- * Return the number of the current entry (line number in the quickfix
- * window).
- */
+// Return the number of the current entry (line number in the quickfix
+// window).
linenr_T qf_current_entry(win_T *wp)
{
qf_info_T *qi = &ql_info;
- if (IS_LL_WINDOW(wp))
- /* In the location list window, use the referenced location list */
+ if (IS_LL_WINDOW(wp)) {
+ // In the location list window, use the referenced location list
qi = wp->w_llist_ref;
+ }
- return qi->qf_lists[qi->qf_curlist].qf_index;
+ return qf_get_curlist(qi)->qf_index;
}
-/*
- * Update the cursor position in the quickfix window to the current error.
- * Return TRUE if there is a quickfix window.
- */
-static int
-qf_win_pos_update (
+// Update the cursor position in the quickfix window to the current error.
+// Return TRUE if there is a quickfix window.
+static int qf_win_pos_update(
qf_info_T *qi,
- int old_qf_index /* previous qf_index or zero */
+ int old_qf_index // previous qf_index or zero
)
{
win_T *win;
- int qf_index = qi->qf_lists[qi->qf_curlist].qf_index;
+ int qf_index = qf_get_curlist(qi)->qf_index;
- /*
- * Put the cursor on the current error in the quickfix window, so that
- * it's viewable.
- */
+ // Put the cursor on the current error in the quickfix window, so that
+ // it's viewable.
win = qf_find_win(qi);
if (win != NULL
&& qf_index <= win->w_buffer->b_ml.ml_line_count
@@ -3198,8 +3650,9 @@ qf_win_pos_update (
}
/// Checks whether the given window is displaying the specified
-/// quickfix/location list buffer.
-static int is_qf_win(win_T *win, qf_info_T *qi)
+/// quickfix/location stack.
+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
@@ -3217,9 +3670,10 @@ static int is_qf_win(win_T *win, qf_info_T *qi)
return false;
}
-/// Find a window displaying the quickfix/location list 'qi'
+/// Find a window displaying the quickfix/location stack 'qi'
/// Only searches in the current tabpage.
-static win_T *qf_find_win(qf_info_T *qi)
+static win_T *qf_find_win(const qf_info_T *qi)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
FOR_ALL_WINDOWS_IN_TAB(win, curtab) {
if (is_qf_win(win, qi)) {
@@ -3230,11 +3684,10 @@ static win_T *qf_find_win(qf_info_T *qi)
return NULL;
}
-/*
- * Find a quickfix buffer.
- * Searches in windows opened in all the tabs.
- */
-static buf_T *qf_find_buf(qf_info_T *qi)
+// Find a quickfix buffer.
+// Searches in windows opened in all the tabs.
+static buf_T *qf_find_buf(const qf_info_T *qi)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
FOR_ALL_TAB_WINDOWS(tp, win) {
if (is_qf_win(win, qi)) {
@@ -3253,21 +3706,19 @@ static void qf_update_win_titlevar(qf_info_T *qi)
if ((win = qf_find_win(qi)) != NULL) {
win_T *curwin_save = curwin;
curwin = win;
- qf_set_title_var(qi);
+ qf_set_title_var(qf_get_curlist(qi));
curwin = curwin_save;
}
}
-/*
- * Find the quickfix buffer. If it exists, update the contents.
- */
+// Find the quickfix buffer. If it exists, update the contents.
static void qf_update_buffer(qf_info_T *qi, qfline_T *old_last)
{
buf_T *buf;
win_T *win;
aco_save_T aco;
- /* Check if a buffer for the quickfix list exists. Update it. */
+ // Check if a buffer for the quickfix list exists. Update it.
buf = qf_find_buf(qi);
if (buf != NULL) {
linenr_T old_line_count = buf->b_ml.ml_line_count;
@@ -3279,7 +3730,7 @@ static void qf_update_buffer(qf_info_T *qi, qfline_T *old_last)
qf_update_win_titlevar(qi);
- qf_fill_buffer(qi, buf, old_last);
+ qf_fill_buffer(qf_get_curlist(qi), buf, old_last);
buf_inc_changedtick(buf);
if (old_last == NULL) {
@@ -3297,26 +3748,86 @@ static void qf_update_buffer(qf_info_T *qi, qfline_T *old_last)
}
}
-// Set "w:quickfix_title" if "qi" has a title.
-static void qf_set_title_var(qf_info_T *qi)
+// Add an error line to the quickfix buffer.
+static int qf_buf_add_line(buf_T *buf, linenr_T lnum, const qfline_T *qfp,
+ char_u *dirname)
+ FUNC_ATTR_NONNULL_ALL
{
- if (qi->qf_lists[qi->qf_curlist].qf_title != NULL) {
- set_internal_string_var((char_u *)"w:quickfix_title",
- qi->qf_lists[qi->qf_curlist].qf_title);
+ int len;
+ buf_T *errbuf;
+
+ if (qfp->qf_module != NULL) {
+ STRLCPY(IObuff, qfp->qf_module, IOSIZE - 1);
+ len = (int)STRLEN(IObuff);
+ } else if (qfp->qf_fnum != 0
+ && (errbuf = buflist_findnr(qfp->qf_fnum)) != NULL
+ && errbuf->b_fname != NULL) {
+ if (qfp->qf_type == 1) { // :helpgrep
+ STRLCPY(IObuff, path_tail(errbuf->b_fname), IOSIZE - 1);
+ } else {
+ // shorten the file name if not done already
+ if (errbuf->b_sfname == NULL
+ || path_is_absolute(errbuf->b_sfname)) {
+ if (*dirname == NUL) {
+ os_dirname(dirname, MAXPATHL);
+ }
+ shorten_buf_fname(errbuf, dirname, false);
+ }
+ STRLCPY(IObuff, errbuf->b_fname, IOSIZE - 1);
+ }
+ len = (int)STRLEN(IObuff);
+ } else {
+ len = 0;
+ }
+ if (len < IOSIZE - 1) {
+ IObuff[len++] = '|';
+ }
+ if (qfp->qf_lnum > 0) {
+ snprintf((char *)IObuff + len, (size_t)(IOSIZE - len), "%" PRId64,
+ (int64_t)qfp->qf_lnum);
+ len += (int)STRLEN(IObuff + len);
+
+ if (qfp->qf_col > 0) {
+ snprintf((char *)IObuff + len, (size_t)(IOSIZE - len), " col %d",
+ qfp->qf_col);
+ len += (int)STRLEN(IObuff + len);
+ }
+
+ snprintf((char *)IObuff + len, (size_t)(IOSIZE - len), "%s",
+ (char *)qf_types(qfp->qf_type, qfp->qf_nr));
+ len += (int)STRLEN(IObuff + len);
+ } 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++] = ' ';
}
+
+ // 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);
+
+ if (ml_append_buf(buf, lnum, IObuff,
+ (colnr_T)STRLEN(IObuff) + 1, false) == FAIL) {
+ return FAIL;
+ }
+ return OK;
}
-// Fill current buffer with quickfix errors, replacing any previous contents.
-// curbuf must be the quickfix buffer!
-// If "old_last" is not NULL append the items after this one.
-// When "old_last" is NULL then "buf" must equal "curbuf"! Because ml_delete()
-// is used and autocommands will be triggered.
-static void qf_fill_buffer(qf_info_T *qi, buf_T *buf, qfline_T *old_last)
+/// Fill current buffer with quickfix errors, replacing any previous contents.
+/// curbuf must be the quickfix buffer!
+/// If "old_last" is not NULL append the items after this one.
+/// When "old_last" is NULL then "buf" must equal "curbuf"! Because ml_delete()
+/// is used and autocommands will be triggered.
+static void qf_fill_buffer(qf_list_T *qfl, buf_T *buf, qfline_T *old_last)
+ FUNC_ATTR_NONNULL_ARG(2)
{
linenr_T lnum;
qfline_T *qfp;
- buf_T *errbuf;
- int len;
const bool old_KeyTyped = KeyTyped;
if (old_last == NULL) {
@@ -3331,73 +3842,22 @@ static void qf_fill_buffer(qf_info_T *qi, buf_T *buf, qfline_T *old_last)
}
}
- /* Check if there is anything to display */
- if (qi->qf_curlist < qi->qf_listcount) {
+ // Check if there is anything to display
+ if (qfl != NULL) {
char_u dirname[MAXPATHL];
*dirname = NUL;
// Add one line for each error
- if (old_last == NULL) {
- qfp = qi->qf_lists[qi->qf_curlist].qf_start;
+ if (old_last == NULL || old_last->qf_next == NULL) {
+ qfp = qfl->qf_start;
lnum = 0;
} else {
qfp = old_last->qf_next;
lnum = buf->b_ml.ml_line_count;
}
- while (lnum < qi->qf_lists[qi->qf_curlist].qf_count) {
- if (qfp->qf_module != NULL) {
- STRCPY(IObuff, qfp->qf_module);
- len = (int)STRLEN(IObuff);
- } else if (qfp->qf_fnum != 0
- && (errbuf = buflist_findnr(qfp->qf_fnum)) != NULL
- && errbuf->b_fname != NULL) {
- if (qfp->qf_type == 1) { // :helpgrep
- STRLCPY(IObuff, path_tail(errbuf->b_fname), sizeof(IObuff));
- } else {
- // shorten the file name if not done already
- if (errbuf->b_sfname == NULL
- || path_is_absolute(errbuf->b_sfname)) {
- if (*dirname == NUL) {
- os_dirname(dirname, MAXPATHL);
- }
- shorten_buf_fname(errbuf, dirname, false);
- }
- STRLCPY(IObuff, errbuf->b_fname, sizeof(IObuff));
- }
- len = (int)STRLEN(IObuff);
- } else {
- len = 0;
- }
- IObuff[len++] = '|';
-
- if (qfp->qf_lnum > 0) {
- sprintf((char *)IObuff + len, "%" PRId64, (int64_t)qfp->qf_lnum);
- len += (int)STRLEN(IObuff + len);
-
- if (qfp->qf_col > 0) {
- sprintf((char *)IObuff + len, " col %d", qfp->qf_col);
- len += (int)STRLEN(IObuff + len);
- }
-
- sprintf((char *)IObuff + len, "%s",
- (char *)qf_types(qfp->qf_type, qfp->qf_nr));
- len += (int)STRLEN(IObuff + len);
- } else if (qfp->qf_pattern != NULL) {
- qf_fmt_text(qfp->qf_pattern, IObuff + len, IOSIZE - len);
- len += (int)STRLEN(IObuff + len);
- }
- IObuff[len++] = '|';
- IObuff[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(len > 3 ? skipwhite(qfp->qf_text) : qfp->qf_text,
- IObuff + len, IOSIZE - len);
-
- if (ml_append_buf(buf, lnum, IObuff, (colnr_T)STRLEN(IObuff) + 1, false)
- == FAIL) {
+ while (lnum < qfl->qf_count) {
+ if (qf_buf_add_line(buf, lnum, qfp, dirname) == FAIL) {
break;
}
lnum++;
@@ -3435,13 +3895,13 @@ static void qf_fill_buffer(qf_info_T *qi, buf_T *buf, qfline_T *old_last)
redraw_curbuf_later(NOT_VALID);
}
- /* Restore KeyTyped, setting 'filetype' may reset it. */
+ // Restore KeyTyped, setting 'filetype' may reset it.
KeyTyped = old_KeyTyped;
}
-static void qf_list_changed(qf_info_T *qi, int qf_idx)
+static void qf_list_changed(qf_list_T *qfl)
{
- qi->qf_lists[qf_idx].qf_changedtick++;
+ qfl->qf_changedtick++;
}
/// Return the quickfix/location list number with the given identifier.
@@ -3457,15 +3917,15 @@ static int qf_id2nr(const qf_info_T *const qi, const unsigned qfid)
return INVALID_QFIDX;
}
-// If the current list is not "save_qfid" and we can find the list with that ID
-// then make it the current list.
-// This is used when autocommands may have changed the current list.
-// Returns OK if successfully restored the list. Returns FAIL if the list with
-// the specified identifier (save_qfid) is not found in the stack.
+/// If the current list is not "save_qfid" and we can find the list with that ID
+/// then make it the current list.
+/// This is used when autocommands may have changed the current list.
+/// Returns OK if successfully restored the list. Returns FAIL if the list with
+/// the specified identifier (save_qfid) is not found in the stack.
static int qf_restore_list(qf_info_T *qi, unsigned save_qfid)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
{
- if (qi->qf_lists[qi->qf_curlist].qf_id != save_qfid) {
+ if (qf_get_curlist(qi)->qf_id != save_qfid) {
const int curlist = qf_id2nr(qi, save_qfid);
if (curlist < 0) {
// list is not present
@@ -3484,14 +3944,12 @@ static void qf_jump_first(qf_info_T *qi, unsigned save_qfid, int forceit)
return;
}
// Autocommands might have cleared the list, check for that
- if (!qf_list_empty(qi, qi->qf_curlist)) {
+ if (!qf_list_empty(qf_get_curlist(qi))) {
qf_jump(qi, 0, 0, forceit);
}
}
-/*
- * Return TRUE when using ":vimgrep" for ":grep".
- */
+// Return TRUE when using ":vimgrep" for ":grep".
int grep_internal(cmdidx_T cmdidx)
{
return (cmdidx == CMD_grep
@@ -3502,33 +3960,74 @@ int grep_internal(cmdidx_T cmdidx)
*curbuf->b_p_gp == NUL ? p_gp : curbuf->b_p_gp) == 0;
}
-/*
- * Used for ":make", ":lmake", ":grep", ":lgrep", ":grepadd", and ":lgrepadd"
- */
+// Return the make/grep autocmd name.
+static char_u *make_get_auname(cmdidx_T cmdidx)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ switch (cmdidx) {
+ case CMD_make:
+ return (char_u *)"make";
+ case CMD_lmake:
+ return (char_u *)"lmake";
+ case CMD_grep:
+ return (char_u *)"grep";
+ case CMD_lgrep:
+ return (char_u *)"lgrep";
+ case CMD_grepadd:
+ return (char_u *)"grepadd";
+ case CMD_lgrepadd:
+ return (char_u *)"lgrepadd";
+ default:
+ return NULL;
+ }
+}
+
+// Form the complete command line to invoke 'make'/'grep'. Quote the command
+// using 'shellquote' and append 'shellpipe'. Echo the fully formed command.
+static char *make_get_fullcmd(const char_u *makecmd, const char_u *fname)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET
+{
+ size_t len = STRLEN(p_shq) * 2 + STRLEN(makecmd) + 1;
+ if (*p_sp != NUL) {
+ len += STRLEN(p_sp) + STRLEN(fname) + 3;
+ }
+ char *const cmd = xmalloc(len);
+ snprintf(cmd, len, "%s%s%s", (char *)p_shq, (char *)makecmd, (char *)p_shq);
+
+ // If 'shellpipe' empty: don't redirect to 'errorfile'.
+ if (*p_sp != NUL) {
+ append_redir(cmd, len, (char *)p_sp, (char *)fname);
+ }
+
+ // Display the fully formed command. Output a newline if there's something
+ // else than the :make command that was typed (in which case the cursor is
+ // in column 0).
+ if (msg_col == 0) {
+ msg_didout = false;
+ }
+ msg_start();
+ MSG_PUTS(":!");
+ msg_outtrans((char_u *)cmd); // show what we are doing
+
+ return cmd;
+}
+
+// Used for ":make", ":lmake", ":grep", ":lgrep", ":grepadd", and ":lgrepadd"
void ex_make(exarg_T *eap)
{
char_u *fname;
win_T *wp = NULL;
qf_info_T *qi = &ql_info;
int res;
- char_u *au_name = NULL;
char_u *enc = (*curbuf->b_p_menc != NUL) ? curbuf->b_p_menc : p_menc;
- /* Redirect ":grep" to ":vimgrep" if 'grepprg' is "internal". */
+ // Redirect ":grep" to ":vimgrep" if 'grepprg' is "internal".
if (grep_internal(eap->cmdidx)) {
ex_vimgrep(eap);
return;
}
- switch (eap->cmdidx) {
- case CMD_make: au_name = (char_u *)"make"; break;
- case CMD_lmake: au_name = (char_u *)"lmake"; break;
- case CMD_grep: au_name = (char_u *)"grep"; break;
- case CMD_lgrep: au_name = (char_u *)"lgrep"; break;
- case CMD_grepadd: au_name = (char_u *)"grepadd"; break;
- case CMD_lgrepadd: au_name = (char_u *)"lgrepadd"; break;
- default: break;
- }
+ char_u *const au_name = make_get_auname(eap->cmdidx);
if (au_name != NULL && apply_autocmds(EVENT_QUICKFIXCMDPRE, au_name,
curbuf->b_fname, true, curbuf)) {
if (aborting()) {
@@ -3536,9 +4035,9 @@ void ex_make(exarg_T *eap)
}
}
- if (eap->cmdidx == CMD_lmake || eap->cmdidx == CMD_lgrep
- || eap->cmdidx == CMD_lgrepadd)
+ if (is_loclist_cmd(eap->cmdidx)) {
wp = curwin;
+ }
autowrite_all();
fname = get_mef_name();
@@ -3546,28 +4045,11 @@ void ex_make(exarg_T *eap)
return;
os_remove((char *)fname); // in case it's not unique
- // If 'shellpipe' empty: don't redirect to 'errorfile'.
- const size_t len = (STRLEN(p_shq) * 2 + STRLEN(eap->arg) + 1
- + (*p_sp == NUL
- ? 0
- : STRLEN(p_sp) + STRLEN(fname) + 3));
- char *const cmd = xmalloc(len);
- snprintf(cmd, len, "%s%s%s", (char *)p_shq, (char *)eap->arg,
- (char *)p_shq);
- if (*p_sp != NUL) {
- append_redir(cmd, len, (char *) p_sp, (char *) fname);
- }
- // Output a newline if there's something else than the :make command that
- // was typed (in which case the cursor is in column 0).
- if (msg_col == 0) {
- msg_didout = false;
- }
- msg_start();
- MSG_PUTS(":!");
- msg_outtrans((char_u *)cmd); // show what we are doing
+ char *const cmd = make_get_fullcmd(eap->arg, fname);
do_shell((char_u *)cmd, 0);
+ incr_quickfix_busy();
res = qf_init(wp, fname, (eap->cmdidx != CMD_make
&& eap->cmdidx != CMD_lmake) ? p_gefm : p_efm,
@@ -3580,11 +4062,11 @@ void ex_make(exarg_T *eap)
}
}
if (res >= 0) {
- qf_list_changed(qi, qi->qf_curlist);
+ qf_list_changed(qf_get_curlist(qi));
}
// Remember the current quickfix list identifier, so that we can
// check for autocommands changing the current quickfix list.
- unsigned save_qfid = qi->qf_lists[qi->qf_curlist].qf_id;
+ unsigned save_qfid = qf_get_curlist(qi)->qf_id;
if (au_name != NULL) {
apply_autocmds(EVENT_QUICKFIXCMDPOST, au_name, curbuf->b_fname, true,
curbuf);
@@ -3595,16 +4077,15 @@ void ex_make(exarg_T *eap)
}
cleanup:
+ decr_quickfix_busy();
os_remove((char *)fname);
xfree(fname);
xfree(cmd);
}
-/*
- * Return the name for the errorfile, in allocated memory.
- * Find a new unique name when 'makeef' contains "##".
- * Returns NULL for error.
- */
+// Return the name for the errorfile, in allocated memory.
+// Find a new unique name when 'makeef' contains "##".
+// Returns NULL for error.
static char_u *get_mef_name(void)
{
char_u *p;
@@ -3626,7 +4107,7 @@ static char_u *get_mef_name(void)
if (*p == NUL)
return vim_strsave(p_mef);
- /* Keep trying until the name doesn't exist yet. */
+ // Keep trying until the name doesn't exist yet.
for (;; ) {
if (start == -1) {
start = (int)os_get_pid();
@@ -3652,23 +4133,20 @@ static char_u *get_mef_name(void)
size_t qf_get_size(exarg_T *eap)
FUNC_ATTR_NONNULL_ALL
{
- qf_info_T *qi = &ql_info;
- if (eap->cmdidx == CMD_ldo || eap->cmdidx == CMD_lfdo) {
- // Location list.
- qi = GET_LOC_LIST(curwin);
- if (qi == NULL) {
- return 0;
- }
+ qf_info_T *qi;
+ qf_list_T *qfl;
+
+ if ((qi = qf_cmd_get_stack(eap, false)) == NULL) {
+ return 0;
}
int prev_fnum = 0;
size_t sz = 0;
qfline_T *qfp;
- size_t i;
- assert(qi->qf_lists[qi->qf_curlist].qf_count >= 0);
- for (i = 0, qfp = qi->qf_lists[qi->qf_curlist].qf_start;
- i < (size_t)qi->qf_lists[qi->qf_curlist].qf_count && qfp != NULL;
- i++, qfp = qfp->qf_next) {
+ int i;
+ assert(qf_get_curlist(qi)->qf_count >= 0);
+ qfl = qf_get_curlist(qi);
+ FOR_ALL_QFL_ITEMS(qfl, qfp, i) {
if (!qfp->qf_valid) {
continue;
}
@@ -3691,18 +4169,14 @@ size_t qf_get_size(exarg_T *eap)
size_t qf_get_cur_idx(exarg_T *eap)
FUNC_ATTR_NONNULL_ALL
{
- qf_info_T *qi = &ql_info;
+ qf_info_T *qi;
- if (eap->cmdidx == CMD_ldo || eap->cmdidx == CMD_lfdo) {
- // Location list.
- qi = GET_LOC_LIST(curwin);
- if (qi == NULL) {
- return 0;
- }
+ if ((qi = qf_cmd_get_stack(eap, false)) == NULL) {
+ return 0;
}
- assert(qi->qf_lists[qi->qf_curlist].qf_index >= 0);
- return (size_t)qi->qf_lists[qi->qf_curlist].qf_index;
+ assert(qf_get_curlist(qi)->qf_index >= 0);
+ return (size_t)qf_get_curlist(qi)->qf_index;
}
/// Returns the current index in the quickfix/location list,
@@ -3711,20 +4185,16 @@ size_t qf_get_cur_idx(exarg_T *eap)
int qf_get_cur_valid_idx(exarg_T *eap)
FUNC_ATTR_NONNULL_ALL
{
- qf_info_T *qi = &ql_info;
+ qf_info_T *qi;
- if (eap->cmdidx == CMD_ldo || eap->cmdidx == CMD_lfdo) {
- // Location list.
- qi = GET_LOC_LIST(curwin);
- if (qi == NULL) {
- return 1;
- }
+ if ((qi = qf_cmd_get_stack(eap, false)) == NULL) {
+ return 1;
}
- qf_list_T *qfl = &qi->qf_lists[qi->qf_curlist];
+ qf_list_T *qfl = qf_get_curlist(qi);
// Check if the list has valid errors.
- if (qfl->qf_count <= 0 || qfl->qf_nonevalid) {
+ if (!qf_list_has_valid_entries(qfl)) {
return 1;
}
@@ -3759,24 +4229,20 @@ int qf_get_cur_valid_idx(exarg_T *eap)
/// Used by :cdo, :ldo, :cfdo and :lfdo commands.
/// For :cdo and :ldo, returns the 'n'th valid error entry.
/// For :cfdo and :lfdo, returns the 'n'th valid file entry.
-static size_t qf_get_nth_valid_entry(qf_info_T *qi, size_t n, bool fdo)
+static size_t qf_get_nth_valid_entry(qf_list_T *qfl, size_t n, int fdo)
FUNC_ATTR_NONNULL_ALL
{
- qf_list_T *qfl = &qi->qf_lists[qi->qf_curlist];
-
// Check if the list has valid errors.
- if (qfl->qf_count <= 0 || qfl->qf_nonevalid) {
+ if (!qf_list_has_valid_entries(qfl)) {
return 1;
}
int prev_fnum = 0;
size_t eidx = 0;
- size_t i;
+ int i;
qfline_T *qfp;
assert(qfl->qf_count >= 0);
- for (i = 1, qfp = qfl->qf_start;
- i <= (size_t)qfl->qf_count && qfp != NULL;
- i++, qfp = qfp->qf_next) {
+ FOR_ALL_QFL_ITEMS(qfl, qfp, i) {
if (qfp->qf_valid) {
if (fdo) {
if (qfp->qf_fnum > 0 && qfp->qf_fnum != prev_fnum) {
@@ -3794,41 +4260,39 @@ static size_t qf_get_nth_valid_entry(qf_info_T *qi, size_t n, bool fdo)
}
}
- return i <= (size_t)qfl->qf_count ? i : 1;
+ return i <= qfl->qf_count ? (size_t)i : 1;
}
-/*
- * ":cc", ":crewind", ":cfirst" and ":clast".
- * ":ll", ":lrewind", ":lfirst" and ":llast".
- * ":cdo", ":ldo", ":cfdo" and ":lfdo".
- */
+/// ":cc", ":crewind", ":cfirst" and ":clast".
+/// ":ll", ":lrewind", ":lfirst" and ":llast".
+/// ":cdo", ":ldo", ":cfdo" and ":lfdo".
void ex_cc(exarg_T *eap)
{
- qf_info_T *qi = &ql_info;
+ qf_info_T *qi;
- if (eap->cmdidx == CMD_ll
- || eap->cmdidx == CMD_lrewind
- || eap->cmdidx == CMD_lfirst
- || eap->cmdidx == CMD_llast
- || eap->cmdidx == CMD_ldo
- || eap->cmdidx == CMD_lfdo) {
- qi = GET_LOC_LIST(curwin);
- if (qi == NULL) {
- EMSG(_(e_loclist));
- return;
- }
+ if ((qi = qf_cmd_get_stack(eap, true)) == NULL) {
+ return;
}
int errornr;
if (eap->addr_count > 0) {
errornr = (int)eap->line2;
- } else if (eap->cmdidx == CMD_cc || eap->cmdidx == CMD_ll) {
- errornr = 0;
- } else if (eap->cmdidx == CMD_crewind || eap->cmdidx == CMD_lrewind
- || eap->cmdidx == CMD_cfirst || eap->cmdidx == CMD_lfirst) {
- errornr = 1;
} else {
- errornr = 32767;
+ switch (eap->cmdidx) {
+ case CMD_cc:
+ case CMD_ll:
+ errornr = 0;
+ break;
+ case CMD_crewind:
+ case CMD_lrewind:
+ case CMD_cfirst:
+ case CMD_lfirst:
+ errornr = 1;
+ break;
+ default:
+ errornr = 32767;
+ break;
+ }
}
// For cdo and ldo commands, jump to the nth valid error.
@@ -3842,8 +4306,9 @@ void ex_cc(exarg_T *eap)
} else {
n = 1;
}
- size_t valid_entry = qf_get_nth_valid_entry(qi, n,
- eap->cmdidx == CMD_cfdo || eap->cmdidx == CMD_lfdo);
+ size_t valid_entry = qf_get_nth_valid_entry(
+ qf_get_curlist(qi), n,
+ eap->cmdidx == CMD_cfdo || eap->cmdidx == CMD_lfdo);
assert(valid_entry <= INT_MAX);
errornr = (int)valid_entry;
}
@@ -3851,28 +4316,15 @@ void ex_cc(exarg_T *eap)
qf_jump(qi, 0, errornr, eap->forceit);
}
-/*
- * ":cnext", ":cnfile", ":cNext" and ":cprevious".
- * ":lnext", ":lNext", ":lprevious", ":lnfile", ":lNfile" and ":lpfile".
- * ":cdo", ":ldo", ":cfdo" and ":lfdo".
- */
+/// ":cnext", ":cnfile", ":cNext" and ":cprevious".
+/// ":lnext", ":lNext", ":lprevious", ":lnfile", ":lNfile" and ":lpfile".
+/// ":cdo", ":ldo", ":cfdo" and ":lfdo".
void ex_cnext(exarg_T *eap)
{
- qf_info_T *qi = &ql_info;
+ qf_info_T *qi;
- if (eap->cmdidx == CMD_lnext
- || eap->cmdidx == CMD_lNext
- || eap->cmdidx == CMD_lprevious
- || eap->cmdidx == CMD_lnfile
- || eap->cmdidx == CMD_lNfile
- || eap->cmdidx == CMD_lpfile
- || eap->cmdidx == CMD_ldo
- || eap->cmdidx == CMD_lfdo) {
- qi = GET_LOC_LIST(curwin);
- if (qi == NULL) {
- EMSG(_(e_loclist));
- return;
- }
+ if ((qi = qf_cmd_get_stack(eap, true)) == NULL) {
+ return;
}
int errornr;
@@ -3884,51 +4336,353 @@ void ex_cnext(exarg_T *eap)
errornr = 1;
}
- qf_jump(qi, (eap->cmdidx == CMD_cnext || eap->cmdidx == CMD_lnext
- || eap->cmdidx == CMD_cdo || eap->cmdidx == CMD_ldo)
- ? FORWARD
- : (eap->cmdidx == CMD_cnfile || eap->cmdidx == CMD_lnfile
- || eap->cmdidx == CMD_cfdo || eap->cmdidx == CMD_lfdo)
- ? FORWARD_FILE
- : (eap->cmdidx == CMD_cpfile || eap->cmdidx == CMD_lpfile
- || eap->cmdidx == CMD_cNfile || eap->cmdidx == CMD_lNfile)
- ? BACKWARD_FILE
- : BACKWARD,
- errornr, eap->forceit);
+ // Depending on the command jump to either next or previous entry/file.
+ Direction dir;
+ switch (eap->cmdidx) {
+ case CMD_cprevious:
+ case CMD_lprevious:
+ case CMD_cNext:
+ case CMD_lNext:
+ dir = BACKWARD;
+ break;
+ case CMD_cnfile:
+ case CMD_lnfile:
+ case CMD_cfdo:
+ case CMD_lfdo:
+ dir = FORWARD_FILE;
+ break;
+ case CMD_cpfile:
+ case CMD_lpfile:
+ case CMD_cNfile:
+ case CMD_lNfile:
+ dir = BACKWARD_FILE;
+ break;
+ case CMD_cnext:
+ case CMD_lnext:
+ case CMD_cdo:
+ case CMD_ldo:
+ default:
+ dir = FORWARD;
+ break;
+ }
+
+ qf_jump(qi, dir, errornr, eap->forceit);
+}
+
+/// Find the first entry in the quickfix list 'qfl' from buffer 'bnr'.
+/// The index of the entry is stored in 'errornr'.
+/// Returns NULL if an entry is not found.
+static qfline_T *qf_find_first_entry_in_buf(qf_list_T *qfl,
+ int bnr,
+ int *errornr)
+{
+ qfline_T *qfp = NULL;
+ int idx = 0;
+
+ // Find the first entry in this file
+ FOR_ALL_QFL_ITEMS(qfl, qfp, idx) {
+ if (qfp->qf_fnum == bnr) {
+ break;
+ }
+ }
+
+ *errornr = idx;
+ return qfp;
+}
+
+/// Find the first quickfix entry on the same line as 'entry'. Updates 'errornr'
+/// with the error number for the first entry. Assumes the entries are sorted in
+/// the quickfix list by line number.
+static qfline_T * qf_find_first_entry_on_line(qfline_T *entry, int *errornr)
+{
+ while (!got_int
+ && entry->qf_prev != NULL
+ && entry->qf_fnum == entry->qf_prev->qf_fnum
+ && entry->qf_lnum == entry->qf_prev->qf_lnum) {
+ entry = entry->qf_prev;
+ (*errornr)--;
+ }
+
+ return entry;
+}
+
+/// Find the last quickfix entry on the same line as 'entry'. Updates 'errornr'
+/// with the error number for the last entry. Assumes the entries are sorted in
+/// the quickfix list by line number.
+static qfline_T * qf_find_last_entry_on_line(qfline_T *entry, int *errornr)
+{
+ while (!got_int
+ && entry->qf_next != NULL
+ && entry->qf_fnum == entry->qf_next->qf_fnum
+ && entry->qf_lnum == entry->qf_next->qf_lnum) {
+ entry = entry->qf_next;
+ (*errornr)++;
+ }
+
+ return entry;
+}
+
+/// Find the first quickfix entry below line 'lnum' in buffer 'bnr'.
+/// 'qfp' points to the very first entry in the buffer and 'errornr' is the
+/// index of the very first entry in the quickfix list.
+/// Returns NULL if an entry is not found after 'lnum'.
+static qfline_T *qf_find_entry_on_next_line(int bnr,
+ linenr_T lnum,
+ qfline_T *qfp,
+ int *errornr)
+{
+ if (qfp->qf_lnum > lnum) {
+ // First entry is after line 'lnum'
+ return qfp;
+ }
+
+ // Find the entry just before or at the line 'lnum'
+ while (qfp->qf_next != NULL
+ && qfp->qf_next->qf_fnum == bnr
+ && qfp->qf_next->qf_lnum <= lnum) {
+ qfp = qfp->qf_next;
+ (*errornr)++;
+ }
+
+ if (qfp->qf_next == NULL || qfp->qf_next->qf_fnum != bnr) {
+ // No entries found after 'lnum'
+ return NULL;
+ }
+
+ // Use the entry just after line 'lnum'
+ qfp = qfp->qf_next;
+ (*errornr)++;
+
+ return qfp;
+}
+
+/// Find the first quickfix entry before line 'lnum' in buffer 'bnr'.
+/// 'qfp' points to the very first entry in the buffer and 'errornr' is the
+/// index of the very first entry in the quickfix list.
+/// Returns NULL if an entry is not found before 'lnum'.
+static qfline_T *qf_find_entry_on_prev_line(int bnr,
+ linenr_T lnum,
+ qfline_T *qfp,
+ int *errornr)
+{
+ // Find the entry just before the line 'lnum'
+ while (qfp->qf_next != NULL
+ && qfp->qf_next->qf_fnum == bnr
+ && qfp->qf_next->qf_lnum < lnum) {
+ qfp = qfp->qf_next;
+ (*errornr)++;
+ }
+
+ if (qfp->qf_lnum >= lnum) { // entry is after 'lnum'
+ return NULL;
+ }
+
+ // If multiple entries are on the same line, then use the first entry
+ qfp = qf_find_first_entry_on_line(qfp, errornr);
+
+ return qfp;
+}
+
+/// Find a quickfix entry in 'qfl' closest to line 'lnum' in buffer 'bnr' in
+/// the direction 'dir'.
+static qfline_T *qf_find_closest_entry(qf_list_T *qfl,
+ int bnr,
+ linenr_T lnum,
+ int dir,
+ int *errornr)
+{
+ qfline_T *qfp;
+
+ *errornr = 0;
+
+ // Find the first entry in this file
+ qfp = qf_find_first_entry_in_buf(qfl, bnr, errornr);
+ if (qfp == NULL) {
+ return NULL; // no entry in this file
+ }
+
+ if (dir == FORWARD) {
+ qfp = qf_find_entry_on_next_line(bnr, lnum, qfp, errornr);
+ } else {
+ qfp = qf_find_entry_on_prev_line(bnr, lnum, qfp, errornr);
+ }
+
+ return qfp;
+}
+
+/// Get the nth quickfix entry below the specified entry treating multiple
+/// entries on a single line as one. Searches forward in the list.
+static void qf_get_nth_below_entry(qfline_T *entry,
+ int *errornr,
+ linenr_T n)
+{
+ while (n-- > 0 && !got_int) {
+ // qfline_T *first_entry = entry;
+ int first_errornr = *errornr;
+
+ // Treat all the entries on the same line in this file as one
+ entry = qf_find_last_entry_on_line(entry, errornr);
+
+ if (entry->qf_next == NULL
+ || entry->qf_next->qf_fnum != entry->qf_fnum) {
+ // If multiple entries are on the same line, then use the first
+ // entry
+ // entry = first_entry;
+ *errornr = first_errornr;
+ break;
+ }
+
+ entry = entry->qf_next;
+ (*errornr)++;
+ }
+}
+
+/// Get the nth quickfix entry above the specified entry treating multiple
+/// entries on a single line as one. Searches backwards in the list.
+static void qf_get_nth_above_entry(qfline_T *entry,
+ int *errornr,
+ linenr_T n)
+{
+ while (n-- > 0 && !got_int) {
+ if (entry->qf_prev == NULL
+ || entry->qf_prev->qf_fnum != entry->qf_fnum) {
+ break;
+ }
+
+ entry = entry->qf_prev;
+ (*errornr)--;
+
+ // If multiple entries are on the same line, then use the first entry
+ entry = qf_find_first_entry_on_line(entry, errornr);
+ }
}
-/*
- * ":cfile"/":cgetfile"/":caddfile" commands.
- * ":lfile"/":lgetfile"/":laddfile" commands.
- */
+/// Find the n'th quickfix entry adjacent to line 'lnum' in buffer 'bnr' in the
+/// specified direction.
+/// Returns the error number in the quickfix list or 0 if an entry is not found.
+static int qf_find_nth_adj_entry(qf_list_T *qfl,
+ int bnr,
+ linenr_T lnum,
+ linenr_T n,
+ int dir)
+{
+ qfline_T *adj_entry;
+ int errornr;
+
+ // Find an entry closest to the specified line
+ adj_entry = qf_find_closest_entry(qfl, bnr, lnum, dir, &errornr);
+ if (adj_entry == NULL) {
+ return 0;
+ }
+
+ if (--n > 0) {
+ // Go to the n'th entry in the current buffer
+ if (dir == FORWARD) {
+ qf_get_nth_below_entry(adj_entry, &errornr, n);
+ } else {
+ qf_get_nth_above_entry(adj_entry, &errornr, n);
+ }
+ }
+
+ return errornr;
+}
+
+/// Jump to a quickfix entry in the current file nearest to the current line.
+/// ":cabove", ":cbelow", ":labove" and ":lbelow" commands
+void ex_cbelow(exarg_T *eap)
+{
+ qf_info_T *qi;
+ qf_list_T *qfl;
+ int dir;
+ int buf_has_flag;
+ int errornr = 0;
+
+ if (eap->addr_count > 0 && eap->line2 <= 0) {
+ EMSG(_(e_invrange));
+ return;
+ }
+
+ // Check whether the current buffer has any quickfix entries
+ if (eap->cmdidx == CMD_cabove || eap->cmdidx == CMD_cbelow) {
+ buf_has_flag = BUF_HAS_QF_ENTRY;
+ } else {
+ buf_has_flag = BUF_HAS_LL_ENTRY;
+ }
+ if (!(curbuf->b_has_qf_entry & buf_has_flag)) {
+ EMSG(_(e_quickfix));
+ return;
+ }
+
+ if ((qi = qf_cmd_get_stack(eap, true)) == NULL) {
+ return;
+ }
+
+ qfl = qf_get_curlist(qi);
+ // check if the list has valid errors
+ if (!qf_list_has_valid_entries(qfl)) {
+ EMSG(_(e_quickfix));
+ return;
+ }
+
+ if (eap->cmdidx == CMD_cbelow || eap->cmdidx == CMD_lbelow) {
+ dir = FORWARD;
+ } else {
+ dir = BACKWARD;
+ }
+
+ errornr = qf_find_nth_adj_entry(qfl, curbuf->b_fnum, curwin->w_cursor.lnum,
+ eap->addr_count > 0 ? eap->line2 : 0, dir);
+
+ if (errornr > 0) {
+ qf_jump(qi, 0, errornr, false);
+ } else {
+ EMSG(_(e_no_more_items));
+ }
+}
+
+
+/// Return the autocmd name for the :cfile Ex commands
+static char_u * cfile_get_auname(cmdidx_T cmdidx)
+{
+ switch (cmdidx) {
+ case CMD_cfile: return (char_u *)"cfile";
+ case CMD_cgetfile: return (char_u *)"cgetfile";
+ case CMD_caddfile: return (char_u *)"caddfile";
+ case CMD_lfile: return (char_u *)"lfile";
+ case CMD_lgetfile: return (char_u *)"lgetfile";
+ case CMD_laddfile: return (char_u *)"laddfile";
+ default: return NULL;
+ }
+}
+
+
+// ":cfile"/":cgetfile"/":caddfile" commands.
+// ":lfile"/":lgetfile"/":laddfile" commands.
void ex_cfile(exarg_T *eap)
{
win_T *wp = NULL;
qf_info_T *qi = &ql_info;
char_u *au_name = NULL;
- switch (eap->cmdidx) {
- case CMD_cfile: au_name = (char_u *)"cfile"; break;
- case CMD_cgetfile: au_name = (char_u *)"cgetfile"; break;
- case CMD_caddfile: au_name = (char_u *)"caddfile"; break;
- case CMD_lfile: au_name = (char_u *)"lfile"; break;
- case CMD_lgetfile: au_name = (char_u *)"lgetfile"; break;
- case CMD_laddfile: au_name = (char_u *)"laddfile"; break;
- default: break;
+ au_name = cfile_get_auname(eap->cmdidx);
+ if (au_name != NULL
+ && apply_autocmds(EVENT_QUICKFIXCMDPRE, au_name, NULL, false, curbuf)) {
+ if (aborting()) {
+ return;
+ }
}
- if (au_name != NULL)
- apply_autocmds(EVENT_QUICKFIXCMDPRE, au_name, NULL, FALSE, curbuf);
- if (*eap->arg != NUL)
+ if (*eap->arg != NUL) {
set_string_option_direct((char_u *)"ef", -1, eap->arg, OPT_FREE, 0);
+ }
char_u *enc = (*curbuf->b_p_menc != NUL) ? curbuf->b_p_menc : p_menc;
- if (eap->cmdidx == CMD_lfile
- || eap->cmdidx == CMD_lgetfile
- || eap->cmdidx == CMD_laddfile) {
+ if (is_loclist_cmd(eap->cmdidx)) {
wp = curwin;
}
+ incr_quickfix_busy();
+
// This function is used by the :cfile, :cgetfile and :caddfile
// commands.
// :cfile always creates a new quickfix list and jumps to the
@@ -3943,13 +4697,14 @@ void ex_cfile(exarg_T *eap)
if (wp != NULL) {
qi = GET_LOC_LIST(wp);
if (qi == NULL) {
+ decr_quickfix_busy();
return;
}
}
if (res >= 0) {
- qf_list_changed(qi, qi->qf_curlist);
+ qf_list_changed(qf_get_curlist(qi));
}
- unsigned save_qfid = qi->qf_lists[qi->qf_curlist].qf_id;
+ unsigned save_qfid = qf_get_curlist(qi)->qf_id;
if (au_name != NULL) {
apply_autocmds(EVENT_QUICKFIXCMDPOST, au_name, NULL, false, curbuf);
}
@@ -3960,6 +4715,8 @@ void ex_cfile(exarg_T *eap)
// display the first error
qf_jump_first(qi, save_qfid, eap->forceit);
}
+
+ decr_quickfix_busy();
}
/// Return the vimgrep autocmd name.
@@ -4022,10 +4779,10 @@ static void vgr_display_fname(char_u *fname)
static buf_T *vgr_load_dummy_buf(char_u *fname, char_u *dirname_start,
char_u *dirname_now)
{
- char_u *save_ei = NULL;
-
// Don't do Filetype autocommands to avoid loading syntax and
// indent scripts, a great speed improvement.
+ char_u *save_ei = au_event_disable(",Filetype");
+
long save_mls = p_mls;
p_mls = 0;
@@ -4080,8 +4837,7 @@ static bool vgr_match_buflines(qf_info_T *qi, char_u *fname, buf_T *buf,
// Pass the buffer number so that it gets used even for a
// dummy buffer, unless duplicate_name is set, then the
// buffer will be wiped out below.
- if (qf_add_entry(qi,
- qi->qf_curlist,
+ if (qf_add_entry(qf_get_curlist(qi),
NULL, // dir
fname,
NULL,
@@ -4094,8 +4850,8 @@ static bool vgr_match_buflines(qf_info_T *qi, char_u *fname, buf_T *buf,
NULL, // search pattern
0, // nr
0, // type
- true // valid
- ) == FAIL) {
+ true) // valid
+ == QF_FAIL) {
got_int = true;
break;
}
@@ -4142,12 +4898,10 @@ static void vgr_jump_to_match(qf_info_T *qi, int forceit, int *redraw_for_dummy,
}
}
-/*
- * ":vimgrep {pattern} file(s)"
- * ":vimgrepadd {pattern} file(s)"
- * ":lvimgrep {pattern} file(s)"
- * ":lvimgrepadd {pattern} file(s)"
- */
+// ":vimgrep {pattern} file(s)"
+// ":vimgrepadd {pattern} file(s)"
+// ":lvimgrep {pattern} file(s)"
+// ":lvimgrepadd {pattern} file(s)"
void ex_vimgrep(exarg_T *eap)
{
regmmatch_T regmatch;
@@ -4157,7 +4911,7 @@ void ex_vimgrep(exarg_T *eap)
char_u *s;
char_u *p;
int fi;
- qf_info_T *qi = &ql_info;
+ qf_list_T *qfl;
win_T *wp = NULL;
buf_T *buf;
int duplicate_name = FALSE;
@@ -4182,20 +4936,14 @@ void ex_vimgrep(exarg_T *eap)
}
}
- if (eap->cmdidx == CMD_lgrep
- || eap->cmdidx == CMD_lvimgrep
- || eap->cmdidx == CMD_lgrepadd
- || eap->cmdidx == CMD_lvimgrepadd) {
- qi = ll_get_or_alloc_list(curwin);
- wp = curwin;
- }
+ qf_info_T *qi = qf_cmd_get_or_alloc_stack(eap, &wp);
if (eap->addr_count > 0)
tomatch = eap->line2;
else
tomatch = MAXLNUM;
- /* Get the search pattern: either white-separated or enclosed in // */
+ // Get the search pattern: either white-separated or enclosed in //
regmatch.regprog = NULL;
char_u *title = vim_strsave(qf_cmdtitle(*eap->cmdlinep));
p = skip_vimgrep_pat(eap->arg, &s, &flags);
@@ -4217,14 +4965,15 @@ void ex_vimgrep(exarg_T *eap)
if ((eap->cmdidx != CMD_grepadd && eap->cmdidx != CMD_lgrepadd
&& eap->cmdidx != CMD_vimgrepadd && eap->cmdidx != CMD_lvimgrepadd)
- || qi->qf_curlist == qi->qf_listcount) {
+ || qf_stack_empty(qi)) {
// make place for a new list
qf_new_list(qi, title);
}
- /* parse the list of arguments */
- if (get_arglist_exp(p, &fcount, &fnames, true) == FAIL)
+ // parse the list of arguments
+ if (get_arglist_exp(p, &fcount, &fnames, true) == FAIL) {
goto theend;
+ }
if (fcount == 0) {
EMSG(_(e_nomatch));
goto theend;
@@ -4233,27 +4982,29 @@ void ex_vimgrep(exarg_T *eap)
dirname_start = xmalloc(MAXPATHL);
dirname_now = xmalloc(MAXPATHL);
- /* Remember the current directory, because a BufRead autocommand that does
- * ":lcd %:p:h" changes the meaning of short path names. */
+ // Remember the current directory, because a BufRead autocommand that does
+ // ":lcd %:p:h" changes the meaning of short path names.
os_dirname(dirname_start, MAXPATHL);
+ incr_quickfix_busy();
+
// Remember the current quickfix list identifier, so that we can check for
// autocommands changing the current quickfix list.
- unsigned save_qfid = qi->qf_lists[qi->qf_curlist].qf_id;
+ unsigned save_qfid = qf_get_curlist(qi)->qf_id;
seconds = (time_t)0;
for (fi = 0; fi < fcount && !got_int && tomatch > 0; fi++) {
fname = path_try_shorten_fname(fnames[fi]);
if (time(NULL) > seconds) {
- /* Display the file name every second or so, show the user we are
- * working on it. */
+ // Display the file name every second or so, show the user we are
+ // working on it.
seconds = time(NULL);
vgr_display_fname(fname);
}
buf = buflist_findname_exp(fnames[fi]);
if (buf == NULL || buf->b_ml.ml_mfp == NULL) {
- /* Remember that a buffer with this name already exists. */
+ // Remember that a buffer with this name already exists.
duplicate_name = (buf != NULL);
using_dummy = TRUE;
redraw_for_dummy = TRUE;
@@ -4268,9 +5019,10 @@ void ex_vimgrep(exarg_T *eap)
// buffer above, autocommands might have changed the quickfix list.
if (!vgr_qflist_valid(wp, qi, save_qfid, *eap->cmdlinep)) {
FreeWild(fcount, fnames);
+ decr_quickfix_busy();
goto theend;
}
- save_qfid = qi->qf_lists[qi->qf_curlist].qf_id;
+ save_qfid = qf_get_curlist(qi)->qf_id;
if (buf == NULL) {
if (!got_int)
@@ -4285,20 +5037,20 @@ void ex_vimgrep(exarg_T *eap)
if (found_match && first_match_buf == NULL)
first_match_buf = buf;
if (duplicate_name) {
- /* Never keep a dummy buffer if there is another buffer
- * with the same name. */
+ // Never keep a dummy buffer if there is another buffer
+ // with the same name.
wipe_dummy_buffer(buf, dirname_start);
buf = NULL;
} else if (!cmdmod.hide
- || buf->b_p_bh[0] == 'u' /* "unload" */
- || buf->b_p_bh[0] == 'w' /* "wipe" */
- || buf->b_p_bh[0] == 'd') { /* "delete" */
- /* When no match was found we don't need to remember the
- * buffer, wipe it out. If there was a match and it
- * wasn't the first one or we won't jump there: only
- * unload the buffer.
- * Ignore 'hidden' here, because it may lead to having too
- * many swap files. */
+ || buf->b_p_bh[0] == 'u' // "unload"
+ || buf->b_p_bh[0] == 'w' // "wipe"
+ || buf->b_p_bh[0] == 'd') { // "delete"
+ // When no match was found we don't need to remember the
+ // buffer, wipe it out. If there was a match and it
+ // wasn't the first one or we won't jump there: only
+ // unload the buffer.
+ // Ignore 'hidden' here, because it may lead to having too
+ // many swap files.
if (!found_match) {
wipe_dummy_buffer(buf, dirname_start);
buf = NULL;
@@ -4322,10 +5074,10 @@ void ex_vimgrep(exarg_T *eap)
target_dir = vim_strsave(dirname_now);
}
- /* The buffer is still loaded, the Filetype autocommands
- * need to be done now, in that buffer. And the modelines
- * need to be done (again). But not the window-local
- * options! */
+ // The buffer is still loaded, the Filetype autocommands
+ // need to be done now, in that buffer. And the modelines
+ // need to be done (again). But not the window-local
+ // options!
aucmd_prepbuf(&aco, buf);
apply_autocmds(EVENT_FILETYPE, buf->b_p_ft,
buf->b_fname, TRUE, buf);
@@ -4338,10 +5090,11 @@ void ex_vimgrep(exarg_T *eap)
FreeWild(fcount, fnames);
- qi->qf_lists[qi->qf_curlist].qf_nonevalid = FALSE;
- qi->qf_lists[qi->qf_curlist].qf_ptr = qi->qf_lists[qi->qf_curlist].qf_start;
- qi->qf_lists[qi->qf_curlist].qf_index = 1;
- qf_list_changed(qi, qi->qf_curlist);
+ qfl = qf_get_curlist(qi);
+ qfl->qf_nonevalid = false;
+ qfl->qf_ptr = qfl->qf_start;
+ qfl->qf_index = 1;
+ qf_list_changed(qfl);
qf_update_buffer(qi, NULL);
@@ -4351,16 +5104,14 @@ void ex_vimgrep(exarg_T *eap)
// The QuickFixCmdPost autocmd may free the quickfix list. Check the list
// is still valid.
- if (!qflist_valid(wp, save_qfid)) {
- goto theend;
- }
-
- if (qf_restore_list(qi, save_qfid) == FAIL) {
+ if (!qflist_valid(wp, save_qfid)
+ || qf_restore_list(qi, save_qfid) == FAIL) {
+ decr_quickfix_busy();
goto theend;
}
- /* Jump to first match. */
- if (qi->qf_lists[qi->qf_curlist].qf_count > 0) {
+ // Jump to first match.
+ if (!qf_list_empty(qf_get_curlist(qi))) {
if ((flags & VGR_NOJUMP) == 0) {
vgr_jump_to_match(qi, eap->forceit, &redraw_for_dummy, first_match_buf,
target_dir);
@@ -4368,8 +5119,10 @@ void ex_vimgrep(exarg_T *eap)
} else
EMSG2(_(e_nomatch2), s);
- /* If we loaded a dummy buffer into the current window, the autocommands
- * may have messed up things, need to redraw and recompute folds. */
+ decr_quickfix_busy();
+
+ // If we loaded a dummy buffer into the current window, the autocommands
+ // may have messed up things, need to redraw and recompute folds.
if (redraw_for_dummy) {
foldUpdateAll(curwin);
}
@@ -4382,18 +5135,17 @@ theend:
vim_regfree(regmatch.regprog);
}
-/*
- * Restore current working directory to "dirname_start" if they differ, taking
- * into account whether it is set locally or globally.
- */
+// Restore current working directory to "dirname_start" if they differ, taking
+// into account whether it is set locally or globally.
static void restore_start_dir(char_u *dirname_start)
+ FUNC_ATTR_NONNULL_ALL
{
char_u *dirname_now = xmalloc(MAXPATHL);
os_dirname(dirname_now, MAXPATHL);
if (STRCMP(dirname_start, dirname_now) != 0) {
- /* If the directory has changed, change it back by building up an
- * appropriate ex command and executing it. */
+ // If the directory has changed, change it back by building up an
+ // appropriate ex command and executing it.
exarg_T ea = {
.arg = dirname_start,
.cmdidx = (curwin->w_localdir == NULL) ? CMD_cd : CMD_lcd,
@@ -4403,23 +5155,21 @@ static void restore_start_dir(char_u *dirname_start)
xfree(dirname_now);
}
-/*
- * Load file "fname" into a dummy buffer and return the buffer pointer,
- * placing the directory resulting from the buffer load into the
- * "resulting_dir" pointer. "resulting_dir" must be allocated by the caller
- * prior to calling this function. Restores directory to "dirname_start" prior
- * to returning, if autocmds or the 'autochdir' option have changed it.
- *
- * If creating the dummy buffer does not fail, must call unload_dummy_buffer()
- * or wipe_dummy_buffer() later!
- *
- * Returns NULL if it fails.
- */
+// Load file "fname" into a dummy buffer and return the buffer pointer,
+// placing the directory resulting from the buffer load into the
+// "resulting_dir" pointer. "resulting_dir" must be allocated by the caller
+// prior to calling this function. Restores directory to "dirname_start" prior
+// to returning, if autocmds or the 'autochdir' option have changed it.
+//
+// If creating the dummy buffer does not fail, must call unload_dummy_buffer()
+// or wipe_dummy_buffer() later!
+//
+// Returns NULL if it fails.
static buf_T *
load_dummy_buffer (
char_u *fname,
- char_u *dirname_start, /* in: old directory */
- char_u *resulting_dir /* out: new directory */
+ char_u *dirname_start, // in: old directory
+ char_u *resulting_dir // out: new directory
)
{
buf_T *newbuf;
@@ -4436,24 +5186,24 @@ load_dummy_buffer (
}
set_bufref(&newbufref, newbuf);
- /* Init the options. */
+ // Init the options.
buf_copy_options(newbuf, BCO_ENTER | BCO_NOHELP);
- /* need to open the memfile before putting the buffer in a window */
+ // need to open the memfile before putting the buffer in a window
if (ml_open(newbuf) == OK) {
// Make sure this buffer isn't wiped out by autocommands.
newbuf->b_locked++;
// set curwin/curbuf to buf and save a few things
aucmd_prepbuf(&aco, newbuf);
- /* Need to set the filename for autocommands. */
- (void)setfname(curbuf, fname, NULL, FALSE);
+ // Need to set the filename for autocommands.
+ (void)setfname(curbuf, fname, NULL, false);
- /* Create swap file now to avoid the ATTENTION message. */
- check_need_swap(TRUE);
+ // Create swap file now to avoid the ATTENTION message.
+ check_need_swap(true);
- /* Remove the "dummy" flag, otherwise autocommands may not
- * work. */
+ // Remove the "dummy" flag, otherwise autocommands may not
+ // work.
curbuf->b_flags &= ~BF_DUMMY;
newbuf_to_wipe.br_buf = NULL;
@@ -4486,11 +5236,9 @@ load_dummy_buffer (
newbuf->b_flags |= BF_DUMMY;
}
- /*
- * When autocommands/'autochdir' option changed directory: go back.
- * Let the caller know what the resulting dir was first, in case it is
- * important.
- */
+ // When autocommands/'autochdir' option changed directory: go back.
+ // Let the caller know what the resulting dir was first, in case it is
+ // important.
os_dirname(resulting_dir, MAXPATHL);
restore_start_dir(dirname_start);
@@ -4504,55 +5252,119 @@ load_dummy_buffer (
return newbuf;
}
-/*
- * Wipe out the dummy buffer that load_dummy_buffer() created. Restores
- * directory to "dirname_start" prior to returning, if autocmds or the
- * 'autochdir' option have changed it.
- */
+// Wipe out the dummy buffer that load_dummy_buffer() created. Restores
+// directory to "dirname_start" prior to returning, if autocmds or the
+// 'autochdir' option have changed it.
static void wipe_dummy_buffer(buf_T *buf, char_u *dirname_start)
+ FUNC_ATTR_NONNULL_ALL
{
- if (curbuf != buf) { /* safety check */
+ // If any autocommand opened a window on the dummy buffer, close that
+ // window. If we can't close them all then give up.
+ while (buf->b_nwindows > 0) {
+ bool did_one = false;
+
+ if (firstwin->w_next != NULL) {
+ for (win_T *wp = firstwin; wp != NULL; wp = wp->w_next) {
+ if (wp->w_buffer == buf) {
+ if (win_close(wp, false) == OK) {
+ did_one = true;
+ }
+ break;
+ }
+ }
+ }
+ if (!did_one) {
+ return;
+ }
+ }
+
+ if (curbuf != buf && buf->b_nwindows == 0) { // safety check
cleanup_T cs;
- /* Reset the error/interrupt/exception state here so that aborting()
- * returns FALSE when wiping out the buffer. Otherwise it doesn't
- * work when got_int is set. */
+ // Reset the error/interrupt/exception state here so that aborting()
+ // returns FALSE when wiping out the buffer. Otherwise it doesn't
+ // work when got_int is set.
enter_cleanup(&cs);
wipe_buffer(buf, FALSE);
- /* Restore the error/interrupt/exception state if not discarded by a
- * new aborting error, interrupt, or uncaught exception. */
+ // Restore the error/interrupt/exception state if not discarded by a
+ // new aborting error, interrupt, or uncaught exception.
leave_cleanup(&cs);
- /* When autocommands/'autochdir' option changed directory: go back. */
+ // When autocommands/'autochdir' option changed directory: go back.
restore_start_dir(dirname_start);
}
}
-/*
- * Unload the dummy buffer that load_dummy_buffer() created. Restores
- * directory to "dirname_start" prior to returning, if autocmds or the
- * 'autochdir' option have changed it.
- */
+// Unload the dummy buffer that load_dummy_buffer() created. Restores
+// directory to "dirname_start" prior to returning, if autocmds or the
+// 'autochdir' option have changed it.
static void unload_dummy_buffer(buf_T *buf, char_u *dirname_start)
{
- if (curbuf != buf) { /* safety check */
- close_buffer(NULL, buf, DOBUF_UNLOAD, FALSE);
+ if (curbuf != buf) { // safety check
+ close_buffer(NULL, buf, DOBUF_UNLOAD, false);
- /* When autocommands/'autochdir' option changed directory: go back. */
+ // When autocommands/'autochdir' option changed directory: go back.
restore_start_dir(dirname_start);
}
}
+/// Copy the specified quickfix entry items into a new dict and appened the dict
+/// to 'list'. Returns OK on success.
+static int get_qfline_items(qfline_T *qfp, list_T *list)
+{
+ char_u buf[2];
+ int bufnum;
+
+ // Handle entries with a non-existing buffer number.
+ bufnum = qfp->qf_fnum;
+ if (bufnum != 0 && (buflist_findnr(bufnum) == NULL)) {
+ bufnum = 0;
+ }
+
+ dict_T *const dict = tv_dict_alloc();
+ tv_list_append_dict(list, dict);
+
+ 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("col"), (varnumber_T)qfp->qf_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))
+ == FAIL)
+ || (tv_dict_add_str(
+ dict, S_LEN("text"),
+ (qfp->qf_text == NULL ? "" : (const char *)qfp->qf_text))
+ == 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* fail only if key already exist, but this is a newly
+ // allocated dictionary which is thus guaranteed to have no existing keys.
+ assert(false);
+ }
+
+ return OK;
+}
+
/// Add each quickfix error to list "list" as a dictionary.
/// If qf_idx is -1, use the current list. Otherwise, use the specified list.
-int get_errorlist(const qf_info_T *qi_arg, win_T *wp, int qf_idx, list_T *list)
+int get_errorlist(qf_info_T *qi_arg, win_T *wp, int qf_idx, list_T *list)
{
- const qf_info_T *qi = qi_arg;
- char_u buf[2];
+ qf_info_T *qi = qi_arg;
+ qf_list_T *qfl;
qfline_T *qfp;
int i;
- int bufnum;
if (qi == NULL) {
qi = &ql_info;
@@ -4568,56 +5380,19 @@ int get_errorlist(const qf_info_T *qi_arg, win_T *wp, int qf_idx, list_T *list)
qf_idx = qi->qf_curlist;
}
- if (qf_idx >= qi->qf_listcount
- || qi->qf_lists[qf_idx].qf_count == 0) {
+ if (qf_idx >= qi->qf_listcount) {
return FAIL;
}
- qfp = qi->qf_lists[qf_idx].qf_start;
- for (i = 1; !got_int && i <= qi->qf_lists[qf_idx].qf_count; i++) {
- // Handle entries with a non-existing buffer number.
- bufnum = qfp->qf_fnum;
- if (bufnum != 0 && (buflist_findnr(bufnum) == NULL))
- bufnum = 0;
-
- dict_T *const dict = tv_dict_alloc();
- tv_list_append_dict(list, dict);
-
- 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("col"), (varnumber_T)qfp->qf_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)) == FAIL
- || tv_dict_add_str(dict, S_LEN("text"),
- (qfp->qf_text == NULL
- ? ""
- : (const char *)qfp->qf_text)) == 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* fail only if key already exist, but this is a newly
- // allocated dictionary which is thus guaranteed to have no existing keys.
- assert(false);
- }
+ qfl = qf_get_list(qi, qf_idx);
+ if (qf_list_empty(qfl)) {
+ return FAIL;
+ }
- qfp = qfp->qf_next;
- if (qfp == NULL) {
- break;
- }
+ FOR_ALL_QFL_ITEMS(qfl, qfp, i) {
+ get_qfline_items(qfp, list);
}
+
return OK;
}
@@ -4633,7 +5408,8 @@ enum {
QF_GETLIST_IDX = 0x40,
QF_GETLIST_SIZE = 0x80,
QF_GETLIST_TICK = 0x100,
- QF_GETLIST_ALL = 0x1FF
+ QF_GETLIST_FILEWINID = 0x200,
+ QF_GETLIST_ALL = 0x3FF,
};
/// Parse text from 'di' and return the quickfix list items.
@@ -4657,12 +5433,12 @@ static int qf_get_list_from_lines(dict_T *what, dictitem_T *di, dict_T *retdict)
}
list_T *l = tv_list_alloc(kListLenMayKnow);
- qf_info_T *const qi = ll_new_list();
+ 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) {
(void)get_errorlist(qi, NULL, 0, l);
- qf_free(qi, 0);
+ qf_free(&qi->qf_lists[0]);
}
xfree(qi);
@@ -4689,12 +5465,17 @@ static int qf_winid(qf_info_T *qi)
}
/// Convert the keys in 'what' to quickfix list property flags.
-static int qf_getprop_keys2flags(dict_T *what)
+static int qf_getprop_keys2flags(const dict_T *what, bool loclist)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
int flags = QF_GETLIST_NONE;
if (tv_dict_find(what, S_LEN("all")) != NULL) {
flags |= QF_GETLIST_ALL;
+ if (!loclist) {
+ // File window ID is applicable only to location list windows
+ flags &= ~QF_GETLIST_FILEWINID;
+ }
}
if (tv_dict_find(what, S_LEN("title")) != NULL) {
flags |= QF_GETLIST_TITLE;
@@ -4723,6 +5504,9 @@ static int qf_getprop_keys2flags(dict_T *what)
if (tv_dict_find(what, S_LEN("changedtick")) != NULL) {
flags |= QF_GETLIST_TICK;
}
+ if (loclist && tv_dict_find(what, S_LEN("filewinid")) != NULL) {
+ flags |= QF_GETLIST_FILEWINID;
+ }
return flags;
}
@@ -4776,7 +5560,10 @@ static int qf_getprop_qfidx(qf_info_T *qi, dict_T *what)
}
/// Return default values for quickfix list properties in retdict.
-static int qf_getprop_defaults(qf_info_T *qi, int flags, dict_T *retdict)
+static int qf_getprop_defaults(qf_info_T *qi,
+ int flags,
+ int locstack,
+ dict_T *retdict)
{
int status = OK;
@@ -4808,15 +5595,37 @@ static int qf_getprop_defaults(qf_info_T *qi, int flags, dict_T *retdict)
if ((status == OK) && (flags & QF_GETLIST_TICK)) {
status = tv_dict_add_nr(retdict, S_LEN("changedtick"), 0);
}
+ if ((status == OK) && locstack && (flags & QF_GETLIST_FILEWINID)) {
+ status = tv_dict_add_nr(retdict, S_LEN("filewinid"), 0);
+ }
return status;
}
/// Return the quickfix list title as 'title' in retdict
-static int qf_getprop_title(qf_info_T *qi, int qf_idx, dict_T *retdict)
+static int qf_getprop_title(qf_list_T *qfl, dict_T *retdict)
{
return tv_dict_add_str(retdict, S_LEN("title"),
- (const char *)qi->qf_lists[qf_idx].qf_title);
+ (const char *)qfl->qf_title);
+}
+
+// Returns the identifier of the window used to display files from a location
+// list. If there is no associated window, then returns 0. Useful only when
+// called from a location list window.
+static int qf_getprop_filewinid(const win_T *wp, const qf_info_T *qi,
+ dict_T *retdict)
+ FUNC_ATTR_NONNULL_ARG(3)
+{
+ handle_T winid = 0;
+
+ if (wp != NULL && IS_LL_WINDOW(wp)) {
+ win_T *ll_wp = qf_find_win_with_loclist(qi);
+ if (ll_wp != NULL) {
+ winid = ll_wp->handle;
+ }
+ }
+
+ return tv_dict_add_nr(retdict, S_LEN("filewinid"), winid);
}
/// Return the quickfix list items/entries as 'items' in retdict
@@ -4830,13 +5639,13 @@ static int qf_getprop_items(qf_info_T *qi, int qf_idx, dict_T *retdict)
}
/// Return the quickfix list context (if any) as 'context' in retdict.
-static int qf_getprop_ctx(qf_info_T *qi, int qf_idx, dict_T *retdict)
+static int qf_getprop_ctx(qf_list_T *qfl, dict_T *retdict)
{
int status;
- if (qi->qf_lists[qf_idx].qf_ctx != NULL) {
+ if (qfl->qf_ctx != NULL) {
dictitem_T *di = tv_dict_item_alloc_len(S_LEN("context"));
- tv_copy(qi->qf_lists[qf_idx].qf_ctx, &di->di_tv);
+ tv_copy(qfl->qf_ctx, &di->di_tv);
status = tv_dict_add(retdict, di);
if (status == FAIL) {
tv_dict_item_free(di);
@@ -4848,15 +5657,15 @@ static int qf_getprop_ctx(qf_info_T *qi, int qf_idx, dict_T *retdict)
return status;
}
-/// Return the quickfix list index as 'idx' in retdict
-static int qf_getprop_idx(qf_info_T *qi, int qf_idx, dict_T *retdict)
+/// Return the current quickfix list index as 'idx' in retdict
+static int qf_getprop_idx(qf_list_T *qfl, dict_T *retdict)
{
- int idx = qi->qf_lists[qf_idx].qf_index;
- if (qi->qf_lists[qf_idx].qf_count == 0) {
- // For empty lists, qf_index is set to 1
- idx = 0;
+ int curidx = qfl->qf_index;
+ if (qf_list_empty(qfl)) {
+ // For empty lists, current index is set to 0
+ curidx = 0;
}
- return tv_dict_add_nr(retdict, S_LEN("idx"), idx);
+ return tv_dict_add_nr(retdict, S_LEN("idx"), curidx);
}
/// Return quickfix/location list details (title) as a dictionary.
@@ -4865,9 +5674,10 @@ static int qf_getprop_idx(qf_info_T *qi, int qf_idx, dict_T *retdict)
int qf_get_properties(win_T *wp, dict_T *what, dict_T *retdict)
{
qf_info_T *qi = &ql_info;
+ qf_list_T *qfl;
dictitem_T *di = NULL;
int status = OK;
- int qf_idx;
+ int qf_idx = INVALID_QFIDX;
if ((di = tv_dict_find(what, S_LEN("lines"))) != NULL) {
return qf_get_list_from_lines(what, di, retdict);
@@ -4877,19 +5687,21 @@ int qf_get_properties(win_T *wp, dict_T *what, dict_T *retdict)
qi = GET_LOC_LIST(wp);
}
- int flags = qf_getprop_keys2flags(what);
+ const int flags = qf_getprop_keys2flags(what, wp != NULL);
- if (qi != NULL && qi->qf_listcount != 0) {
+ if (!qf_stack_empty(qi)) {
qf_idx = qf_getprop_qfidx(qi, what);
}
// List is not present or is empty
- if (qi == NULL || qi->qf_listcount == 0 || qf_idx == INVALID_QFIDX) {
- return qf_getprop_defaults(qi, flags, retdict);
+ if (qf_stack_empty(qi) || qf_idx == INVALID_QFIDX) {
+ return qf_getprop_defaults(qi, flags, wp != NULL, retdict);
}
+ qfl = qf_get_list(qi, qf_idx);
+
if (flags & QF_GETLIST_TITLE) {
- status = qf_getprop_title(qi, qf_idx, retdict);
+ status = qf_getprop_title(qfl, retdict);
}
if ((status == OK) && (flags & QF_GETLIST_NR)) {
status = tv_dict_add_nr(retdict, S_LEN("nr"), qf_idx + 1);
@@ -4901,21 +5713,101 @@ int qf_get_properties(win_T *wp, dict_T *what, dict_T *retdict)
status = qf_getprop_items(qi, qf_idx, retdict);
}
if ((status == OK) && (flags & QF_GETLIST_CONTEXT)) {
- status = qf_getprop_ctx(qi, qf_idx, retdict);
+ status = qf_getprop_ctx(qfl, retdict);
}
if ((status == OK) && (flags & QF_GETLIST_ID)) {
- status = tv_dict_add_nr(retdict, S_LEN("id"), qi->qf_lists[qf_idx].qf_id);
+ status = tv_dict_add_nr(retdict, S_LEN("id"), qfl->qf_id);
}
if ((status == OK) && (flags & QF_GETLIST_IDX)) {
- status = qf_getprop_idx(qi, qf_idx, retdict);
+ status = qf_getprop_idx(qfl, retdict);
}
if ((status == OK) && (flags & QF_GETLIST_SIZE)) {
status = tv_dict_add_nr(retdict, S_LEN("size"),
- qi->qf_lists[qf_idx].qf_count);
+ qfl->qf_count);
}
if ((status == OK) && (flags & QF_GETLIST_TICK)) {
status = tv_dict_add_nr(retdict, S_LEN("changedtick"),
- qi->qf_lists[qf_idx].qf_changedtick);
+ qfl->qf_changedtick);
+ }
+ if ((status == OK) && (wp != NULL) && (flags & QF_GETLIST_FILEWINID)) {
+ status = qf_getprop_filewinid(wp, qi, retdict);
+ }
+
+ return status;
+}
+
+/// 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)
+ FUNC_ATTR_NONNULL_ALL
+{
+ static bool did_bufnr_emsg;
+
+ if (first_entry) {
+ did_bufnr_emsg = false;
+ }
+
+ char *const filename = tv_dict_get_string(d, "filename", true);
+ char *const module = tv_dict_get_string(d, "module", true);
+ int bufnum = (int)tv_dict_get_number(d, "bufnr");
+ const long lnum = (long)tv_dict_get_number(d, "lnum");
+ const int col = (int)tv_dict_get_number(d, "col");
+ const char_u vcol = (char_u)tv_dict_get_number(d, "vcol");
+ const int nr = (int)tv_dict_get_number(d, "nr");
+ const char *const type = tv_dict_get_string(d, "type", false);
+ char *const pattern = tv_dict_get_string(d, "pattern", true);
+ char *text = tv_dict_get_string(d, "text", true);
+ if (text == NULL) {
+ text = xcalloc(1, 1);
+ }
+ bool valid = true;
+ if ((filename == NULL && bufnum == 0)
+ || (lnum == 0 && pattern == NULL)) {
+ valid = false;
+ }
+
+ // Mark entries with non-existing buffer number as not valid. Give the
+ // error message only once.
+ if (bufnum != 0 && (buflist_findnr(bufnum) == NULL)) {
+ if (!did_bufnr_emsg) {
+ did_bufnr_emsg = true;
+ EMSGN(_("E92: Buffer %" PRId64 " not found"), bufnum);
+ }
+ valid = false;
+ bufnum = 0;
+ }
+
+ // If the 'valid' field is present it overrules the detected value.
+ if (tv_dict_find(d, "valid", -1) != NULL) {
+ valid = tv_dict_get_number(d, "valid");
+ }
+
+ const int status = qf_add_entry(qfl,
+ NULL, // dir
+ (char_u *)filename,
+ (char_u *)module,
+ bufnum,
+ (char_u *)text,
+ lnum,
+ col,
+ vcol, // vis_col
+ (char_u *)pattern, // search pattern
+ nr,
+ (char_u)(type == NULL ? NUL : *type),
+ valid);
+
+ xfree(filename);
+ xfree(module);
+ xfree(pattern);
+ xfree(text);
+
+ if (valid) {
+ *valid_entry = true;
}
return status;
@@ -4926,21 +5818,22 @@ int qf_get_properties(win_T *wp, dict_T *what, dict_T *retdict)
static int qf_add_entries(qf_info_T *qi, int qf_idx, list_T *list,
char_u *title, int action)
{
- dict_T *d;
+ qf_list_T *qfl = qf_get_list(qi, qf_idx);
qfline_T *old_last = NULL;
int retval = OK;
- bool did_bufnr_emsg = false;
+ bool valid_entry = false;
if (action == ' ' || qf_idx == qi->qf_listcount) {
// make place for a new list
qf_new_list(qi, title);
qf_idx = qi->qf_curlist;
- } else if (action == 'a' && qi->qf_lists[qf_idx].qf_count > 0) {
+ qfl = qf_get_list(qi, qf_idx);
+ } else if (action == 'a' && !qf_list_empty(qfl)) {
// Adding to existing list, use last entry.
- old_last = qi->qf_lists[qf_idx].qf_last;
+ old_last = qfl->qf_last;
} else if (action == 'r') {
- qf_free_items(qi, qf_idx);
- qf_store_title(qi, qf_idx, title);
+ qf_free_items(qfl);
+ qf_store_title(qfl, title);
}
TV_LIST_ITER_CONST(list, li, {
@@ -4948,83 +5841,35 @@ static int qf_add_entries(qf_info_T *qi, int qf_idx, list_T *list,
continue; // Skip non-dict items.
}
- d = TV_LIST_ITEM_TV(li)->vval.v_dict;
+ const dict_T *const d = TV_LIST_ITEM_TV(li)->vval.v_dict;
if (d == NULL) {
continue;
}
- char *const filename = tv_dict_get_string(d, "filename", true);
- char *const module = tv_dict_get_string(d, "module", true);
- int bufnum = (int)tv_dict_get_number(d, "bufnr");
- long lnum = (long)tv_dict_get_number(d, "lnum");
- int col = (int)tv_dict_get_number(d, "col");
- char_u vcol = (char_u)tv_dict_get_number(d, "vcol");
- int nr = (int)tv_dict_get_number(d, "nr");
- const char *type_str = tv_dict_get_string(d, "type", false);
- const char_u type = (char_u)(uint8_t)(type_str == NULL ? NUL : *type_str);
- char *const pattern = tv_dict_get_string(d, "pattern", true);
- char *text = tv_dict_get_string(d, "text", true);
- if (text == NULL) {
- text = xcalloc(1, 1);
- }
- bool valid = true;
- if ((filename == NULL && bufnum == 0) || (lnum == 0 && pattern == NULL)) {
- valid = false;
- }
-
- /* Mark entries with non-existing buffer number as not valid. Give the
- * error message only once. */
- if (bufnum != 0 && (buflist_findnr(bufnum) == NULL)) {
- if (!did_bufnr_emsg) {
- did_bufnr_emsg = TRUE;
- EMSGN(_("E92: Buffer %" PRId64 " not found"), bufnum);
- }
- valid = false;
- bufnum = 0;
- }
-
- // If the 'valid' field is present it overrules the detected value.
- if (tv_dict_find(d, "valid", -1) != NULL) {
- valid = (int)tv_dict_get_number(d, "valid");
- }
-
- int status = qf_add_entry(qi,
- qf_idx,
- NULL, // dir
- (char_u *)filename,
- (char_u *)module,
- bufnum,
- (char_u *)text,
- lnum,
- col,
- vcol, // vis_col
- (char_u *)pattern, // search pattern
- nr,
- type,
- valid);
-
- xfree(filename);
- xfree(module);
- xfree(pattern);
- xfree(text);
-
- if (status == FAIL) {
- retval = FAIL;
+ retval = qf_add_entry_from_dict(qfl, d, li == tv_list_first(list),
+ &valid_entry);
+ if (retval == QF_FAIL) {
break;
}
});
- if (qi->qf_lists[qf_idx].qf_index == 0) {
- // no valid entry
- qi->qf_lists[qf_idx].qf_nonevalid = true;
- } else {
- qi->qf_lists[qf_idx].qf_nonevalid = false;
+ // Check if any valid error entries are added to the list.
+ if (valid_entry) {
+ qfl->qf_nonevalid = false;
+ } else if (qfl->qf_index == 0) {
+ qfl->qf_nonevalid = true;
}
+
+ // If not appending to the list, set the current error to the first entry
if (action != 'a') {
- qi->qf_lists[qf_idx].qf_ptr = qi->qf_lists[qf_idx].qf_start;
- if (qi->qf_lists[qf_idx].qf_count > 0) {
- qi->qf_lists[qf_idx].qf_index = 1;
- }
+ qfl->qf_ptr = qfl->qf_start;
+ }
+
+ // Update the current error index if not appending to the list or if the
+ // list was empty before and it is not empty now.
+ if ((action != 'a' || qfl->qf_index == 0)
+ && !qf_list_empty(qfl)) {
+ qfl->qf_index = 1;
}
// Don't update the cursor in quickfix window when appending entries
@@ -5033,7 +5878,7 @@ static int qf_add_entries(qf_info_T *qi, int qf_idx, list_T *list,
return retval;
}
-// Get the quickfix list index from 'nr' or 'id'
+/// Get the quickfix list index from 'nr' or 'id'
static int qf_setprop_get_qfidx(
const qf_info_T *qi,
const dict_T *what,
@@ -5057,7 +5902,7 @@ static int qf_setprop_get_qfidx(
// non-available list and add the new list at the end of the
// stack.
*newlist = true;
- qf_idx = qi->qf_listcount > 0 ? qi->qf_listcount - 1 : 0;
+ qf_idx = qf_stack_empty(qi) ? 0 : qi->qf_listcount - 1;
} else if (qf_idx < 0 || qf_idx >= qi->qf_listcount) {
return INVALID_QFIDX;
} else if (action != ' ') {
@@ -5065,7 +5910,7 @@ static int qf_setprop_get_qfidx(
}
} else if (di->di_tv.v_type == VAR_STRING
&& strequal((const char *)di->di_tv.vval.v_string, "$")) {
- if (qi->qf_listcount > 0) {
+ if (!qf_stack_empty(qi)) {
qf_idx = qi->qf_listcount - 1;
} else if (*newlist) {
qf_idx = 0;
@@ -5093,13 +5938,13 @@ static int qf_setprop_title(qf_info_T *qi, int qf_idx, const dict_T *what,
const dictitem_T *di)
FUNC_ATTR_NONNULL_ALL
{
+ qf_list_T *qfl = qf_get_list(qi, qf_idx);
if (di->di_tv.v_type != VAR_STRING) {
return FAIL;
}
- xfree(qi->qf_lists[qf_idx].qf_title);
- qi->qf_lists[qf_idx].qf_title =
- (char_u *)tv_dict_get_string(what, "title", true);
+ xfree(qfl->qf_title);
+ qfl->qf_title = (char_u *)tv_dict_get_string(what, "title", true);
if (qf_idx == qi->qf_curlist) {
qf_update_win_titlevar(qi);
}
@@ -5153,7 +5998,7 @@ static int qf_setprop_items_from_lines(
}
if (action == 'r') {
- qf_free_items(qi, qf_idx);
+ 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) {
@@ -5164,27 +6009,28 @@ static int qf_setprop_items_from_lines(
}
// Set quickfix list context.
-static int qf_setprop_context(qf_info_T *qi, int qf_idx, dictitem_T *di)
+static int qf_setprop_context(qf_list_T *qfl, dictitem_T *di)
FUNC_ATTR_NONNULL_ALL
{
- tv_free(qi->qf_lists[qf_idx].qf_ctx);
+ tv_free(qfl->qf_ctx);
typval_T *ctx = xcalloc(1, sizeof(typval_T));
tv_copy(&di->di_tv, ctx);
- qi->qf_lists[qf_idx].qf_ctx = ctx;
+ qfl->qf_ctx = ctx;
return OK;
}
-// Set quickfix/location list properties (title, items, context).
-// Also used to add items from parsing a list of lines.
-// Used by the setqflist() and setloclist() VimL functions.
+/// Set quickfix/location list properties (title, items, context).
+/// Also used to add items from parsing a list of lines.
+/// Used by the setqflist() and setloclist() Vim script functions.
static int qf_set_properties(qf_info_T *qi, const dict_T *what, int action,
char_u *title)
FUNC_ATTR_NONNULL_ALL
{
+ qf_list_T *qfl;
dictitem_T *di;
int retval = FAIL;
- bool newlist = action == ' ' || qi->qf_curlist == qi->qf_listcount;
+ bool newlist = action == ' ' || qf_stack_empty(qi);
int qf_idx = qf_setprop_get_qfidx(qi, what, action, &newlist);
if (qf_idx == INVALID_QFIDX) { // List not found
return FAIL;
@@ -5196,6 +6042,7 @@ static int qf_set_properties(qf_info_T *qi, const dict_T *what, int action,
qf_idx = qi->qf_curlist;
}
+ qfl = qf_get_list(qi, qf_idx);
if ((di = tv_dict_find(what, S_LEN("title"))) != NULL) {
retval = qf_setprop_title(qi, qf_idx, what, di);
}
@@ -5206,17 +6053,18 @@ static int qf_set_properties(qf_info_T *qi, const dict_T *what, int action,
retval = qf_setprop_items_from_lines(qi, qf_idx, what, di, action);
}
if ((di = tv_dict_find(what, S_LEN("context"))) != NULL) {
- retval = qf_setprop_context(qi, qf_idx, di);
+ retval = qf_setprop_context(qfl, di);
}
if (retval == OK) {
- qf_list_changed(qi, qf_idx);
+ qf_list_changed(qfl);
}
return retval;
}
-// Find the non-location list window with the specified location list.
+/// Find the non-location list window with the specified location list stack in
+/// the current tabpage.
static win_T * find_win_with_ll(qf_info_T *qi)
{
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
@@ -5237,7 +6085,7 @@ static void qf_free_stack(win_T *wp, qf_info_T *qi)
if (qfwin != NULL) {
// If the quickfix/location list window is open, then clear it
if (qi->qf_curlist < qi->qf_listcount) {
- qf_free(qi, qi->qf_curlist);
+ qf_free(qf_get_curlist(qi));
}
qf_update_buffer(qi, NULL);
}
@@ -5261,15 +6109,14 @@ static void qf_free_stack(win_T *wp, qf_info_T *qi)
} else if (IS_LL_WINDOW(orig_wp)) {
// If the location list window is open, then create a new empty location
// list
- qf_info_T *new_ll = ll_new_list();
+ qf_info_T *new_ll = qf_alloc_stack(QFLT_LOCATION);
// first free the list reference in the location list window
ll_free_all(&orig_wp->w_llist_ref);
orig_wp->w_llist_ref = new_ll;
if (llwin != NULL) {
- llwin->w_llist = new_ll;
- new_ll->qf_refcount++;
+ win_set_loclist(wp, new_ll);
}
}
}
@@ -5290,15 +6137,22 @@ int set_errorlist(win_T *wp, list_T *list, int action, char_u *title,
if (action == 'f') {
// Free the entire quickfix or location list stack
qf_free_stack(wp, qi);
- } else if (what != NULL) {
+ return OK;
+ }
+
+ incr_quickfix_busy();
+
+ if (what != NULL) {
retval = qf_set_properties(qi, what, action, title);
} else {
retval = qf_add_entries(qi, qi->qf_curlist, list, title, action);
if (retval == OK) {
- qf_list_changed(qi, qi->qf_curlist);
+ qf_list_changed(qf_get_curlist(qi));
}
}
+ decr_quickfix_busy();
+
return retval;
}
@@ -5349,45 +6203,79 @@ bool set_ref_in_quickfix(int copyID)
return abort;
}
-/*
- * ":[range]cbuffer [bufnr]" command.
- * ":[range]caddbuffer [bufnr]" command.
- * ":[range]cgetbuffer [bufnr]" command.
- * ":[range]lbuffer [bufnr]" command.
- * ":[range]laddbuffer [bufnr]" command.
- * ":[range]lgetbuffer [bufnr]" command.
- */
-void ex_cbuffer(exarg_T *eap)
+/// Return the autocmd name for the :cbuffer Ex commands
+static char_u * cbuffer_get_auname(cmdidx_T cmdidx)
+{
+ switch (cmdidx) {
+ case CMD_cbuffer: return (char_u *)"cbuffer";
+ case CMD_cgetbuffer: return (char_u *)"cgetbuffer";
+ case CMD_caddbuffer: return (char_u *)"caddbuffer";
+ case CMD_lbuffer: return (char_u *)"lbuffer";
+ case CMD_lgetbuffer: return (char_u *)"lgetbuffer";
+ case CMD_laddbuffer: return (char_u *)"laddbuffer";
+ default: return NULL;
+ }
+}
+
+/// Process and validate the arguments passed to the :cbuffer, :caddbuffer,
+/// :cgetbuffer, :lbuffer, :laddbuffer, :lgetbuffer Ex commands.
+static int cbuffer_process_args(exarg_T *eap,
+ buf_T **bufp,
+ linenr_T *line1,
+ linenr_T *line2)
{
buf_T *buf = NULL;
- qf_info_T *qi = &ql_info;
- const char *au_name = NULL;
- win_T *wp = NULL;
- switch (eap->cmdidx) {
- case CMD_cbuffer:
- au_name = "cbuffer";
- break;
- case CMD_cgetbuffer:
- au_name = "cgetbuffer";
- break;
- case CMD_caddbuffer:
- au_name = "caddbuffer";
- break;
- case CMD_lbuffer:
- au_name = "lbuffer";
- break;
- case CMD_lgetbuffer:
- au_name = "lgetbuffer";
- break;
- case CMD_laddbuffer:
- au_name = "laddbuffer";
- break;
- default:
- break;
+ if (*eap->arg == NUL)
+ buf = curbuf;
+ else if (*skipwhite(skipdigits(eap->arg)) == NUL)
+ buf = buflist_findnr(atoi((char *)eap->arg));
+
+ if (buf == NULL) {
+ EMSG(_(e_invarg));
+ return FAIL;
}
- if (au_name != NULL && apply_autocmds(EVENT_QUICKFIXCMDPRE, (char_u *)au_name,
+ if (buf->b_ml.ml_mfp == NULL) {
+ EMSG(_("E681: Buffer is not loaded"));
+ return FAIL;
+ }
+
+ if (eap->addr_count == 0) {
+ eap->line1 = 1;
+ eap->line2 = buf->b_ml.ml_line_count;
+ }
+
+ if (eap->line1 < 1 || eap->line1 > buf->b_ml.ml_line_count
+ || eap->line2 < 1 || eap->line2 > buf->b_ml.ml_line_count) {
+ EMSG(_(e_invrange));
+ return FAIL;
+ }
+
+ *line1 = eap->line1;
+ *line2 = eap->line2;
+ *bufp = buf;
+
+ return OK;
+}
+
+// ":[range]cbuffer [bufnr]" command.
+// ":[range]caddbuffer [bufnr]" command.
+// ":[range]cgetbuffer [bufnr]" command.
+// ":[range]lbuffer [bufnr]" command.
+// ":[range]laddbuffer [bufnr]" command.
+// ":[range]lgetbuffer [bufnr]" command.
+void ex_cbuffer(exarg_T *eap)
+{
+ buf_T *buf = NULL;
+ char_u *au_name = NULL;
+ win_T *wp = NULL;
+ char_u *qf_title;
+ linenr_T line1;
+ linenr_T line2;
+
+ au_name = cbuffer_get_auname(eap->cmdidx);
+ if (au_name != NULL && apply_autocmds(EVENT_QUICKFIXCMDPRE, au_name,
curbuf->b_fname, true, curbuf)) {
if (aborting()) {
return;
@@ -5395,134 +6283,112 @@ void ex_cbuffer(exarg_T *eap)
}
// Must come after autocommands.
- if (eap->cmdidx == CMD_lbuffer
- || eap->cmdidx == CMD_lgetbuffer
- || eap->cmdidx == CMD_laddbuffer) {
- qi = ll_get_or_alloc_list(curwin);
- wp = curwin;
+ qf_info_T *qi = qf_cmd_get_or_alloc_stack(eap, &wp);
+
+ if (cbuffer_process_args(eap, &buf, &line1, &line2) == FAIL) {
+ return;
}
- if (*eap->arg == NUL)
- buf = curbuf;
- else if (*skipwhite(skipdigits(eap->arg)) == NUL)
- buf = buflist_findnr(atoi((char *)eap->arg));
- if (buf == NULL)
- EMSG(_(e_invarg));
- else if (buf->b_ml.ml_mfp == NULL)
- EMSG(_("E681: Buffer is not loaded"));
- else {
- if (eap->addr_count == 0) {
- eap->line1 = 1;
- eap->line2 = buf->b_ml.ml_line_count;
- }
- if (eap->line1 < 1 || eap->line1 > buf->b_ml.ml_line_count
- || eap->line2 < 1 || eap->line2 > buf->b_ml.ml_line_count) {
- EMSG(_(e_invrange));
- } else {
- char_u *qf_title = qf_cmdtitle(*eap->cmdlinep);
+ qf_title = qf_cmdtitle(*eap->cmdlinep);
- if (buf->b_sfname) {
- vim_snprintf((char *)IObuff, IOSIZE, "%s (%s)",
- (char *)qf_title, (char *)buf->b_sfname);
- qf_title = IObuff;
- }
+ if (buf->b_sfname) {
+ vim_snprintf((char *)IObuff, IOSIZE, "%s (%s)",
+ (char *)qf_title, (char *)buf->b_sfname);
+ qf_title = IObuff;
+ }
- int res = qf_init_ext(qi, qi->qf_curlist, NULL, buf, NULL, p_efm,
- (eap->cmdidx != CMD_caddbuffer
- && eap->cmdidx != CMD_laddbuffer),
- eap->line1, eap->line2, qf_title, NULL);
- if (res >= 0) {
- qf_list_changed(qi, qi->qf_curlist);
- }
- // Remember the current quickfix list identifier, so that we can
- // check for autocommands changing the current quickfix list.
- unsigned save_qfid = qi->qf_lists[qi->qf_curlist].qf_id;
- if (au_name != NULL) {
- const buf_T *const curbuf_old = curbuf;
- apply_autocmds(EVENT_QUICKFIXCMDPOST, (char_u *)au_name,
- curbuf->b_fname, true, curbuf);
- if (curbuf != curbuf_old) {
- // Autocommands changed buffer, don't jump now, "qi" may
- // be invalid.
- res = 0;
- }
- }
- // Jump to the first error for new list and if autocmds didn't
- // free the list.
- if (res > 0 && (eap->cmdidx == CMD_cbuffer || eap->cmdidx == CMD_lbuffer)
- && qflist_valid(wp, save_qfid)) {
- // display the first error
- qf_jump_first(qi, save_qfid, eap->forceit);
- }
+ incr_quickfix_busy();
+
+ int res = qf_init_ext(qi, qi->qf_curlist, NULL, buf, NULL, p_efm,
+ (eap->cmdidx != CMD_caddbuffer
+ && eap->cmdidx != CMD_laddbuffer),
+ eap->line1, eap->line2, qf_title, NULL);
+ if (qf_stack_empty(qi)) {
+ decr_quickfix_busy();
+ return;
+ }
+ if (res >= 0) {
+ qf_list_changed(qf_get_curlist(qi));
+ }
+ // Remember the current quickfix list identifier, so that we can
+ // check for autocommands changing the current quickfix list.
+ unsigned save_qfid = qf_get_curlist(qi)->qf_id;
+ if (au_name != NULL) {
+ const buf_T *const curbuf_old = curbuf;
+ apply_autocmds(EVENT_QUICKFIXCMDPOST, au_name,
+ curbuf->b_fname, true, curbuf);
+ if (curbuf != curbuf_old) {
+ // Autocommands changed buffer, don't jump now, "qi" may
+ // be invalid.
+ res = 0;
}
}
+ // Jump to the first error for new list and if autocmds didn't
+ // free the list.
+ if (res > 0 && (eap->cmdidx == CMD_cbuffer || eap->cmdidx == CMD_lbuffer)
+ && qflist_valid(wp, save_qfid)) {
+ // display the first error
+ qf_jump_first(qi, save_qfid, eap->forceit);
+ }
+
+ decr_quickfix_busy();
+}
+
+/// Return the autocmd name for the :cexpr Ex commands.
+static char_u * cexpr_get_auname(cmdidx_T cmdidx)
+{
+ switch (cmdidx) {
+ case CMD_cexpr: return (char_u *)"cexpr";
+ case CMD_cgetexpr: return (char_u *)"cgetexpr";
+ case CMD_caddexpr: return (char_u *)"caddexpr";
+ case CMD_lexpr: return (char_u *)"lexpr";
+ case CMD_lgetexpr: return (char_u *)"lgetexpr";
+ case CMD_laddexpr: return (char_u *)"laddexpr";
+ default: return NULL;
+ }
}
-/*
- * ":cexpr {expr}", ":cgetexpr {expr}", ":caddexpr {expr}" command.
- * ":lexpr {expr}", ":lgetexpr {expr}", ":laddexpr {expr}" command.
- */
+/// ":cexpr {expr}", ":cgetexpr {expr}", ":caddexpr {expr}" command.
+/// ":lexpr {expr}", ":lgetexpr {expr}", ":laddexpr {expr}" command.
void ex_cexpr(exarg_T *eap)
{
- qf_info_T *qi = &ql_info;
- const char *au_name = NULL;
+ char_u *au_name = NULL;
win_T *wp = NULL;
- switch (eap->cmdidx) {
- case CMD_cexpr:
- au_name = "cexpr";
- break;
- case CMD_cgetexpr:
- au_name = "cgetexpr";
- break;
- case CMD_caddexpr:
- au_name = "caddexpr";
- break;
- case CMD_lexpr:
- au_name = "lexpr";
- break;
- case CMD_lgetexpr:
- au_name = "lgetexpr";
- break;
- case CMD_laddexpr:
- au_name = "laddexpr";
- break;
- default:
- break;
- }
- if (au_name != NULL && apply_autocmds(EVENT_QUICKFIXCMDPRE, (char_u *)au_name,
+ au_name = cexpr_get_auname(eap->cmdidx);
+ if (au_name != NULL && apply_autocmds(EVENT_QUICKFIXCMDPRE, au_name,
curbuf->b_fname, true, curbuf)) {
if (aborting()) {
return;
}
}
- if (eap->cmdidx == CMD_lexpr
- || eap->cmdidx == CMD_lgetexpr
- || eap->cmdidx == CMD_laddexpr) {
- qi = ll_get_or_alloc_list(curwin);
- wp = curwin;
- }
+ qf_info_T *qi = qf_cmd_get_or_alloc_stack(eap, &wp);
- /* Evaluate the expression. When the result is a string or a list we can
- * use it to fill the errorlist. */
+ // 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, NULL, true) != FAIL) {
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,
(eap->cmdidx != CMD_caddexpr
&& eap->cmdidx != CMD_laddexpr),
(linenr_T)0, (linenr_T)0,
qf_cmdtitle(*eap->cmdlinep), NULL);
+ if (qf_stack_empty(qi)) {
+ decr_quickfix_busy();
+ goto cleanup;
+ }
if (res >= 0) {
- qf_list_changed(qi, qi->qf_curlist);
+ qf_list_changed(qf_get_curlist(qi));
}
// Remember the current quickfix list identifier, so that we can
// check for autocommands changing the current quickfix list.
- unsigned save_qfid = qi->qf_lists[qi->qf_curlist].qf_id;
+ unsigned save_qfid = qf_get_curlist(qi)->qf_id;
if (au_name != NULL) {
- apply_autocmds(EVENT_QUICKFIXCMDPOST, (char_u *)au_name,
+ apply_autocmds(EVENT_QUICKFIXCMDPOST, au_name,
curbuf->b_fname, true, curbuf);
}
// Jump to the first error for a new list and if autocmds didn't
@@ -5533,33 +6399,155 @@ void ex_cexpr(exarg_T *eap)
// display the first error
qf_jump_first(qi, save_qfid, eap->forceit);
}
+ decr_quickfix_busy();
} else {
EMSG(_("E777: String or List expected"));
}
+cleanup:
tv_clear(&tv);
}
}
-/*
- * ":helpgrep {pattern}"
- */
-void ex_helpgrep(exarg_T *eap)
+// Get the location list for ":lhelpgrep"
+static qf_info_T *hgr_get_ll(bool *new_ll)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET
+{
+ win_T *wp;
+ qf_info_T *qi;
+
+ // If the current window is a help window, then use it
+ if (bt_help(curwin->w_buffer)) {
+ wp = curwin;
+ } else {
+ // Find an existing help window
+ wp = qf_find_help_win();
+ }
+
+ if (wp == NULL) { // Help window not found
+ qi = NULL;
+ } else {
+ qi = wp->w_llist;
+ }
+ if (qi == NULL) {
+ // Allocate a new location list for help text matches
+ qi = qf_alloc_stack(QFLT_LOCATION);
+ *new_ll = true;
+ }
+
+ return qi;
+}
+
+// Search for a pattern in a help file.
+static void hgr_search_file(
+ qf_info_T *qi,
+ char_u *fname,
+ regmatch_T *p_regmatch)
+ FUNC_ATTR_NONNULL_ARG(1, 3)
+{
+ FILE *const fd = os_fopen((char *)fname, "r");
+ if (fd == NULL) {
+ return;
+ }
+
+ long lnum = 1;
+ while (!vim_fgets(IObuff, IOSIZE, fd) && !got_int) {
+ char_u *line = IObuff;
+
+ if (vim_regexec(p_regmatch, line, (colnr_T)0)) {
+ int l = (int)STRLEN(line);
+
+ // remove trailing CR, LF, spaces, etc.
+ while (l > 0 && line[l - 1] <= ' ') {
+ line[--l] = NUL;
+ }
+
+ if (qf_add_entry(qf_get_curlist(qi),
+ NULL, // dir
+ fname,
+ NULL,
+ 0,
+ line,
+ lnum,
+ (int)(p_regmatch->startp[0] - line) + 1, // col
+ false, // vis_col
+ NULL, // search pattern
+ 0, // nr
+ 1, // type
+ true) // valid
+ == QF_FAIL) {
+ got_int = true;
+ if (line != IObuff) {
+ xfree(line);
+ }
+ break;
+ }
+ }
+ if (line != IObuff) {
+ xfree(line);
+ }
+ lnum++;
+ line_breakcheck();
+ }
+ fclose(fd);
+}
+
+// Search for a pattern in all the help files in the doc directory under
+// the given directory.
+static void hgr_search_files_in_dir(
+ qf_info_T *qi,
+ char_u *dirname,
+ regmatch_T *p_regmatch,
+ const char_u *lang)
+ FUNC_ATTR_NONNULL_ARG(1, 2, 3)
{
- regmatch_T regmatch;
- char_u *save_cpo;
- char_u *p;
int fcount;
- char_u **fnames;
- FILE *fd;
- int fi;
- long lnum;
- char_u *lang;
- qf_info_T *qi = &ql_info;
- int new_qi = FALSE;
- char_u *au_name = NULL;
+ char_u **fnames;
+
+ // Find all "*.txt" and "*.??x" files in the "doc" directory.
+ add_pathsep((char *)dirname);
+ STRCAT(dirname, "doc/*.\\(txt\\|??x\\)"); // NOLINT
+ if (gen_expand_wildcards(1, &dirname, &fcount,
+ &fnames, EW_FILE|EW_SILENT) == OK
+ && fcount > 0) {
+ for (int fi = 0; fi < fcount && !got_int; fi++) {
+ // Skip files for a different language.
+ if (lang != NULL
+ && STRNICMP(lang, fnames[fi] + STRLEN(fnames[fi]) - 3, 2) != 0
+ && !(STRNICMP(lang, "en", 2) == 0
+ && STRNICMP("txt", fnames[fi] + STRLEN(fnames[fi]) - 3, 3)
+ == 0)) {
+ continue;
+ }
+
+ hgr_search_file(qi, fnames[fi], p_regmatch);
+ }
+ FreeWild(fcount, fnames);
+ }
+}
+
+// Search for a pattern in all the help files in the 'runtimepath'
+// and add the matches to a quickfix list.
+// 'lang' is the language specifier. If supplied, then only matches in the
+// specified language are found.
+static void hgr_search_in_rtp(qf_info_T *qi, regmatch_T *p_regmatch,
+ const char_u *lang)
+ FUNC_ATTR_NONNULL_ARG(1, 2)
+{
+ // Go through all directories in 'runtimepath'
+ char_u *p = p_rtp;
+ while (*p != NUL && !got_int) {
+ copy_option_part(&p, NameBuff, MAXPATHL, ",");
+
+ hgr_search_files_in_dir(qi, NameBuff, p_regmatch, lang);
+ }
+}
- /* Check for a specified language */
- lang = check_help_lang(eap->arg);
+// ":helpgrep {pattern}"
+void ex_helpgrep(exarg_T *eap)
+{
+ qf_info_T *qi = &ql_info;
+ bool new_qi = false;
+ char_u *au_name = NULL;
switch (eap->cmdidx) {
case CMD_helpgrep: au_name = (char_u *)"helpgrep"; break;
@@ -5573,150 +6561,67 @@ void ex_helpgrep(exarg_T *eap)
}
}
- /* Make 'cpoptions' empty, the 'l' flag should not be used here. */
- save_cpo = p_cpo;
+ // Make 'cpoptions' empty, the 'l' flag should not be used here.
+ char_u *const save_cpo = p_cpo;
p_cpo = empty_option;
- if (eap->cmdidx == CMD_lhelpgrep) {
- win_T *wp = NULL;
-
- // If the current window is a help window, then use it
- if (bt_help(curwin->w_buffer)) {
- wp = curwin;
- } else {
- // Find an existing help window
- FOR_ALL_WINDOWS_IN_TAB(wp2, curtab) {
- if (bt_help(wp2->w_buffer)) {
- wp = wp2;
- break;
- }
- }
- }
-
- if (wp == NULL) { // Help window not found
- qi = NULL;
- } else {
- qi = wp->w_llist;
- }
- if (qi == NULL) {
- /* Allocate a new location list for help text matches */
- qi = ll_new_list();
- new_qi = TRUE;
- }
+ if (is_loclist_cmd(eap->cmdidx)) {
+ qi = hgr_get_ll(&new_qi);
}
- regmatch.regprog = vim_regcomp(eap->arg, RE_MAGIC + RE_STRING);
- regmatch.rm_ic = FALSE;
+ incr_quickfix_busy();
+
+ // Check for a specified language
+ char_u *const lang = check_help_lang(eap->arg);
+ regmatch_T regmatch = {
+ .regprog = vim_regcomp(eap->arg, RE_MAGIC + RE_STRING),
+ .rm_ic = false,
+ };
if (regmatch.regprog != NULL) {
// Create a new quickfix list.
qf_new_list(qi, qf_cmdtitle(*eap->cmdlinep));
- // Go through all the directories in 'runtimepath'
- p = p_rtp;
- while (*p != NUL && !got_int) {
- copy_option_part(&p, NameBuff, MAXPATHL, ",");
-
- /* Find all "*.txt" and "*.??x" files in the "doc" directory. */
- add_pathsep((char *)NameBuff);
- STRCAT(NameBuff, "doc/*.\\(txt\\|??x\\)");
-
- // Note: We cannot just do `&NameBuff` because it is a statically sized array
- // so `NameBuff == &NameBuff` according to C semantics.
- char_u *buff_list[1] = {NameBuff};
- if (gen_expand_wildcards(1, buff_list, &fcount,
- &fnames, EW_FILE|EW_SILENT) == OK
- && fcount > 0) {
- for (fi = 0; fi < fcount && !got_int; fi++) {
- // Skip files for a different language.
- if (lang != NULL
- && STRNICMP(lang, fnames[fi] + STRLEN(fnames[fi]) - 3, 2) != 0
- && !(STRNICMP(lang, "en", 2) == 0
- && STRNICMP("txt", fnames[fi]
- + STRLEN(fnames[fi]) - 3, 3) == 0)) {
- continue;
- }
- fd = os_fopen((char *)fnames[fi], "r");
- if (fd != NULL) {
- lnum = 1;
- while (!vim_fgets(IObuff, IOSIZE, fd) && !got_int) {
- char_u *line = IObuff;
- if (vim_regexec(&regmatch, line, (colnr_T)0)) {
- int l = (int)STRLEN(line);
-
- /* remove trailing CR, LF, spaces, etc. */
- while (l > 0 && line[l - 1] <= ' ')
- line[--l] = NUL;
-
- if (qf_add_entry(qi,
- qi->qf_curlist,
- NULL, // dir
- fnames[fi],
- NULL,
- 0,
- line,
- lnum,
- (int)(regmatch.startp[0] - line)
- + 1, // col
- false, // vis_col
- NULL, // search pattern
- 0, // nr
- 1, // type
- true) // valid
- == FAIL) {
- got_int = true;
- if (line != IObuff) {
- xfree(line);
- }
- break;
- }
- }
- if (line != IObuff)
- xfree(line);
- ++lnum;
- line_breakcheck();
- }
- fclose(fd);
- }
- }
- FreeWild(fcount, fnames);
- }
- }
+ hgr_search_in_rtp(qi, &regmatch, lang);
vim_regfree(regmatch.regprog);
- qi->qf_lists[qi->qf_curlist].qf_nonevalid = FALSE;
- qi->qf_lists[qi->qf_curlist].qf_ptr =
- qi->qf_lists[qi->qf_curlist].qf_start;
- qi->qf_lists[qi->qf_curlist].qf_index = 1;
+ qf_list_T *qfl = qf_get_curlist(qi);
+ qfl->qf_nonevalid = false;
+ qfl->qf_ptr = qfl->qf_start;
+ qfl->qf_index = 1;
+ qf_list_changed(qfl);
+ qf_update_buffer(qi, NULL);
}
- if (p_cpo == empty_option)
+ if (p_cpo == empty_option) {
p_cpo = save_cpo;
- else
- /* Darn, some plugin changed the value. */
+ } else {
+ // Darn, some plugin changed the value.
free_string_option(save_cpo);
-
- qf_list_changed(qi, qi->qf_curlist);
- qf_update_buffer(qi, NULL);
+ }
if (au_name != NULL) {
apply_autocmds(EVENT_QUICKFIXCMDPOST, au_name,
curbuf->b_fname, true, curbuf);
if (!new_qi && IS_LL_STACK(qi) && qf_find_buf(qi) == NULL) {
// autocommands made "qi" invalid
+ decr_quickfix_busy();
return;
}
}
- /* Jump to first match. */
- if (qi->qf_lists[qi->qf_curlist].qf_count > 0)
- qf_jump(qi, 0, 0, FALSE);
- else
+ // Jump to first match.
+ if (!qf_list_empty(qf_get_curlist(qi))) {
+ qf_jump(qi, 0, 0, false);
+ } else {
EMSG2(_(e_nomatch2), eap->arg);
+ }
+
+ decr_quickfix_busy();
if (eap->cmdidx == CMD_lhelpgrep) {
- /* If the help window is not opened or if it already points to the
- * correct location list, then free the new location list. */
+ // If the help window is not opened or if it already points to the
+ // correct location list, then free the new location list.
if (!bt_help(curwin->w_buffer) || curwin->w_llist == qi) {
if (new_qi) {
ll_free_all(&qi);
@@ -5727,3 +6632,4 @@ void ex_helpgrep(exarg_T *eap)
}
}
+
diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c
index 9bc7ef07eb..9705896e9b 100644
--- a/src/nvim/regexp.c
+++ b/src/nvim/regexp.c
@@ -40,11 +40,11 @@
* Named character class support added by Walter Briscoe (1998 Jul 01)
*/
-/* Uncomment the first if you do not want to see debugging logs or files
- * related to regular expressions, even when compiling with -DDEBUG.
- * Uncomment the second to get the regexp debugging. */
-/* #undef REGEXP_DEBUG */
-/* #define REGEXP_DEBUG */
+// By default: do not create debugging logs or files related to regular
+// expressions, even when compiling with -DDEBUG.
+// Uncomment the second line to get the regexp debugging.
+// #undef REGEXP_DEBUG
+// #define REGEXP_DEBUG
#include <assert.h>
#include <inttypes.h>
@@ -56,6 +56,7 @@
#include "nvim/regexp.h"
#include "nvim/charset.h"
#include "nvim/eval.h"
+#include "nvim/eval/userfunc.h"
#include "nvim/ex_cmds2.h"
#include "nvim/mark.h"
#include "nvim/memline.h"
@@ -300,8 +301,8 @@ typedef struct {
*/
typedef struct {
union {
- char_u *ptr; /* reginput pointer, for single-line regexp */
- lpos_T pos; /* reginput pos, for multi-line regexp */
+ char_u *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;
@@ -354,7 +355,7 @@ typedef struct regitem_S {
union {
save_se_T sesave;
regsave_T regsave;
- } rs_un; // room for saving reginput
+ } rs_un; ///< room for saving rex.input
} regitem_T;
@@ -489,6 +490,8 @@ static char_u e_z_not_allowed[] = N_("E66: \\z( not allowed here");
static char_u e_z1_not_allowed[] = N_("E67: \\z1 - \\z9 not allowed here");
static char_u e_missing_sb[] = N_("E69: Missing ] after %s%%[");
static char_u e_empty_sb[] = N_("E70: Empty %s%%[]");
+static char_u e_recursive[] = N_("E956: Cannot use pattern recursively");
+
#define NOT_MULTI 0
#define MULTI_ONE 1
#define MULTI_MULT 2
@@ -599,6 +602,12 @@ static int get_char_class(char_u **pp)
#define CLASS_BACKSPACE 14
"escape:]",
#define CLASS_ESCAPE 15
+ "ident:]",
+#define CLASS_IDENT 16
+ "keyword:]",
+#define CLASS_KEYWORD 17
+ "fname:]",
+#define CLASS_FNAME 18
};
#define CLASS_NONE 99
int i;
@@ -632,7 +641,7 @@ static short class_tab[256];
static void init_class_tab(void)
{
int i;
- static int done = FALSE;
+ static int done = false;
if (done)
return;
@@ -657,7 +666,7 @@ static void init_class_tab(void)
}
class_tab[' '] |= RI_WHITE;
class_tab['\t'] |= RI_WHITE;
- done = TRUE;
+ done = true;
}
# define ri_digit(c) (c < 0x100 && (class_tab[c] & RI_DIGIT))
@@ -677,26 +686,24 @@ static void init_class_tab(void)
#define RF_ICOMBINE 8 /* ignore combining characters */
#define RF_LOOKBH 16 /* uses "\@<=" or "\@<!" */
-/*
- * Global work variables for vim_regcomp().
- */
-
-static char_u *regparse; /* Input-scan pointer. */
-static int prevchr_len; /* byte length of previous char */
-static int num_complex_braces; /* Complex \{...} count */
-static int regnpar; /* () count. */
-static int regnzpar; /* \z() count. */
-static int re_has_z; /* \z item detected */
-static char_u *regcode; /* Code-emit pointer, or JUST_CALC_SIZE */
-static long regsize; /* Code size. */
-static int reg_toolong; /* TRUE when offset out of range */
-static char_u had_endbrace[NSUBEXP]; /* flags, TRUE if end of () found */
-static unsigned regflags; /* RF_ flags for prog */
-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 had_eol; /* TRUE when EOL found by vim_regcomp() */
-static int one_exactly = FALSE; /* only do one char for EXACTLY */
+// Global work variables for vim_regcomp().
+
+static char_u *regparse; ///< Input-scan pointer.
+static int prevchr_len; ///< byte length of previous char
+static int num_complex_braces; ///< Complex \{...} count
+static int regnpar; ///< () count.
+static int regnzpar; ///< \z() count.
+static int re_has_z; ///< \z item detected
+static char_u *regcode; ///< Code-emit pointer, or JUST_CALC_SIZE
+static long regsize; ///< Code size.
+static int reg_toolong; ///< true when offset out of range
+static char_u had_endbrace[NSUBEXP]; ///< flags, true if end of () found
+static unsigned regflags; ///< RF_ flags for prog
+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 had_eol; ///< true when EOL found by vim_regcomp()
+static int one_exactly = false; ///< only do one char for EXACTLY
static int reg_magic; /* magicness of the pattern: */
#define MAGIC_NONE 1 /* "\V" very unmagic */
@@ -753,10 +760,9 @@ static int nextchr; /* used for ungetchr() */
static regengine_T bt_regengine;
static regengine_T nfa_regengine;
-/*
- * Return TRUE if compiled regular expression "prog" can match a line break.
- */
-int re_multiline(regprog_T *prog)
+// Return true if compiled regular expression "prog" can match a line break.
+int re_multiline(const regprog_T *prog)
+ FUNC_ATTR_NONNULL_ALL
{
return prog->regflags & RF_HASNL;
}
@@ -1210,7 +1216,7 @@ char_u *skip_regexp(char_u *startp, int dirc, int magic, char_u **newp)
return p;
}
-/// Return TRUE if the back reference is legal. We must have seen the close
+/// 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?
@@ -1233,7 +1239,7 @@ static int seen_endbrace(int refnum)
return false;
}
}
- return TRUE;
+ return true;
}
/*
@@ -1280,6 +1286,7 @@ static regprog_T *bt_regcomp(char_u *expr, int re_flags)
/* Allocate space. */
bt_regprog_T *r = xmalloc(sizeof(bt_regprog_T) + regsize);
+ r->re_in_use = false;
/*
* Second pass: emit code.
@@ -1393,9 +1400,9 @@ regcomp_start (
regnzpar = 1;
re_has_z = 0;
regsize = 0L;
- reg_toolong = FALSE;
+ reg_toolong = false;
regflags = 0;
- had_eol = FALSE;
+ had_eol = false;
}
/*
@@ -1407,7 +1414,7 @@ int vim_regcomp_had_eol(void)
return had_eol;
}
-// variables for parsing reginput
+// variables used for parsing
static int at_start; // True when on the first character
static int prev_at_start; // True when on the second character
@@ -1505,12 +1512,11 @@ reg (
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 */
+ // 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;
}
@@ -1564,7 +1570,7 @@ static char_u *regconcat(int *flagp)
char_u *chain = NULL;
char_u *latest;
int flags;
- int cont = TRUE;
+ int cont = true;
*flagp = WORST; /* Tentatively. */
@@ -1574,7 +1580,7 @@ static char_u *regconcat(int *flagp)
case Magic('|'):
case Magic('&'):
case Magic(')'):
- cont = FALSE;
+ cont = false;
break;
case Magic('Z'):
regflags |= RF_ICOMBINE;
@@ -1801,7 +1807,7 @@ static char_u *regatom(int *flagp)
case Magic('$'):
ret = regnode(EOL);
- had_eol = TRUE;
+ had_eol = true;
break;
case Magic('<'):
@@ -1820,7 +1826,7 @@ static char_u *regatom(int *flagp)
}
if (c == '$') { /* "\_$" is end-of-line */
ret = regnode(EOL);
- had_eol = TRUE;
+ had_eol = true;
break;
}
@@ -2068,11 +2074,12 @@ static char_u *regatom(int *flagp)
}
ungetchr();
- one_exactly = TRUE;
+ one_exactly = true;
lastnode = regatom(flagp);
- one_exactly = FALSE;
- if (lastnode == NULL)
+ one_exactly = false;
+ if (lastnode == NULL) {
return NULL;
+ }
}
if (ret == NULL)
EMSG2_RET_NULL(_(e_empty_sb),
@@ -2416,6 +2423,27 @@ collection:
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
@@ -2513,15 +2541,13 @@ static bool re_mult_next(char *what)
return true;
}
-/*
- * Return TRUE if MULTIBYTECODE should be used instead of EXACTLY for
- * character "c".
- */
-static int use_multibytecode(int c)
+// Return true if MULTIBYTECODE should be used instead of EXACTLY for
+// character "c".
+static bool use_multibytecode(int c)
{
- return has_mbyte && (*mb_char2len)(c) > 1
+ return utf_char2len(c) > 1
&& (re_multi_type(peekchr()) != NOT_MULTI
- || (enc_utf8 && utf_iscomposing(c)));
+ || utf_iscomposing(c));
}
/*
@@ -2666,39 +2692,38 @@ static char_u *re_put_uint32(char_u *p, uint32_t val)
return p;
}
-/*
- * Set the next-pointer at the end of a node chain.
- */
+// Set the next-pointer at the end of a node chain.
static void regtail(char_u *p, char_u *val)
{
- char_u *scan;
- char_u *temp;
int offset;
- if (p == JUST_CALC_SIZE)
+ if (p == JUST_CALC_SIZE) {
return;
+ }
- /* Find last node. */
- scan = p;
+ // Find last node.
+ char_u *scan = p;
for (;; ) {
- temp = regnext(scan);
- if (temp == NULL)
+ char_u *temp = regnext(scan);
+ if (temp == NULL) {
break;
+ }
scan = temp;
}
- if (OP(scan) == BACK)
+ if (OP(scan) == BACK) {
offset = (int)(scan - val);
- else
+ } 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) = (char_u) (((unsigned)offset >> 8) & 0377);
- *(scan + 2) = (char_u) (offset & 0377);
+ }
+ // 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) = (char_u)(((unsigned)offset >> 8) & 0377);
+ *(scan + 2) = (char_u)(offset & 0377);
}
}
@@ -2727,8 +2752,8 @@ static void initchr(char_u *str)
regparse = str;
prevchr_len = 0;
curchr = prevprevchr = prevchr = nextchr = -1;
- at_start = TRUE;
- prev_at_start = FALSE;
+ at_start = true;
+ prev_at_start = false;
}
/*
@@ -2770,7 +2795,7 @@ static void restore_parse_state(parse_state_T *ps)
*/
static int peekchr(void)
{
- static int after_slash = FALSE;
+ static int after_slash = false;
if (curchr != -1) {
return curchr;
@@ -2836,8 +2861,8 @@ static int peekchr(void)
|| (no_Magic(prevchr) == '('
&& prevprevchr == Magic('%')))) {
curchr = Magic('^');
- at_start = TRUE;
- prev_at_start = FALSE;
+ at_start = true;
+ prev_at_start = false;
}
break;
case '$':
@@ -2888,12 +2913,12 @@ static int peekchr(void)
*/
curchr = -1;
prev_at_start = at_start;
- at_start = FALSE; /* be able to say "/\*ptr" */
- ++regparse;
- ++after_slash;
+ at_start = false; // be able to say "/\*ptr"
+ regparse++;
+ after_slash++;
peekchr();
- --regparse;
- --after_slash;
+ regparse--;
+ after_slash--;
curchr = toggle_Magic(curchr);
} else if (vim_strchr(REGEXP_ABBR, c)) {
/*
@@ -2935,7 +2960,7 @@ static void skipchr(void)
}
regparse += prevchr_len;
prev_at_start = at_start;
- at_start = FALSE;
+ at_start = false;
prevprevchr = prevchr;
prevchr = curchr;
curchr = nextchr; /* use previously unget char, or -1 */
@@ -2979,7 +3004,7 @@ static void ungetchr(void)
curchr = prevchr;
prevchr = prevprevchr;
at_start = prev_at_start;
- prev_at_start = FALSE;
+ prev_at_start = false;
/* Backup regparse, so that it's at the same position as before the
* getchr(). */
@@ -3100,14 +3125,14 @@ static int coll_get_char(void)
*/
static int read_limits(long *minval, long *maxval)
{
- int reverse = FALSE;
+ int reverse = false;
char_u *first_char;
long tmp;
if (*regparse == '-') {
// Starts with '-', so reverse the range later.
regparse++;
- reverse = TRUE;
+ reverse = true;
}
first_char = regparse;
*minval = getdigits_long(&regparse, false, 0);
@@ -3152,17 +3177,6 @@ static int read_limits(long *minval, long *maxval)
* Global work variables for vim_regexec().
*/
-/* The current match-position is remembered with these variables: */
-static linenr_T reglnum; /* line number, relative to first line */
-static char_u *regline; /* start of current line */
-static char_u *reginput; /* current input, points into "regline" */
-
-static int need_clear_subexpr; /* subexpressions still need to be
- * cleared */
-static int need_clear_zsubexpr = FALSE; /* extmatch subexpressions
- * still need to be cleared */
-
-
/* 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))
@@ -3213,18 +3227,42 @@ typedef struct {
linenr_T reg_maxline;
bool reg_line_lbr; // "\n" in string is line break
+ // The current match-position is remembered with these variables:
+ linenr_T lnum; ///< line number, relative to first line
+ char_u *line; ///< start of current line
+ char_u *input; ///< current input, points into "regline"
+
+ int need_clear_subexpr; ///< subexpressions still need to be cleared
+ int need_clear_zsubexpr; ///< extmatch subexpressions still need to be
+ ///< cleared
+
+
// Internal copy of 'ignorecase'. It is set at each call to vim_regexec().
// Normally it gets the value of "rm_ic" or "rmm_ic", but when the pattern
// contains '\c' or '\C' the value is overruled.
bool reg_ic;
- // Similar to rex.reg_ic, but only for 'combining' characters. Set with \Z
+ // Similar to "reg_ic", but only for 'combining' characters. Set with \Z
// flag in the regexp. Defaults to false, always.
bool reg_icombine;
// Copy of "rmm_maxcol": maximum column to search for a match. Zero when
// there is no maximum.
colnr_T reg_maxcol;
+
+ // State for the NFA engine regexec.
+ int nfa_has_zend; ///< NFA regexp \ze operator encountered.
+ int nfa_has_backref; ///< NFA regexp \1 .. \9 encountered.
+ int nfa_nsubexpr; ///< Number of sub expressions actually being used
+ ///< during execution. 1 if only the whole match
+ ///< (subexpr 0) is used.
+ // listid is global, so that it increases on recursive calls to
+ // nfa_regmatch(), which means we don't have to clear the lastlist field of
+ // all the states.
+ int nfa_listid;
+ int nfa_alt_listid;
+
+ int nfa_has_zsubexpr; ///< NFA regexp has \z( ), set zsubexpr.
} regexec_T;
static regexec_T rex;
@@ -3265,6 +3303,13 @@ void free_regexp_stuff(void)
#endif
+// Return true if character 'c' is included in 'iskeyword' option for
+// "reg_buf" buffer.
+static bool reg_iswordc(int c)
+{
+ return vim_iswordc_buf(c, rex.reg_buf);
+}
+
/*
* Get pointer to the line "lnum", which is relative to "reg_firstlnum".
*/
@@ -3289,7 +3334,7 @@ static char_u *reg_endzp[NSUBEXP]; /* and end of \z(...\) matches */
static lpos_T reg_startzpos[NSUBEXP]; /* idem, beginning pos */
static lpos_T reg_endzpos[NSUBEXP]; /* idem, end pos */
-// TRUE if using multi-line regexp.
+// true if using multi-line regexp.
#define REG_MULTI (rex.reg_match == NULL)
/*
@@ -3490,13 +3535,13 @@ static long bt_regexec_both(char_u *line,
}
}
- regline = line;
- reglnum = 0;
- reg_toolong = FALSE;
+ 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(regline + col);
+ int c = utf_ptr2char(rex.line + col);
if (prog->regstart == NUL
|| prog->regstart == c
|| (rex.reg_ic
@@ -3513,12 +3558,12 @@ static long bt_regexec_both(char_u *line,
while (!got_int) {
if (prog->regstart != NUL) {
// Skip until the char we know it must start with.
- s = cstrchr(regline + col, prog->regstart);
+ s = cstrchr(rex.line + col, prog->regstart);
if (s == NULL) {
retval = 0;
break;
}
- col = (int)(s - regline);
+ col = (int)(s - rex.line);
}
// Check for maximum column to try.
@@ -3532,18 +3577,16 @@ static long bt_regexec_both(char_u *line,
break;
}
- /* if not currently on the first line, get it again */
- if (reglnum != 0) {
- reglnum = 0;
- regline = reg_getline((linenr_T)0);
+ // if not currently on the first line, get it again
+ if (rex.lnum != 0) {
+ rex.lnum = 0;
+ rex.line = reg_getline((linenr_T)0);
}
- if (regline[col] == NUL)
+ if (rex.line[col] == NUL) {
break;
- if (has_mbyte)
- col += (*mb_ptr2len)(regline + col);
- else
- ++col;
- /* Check for timeout once in a twenty times to avoid overhead. */
+ }
+ col += (*mb_ptr2len)(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)) {
@@ -3575,6 +3618,7 @@ theend:
* Create a new extmatch and mark it as referenced once.
*/
static reg_extmatch_T *make_extmatch(void)
+ FUNC_ATTR_NONNULL_RET
{
reg_extmatch_T *em = xcalloc(1, sizeof(reg_extmatch_T));
em->refcnt = 1;
@@ -3606,18 +3650,17 @@ void unref_extmatch(reg_extmatch_T *em)
}
}
-/// Try match of "prog" with at regline["col"].
+/// Try match of "prog" with at rex.line["col"].
/// @returns 0 for failure, or number of lines contained in the match.
static long regtry(bt_regprog_T *prog,
colnr_T col,
proftime_T *tm, // timeout limit or NULL
int *timed_out) // flag set on timeout or NULL
{
- reginput = regline + col;
- need_clear_subexpr = TRUE;
- /* Clear the external match subpointers if necessary. */
- if (prog->reghasz == REX_SET)
- need_clear_zsubexpr = TRUE;
+ 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;
@@ -3630,18 +3673,18 @@ static long regtry(bt_regprog_T *prog,
rex.reg_startpos[0].col = col;
}
if (rex.reg_endpos[0].lnum < 0) {
- rex.reg_endpos[0].lnum = reglnum;
- rex.reg_endpos[0].col = (int)(reginput - regline);
+ rex.reg_endpos[0].lnum = rex.lnum;
+ rex.reg_endpos[0].col = (int)(rex.input - rex.line);
} else {
// Use line number of "\ze".
- reglnum = rex.reg_endpos[0].lnum;
+ rex.lnum = rex.reg_endpos[0].lnum;
}
} else {
if (rex.reg_startp[0] == NULL) {
- rex.reg_startp[0] = regline + col;
+ rex.reg_startp[0] = rex.line + col;
}
if (rex.reg_endp[0] == NULL) {
- rex.reg_endp[0] = reginput;
+ rex.reg_endp[0] = rex.input;
}
}
/* Package any found \z(...\) matches for export. Default is none. */
@@ -3673,23 +3716,24 @@ static long regtry(bt_regprog_T *prog,
}
}
}
- return 1 + reglnum;
+ return 1 + rex.lnum;
}
// Get class of previous character.
static int reg_prev_class(void)
{
- if (reginput > regline) {
- return mb_get_class_tab(reginput - 1 - utf_head_off(regline, reginput - 1),
- rex.reg_buf->b_chartab);
+ if (rex.input > rex.line) {
+ return mb_get_class_tab(
+ rex.input - 1 - utf_head_off(rex.line, rex.input - 1),
+ rex.reg_buf->b_chartab);
}
return -1;
}
-// Return TRUE if the current reginput position matches the Visual area.
-static int reg_match_visual(void)
+// Return true if the current rex.input position matches the Visual area.
+static bool reg_match_visual(void)
{
pos_T top, bot;
linenr_T lnum;
@@ -3723,16 +3767,17 @@ static int reg_match_visual(void)
}
mode = curbuf->b_visual.vi_mode;
}
- lnum = reglnum + rex.reg_firstlnum;
+ lnum = rex.lnum + rex.reg_firstlnum;
if (lnum < top.lnum || lnum > bot.lnum) {
return false;
}
if (mode == 'v') {
- col = (colnr_T)(reginput - regline);
+ col = (colnr_T)(rex.input - rex.line);
if ((lnum == top.lnum && col < top.col)
- || (lnum == bot.lnum && col >= bot.col + (*p_sel != 'e')))
- return FALSE;
+ || (lnum == bot.lnum && col >= bot.col + (*p_sel != 'e'))) {
+ return false;
+ }
} else if (mode == Ctrl_V) {
getvvcol(wp, &top, &start, NULL, &end);
getvvcol(wp, &bot, &start2, NULL, &end2);
@@ -3742,17 +3787,18 @@ static int reg_match_visual(void)
end = end2;
if (top.col == MAXCOL || bot.col == MAXCOL)
end = MAXCOL;
- unsigned int cols_u = win_linetabsize(wp, regline,
- (colnr_T)(reginput - regline));
+ unsigned int cols_u = win_linetabsize(wp, rex.line,
+ (colnr_T)(rex.input - rex.line));
assert(cols_u <= MAXCOL);
colnr_T cols = (colnr_T)cols_u;
- if (cols < start || cols > end - (*p_sel == 'e'))
- return FALSE;
+ if (cols < start || cols > end - (*p_sel == 'e')) {
+ return false;
+ }
}
- return TRUE;
+ return true;
}
-#define ADVANCE_REGINPUT() MB_PTR_ADV(reginput)
+#define ADVANCE_REGINPUT() MB_PTR_ADV(rex.input)
/*
* The arguments from BRACE_LIMITS are stored here. They are actually local
@@ -3771,11 +3817,11 @@ static long bl_maxval;
/// (that don't need to know whether the rest of the match failed) by a nested
/// loop.
///
-/// Returns TRUE when there is a match. Leaves reginput and reglnum just after
-/// the last matched character.
-/// Returns FALSE when there is no match. Leaves reginput and reglnum in an
+/// Returns true when there is a match. Leaves rex.input and rex.lnum
+/// just after the last matched character.
+/// Returns false when there is no match. Leaves rex.input and rex.lnum in an
/// undefined state!
-static int regmatch(
+static bool regmatch(
char_u *scan, // Current node.
proftime_T *tm, // timeout limit or NULL
int *timed_out // flag set on timeout or NULL
@@ -3858,38 +3904,40 @@ static int regmatch(
op = OP(scan);
// Check for character class with NL added.
if (!rex.reg_line_lbr && WITH_NL(op) && REG_MULTI
- && *reginput == NUL && reglnum <= rex.reg_maxline) {
+ && *rex.input == NUL && rex.lnum <= rex.reg_maxline) {
reg_nextline();
- } else if (rex.reg_line_lbr && WITH_NL(op) && *reginput == '\n') {
+ } 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(reginput);
+ c = utf_ptr2char(rex.input);
switch (op) {
case BOL:
- if (reginput != regline)
+ if (rex.input != rex.line) {
status = RA_NOMATCH;
+ }
break;
case EOL:
- if (c != NUL)
+ 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 (reglnum != 0 || reginput != regline
+ if (rex.lnum != 0 || rex.input != rex.line
|| (REG_MULTI && rex.reg_firstlnum > 1)) {
status = RA_NOMATCH;
}
break;
case RE_EOF:
- if (reglnum != rex.reg_maxline || c != NUL) {
+ if (rex.lnum != rex.reg_maxline || c != NUL) {
status = RA_NOMATCH;
}
break;
@@ -3898,8 +3946,9 @@ static int regmatch(
// 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
- || (reglnum + rex.reg_firstlnum != rex.reg_win->w_cursor.lnum)
- || ((colnr_T)(reginput - regline) != rex.reg_win->w_cursor.col)) {
+ || (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;
@@ -3914,13 +3963,13 @@ static int regmatch(
pos = getmark_buf(rex.reg_buf, mark, false);
if (pos == NULL // mark doesn't exist
|| pos->lnum <= 0 // mark isn't set in reg_buf
- || (pos->lnum == reglnum + rex.reg_firstlnum
- ? (pos->col == (colnr_T)(reginput - regline)
+ || (pos->lnum == rex.lnum + rex.reg_firstlnum
+ ? (pos->col == (colnr_T)(rex.input - rex.line)
? (cmp == '<' || cmp == '>')
- : (pos->col < (colnr_T)(reginput - regline)
+ : (pos->col < (colnr_T)(rex.input - rex.line)
? cmp != '>'
: cmp != '<'))
- : (pos->lnum < reglnum + rex.reg_firstlnum
+ : (pos->lnum < rex.lnum + rex.reg_firstlnum
? cmp != '>'
: cmp != '<'))) {
status = RA_NOMATCH;
@@ -3934,79 +3983,70 @@ static int regmatch(
break;
case RE_LNUM:
- assert(reglnum + rex.reg_firstlnum >= 0
- && (uintmax_t)(reglnum + rex.reg_firstlnum) <= UINT32_MAX);
+ assert(rex.lnum + rex.reg_firstlnum >= 0
+ && (uintmax_t)(rex.lnum + rex.reg_firstlnum) <= UINT32_MAX);
if (!REG_MULTI
- || !re_num_cmp((uint32_t)(reglnum + rex.reg_firstlnum), scan)) {
+ || !re_num_cmp((uint32_t)(rex.lnum + rex.reg_firstlnum), scan)) {
status = RA_NOMATCH;
}
break;
case RE_COL:
- assert(reginput - regline + 1 >= 0
- && (uintmax_t)(reginput - regline + 1) <= UINT32_MAX);
- if (!re_num_cmp((uint32_t)(reginput - regline + 1), scan))
+ 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,
- regline,
- (colnr_T)(reginput - regline)) + 1,
+ rex.line,
+ (colnr_T)(rex.input - rex.line)) + 1,
scan)) {
status = RA_NOMATCH;
}
break;
- case BOW: /* \<word; reginput points to w */
- if (c == NUL) /* Can't match at end of line */
+ case BOW: // \<word; rex.input points to w
+ if (c == NUL) { // Can't match at end of line
status = RA_NOMATCH;
- else if (has_mbyte) {
- int this_class;
-
+ } else {
// Get class of current and previous char (if it exists).
- this_class = mb_get_class_tab(reginput, rex.reg_buf->b_chartab);
+ const int this_class =
+ mb_get_class_tab(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.
}
- } else {
- if (!vim_iswordc_buf(c, rex.reg_buf)
- || (reginput > regline
- && vim_iswordc_buf(reginput[-1], rex.reg_buf))) {
- status = RA_NOMATCH;
- }
}
break;
- case EOW: /* word\>; reginput points after d */
- if (reginput == regline) /* Can't match at start of line */
+ case EOW: // word\>; rex.input points after d
+ if (rex.input == rex.line) { // Can't match at start of line
status = RA_NOMATCH;
- else if (has_mbyte) {
+ } else {
int this_class, prev_class;
// Get class of current and previous char (if it exists).
- this_class = mb_get_class_tab(reginput, rex.reg_buf->b_chartab);
+ this_class = mb_get_class_tab(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;
- } else {
- if (!vim_iswordc_buf(reginput[-1], rex.reg_buf)
- || (reginput[0] != NUL && vim_iswordc_buf(c, rex.reg_buf))) {
+ || prev_class == 0 || prev_class == 1) {
status = RA_NOMATCH;
}
}
- break; /* Matched with EOW */
+ break; // Matched with EOW
case ANY:
- /* ANY does not match new lines. */
- if (c == NUL)
+ // ANY does not match new lines.
+ if (c == NUL) {
status = RA_NOMATCH;
- else
+ } else {
ADVANCE_REGINPUT();
+ }
break;
case IDENT:
@@ -4017,14 +4057,15 @@ static int regmatch(
break;
case SIDENT:
- if (ascii_isdigit(*reginput) || !vim_isIDc(c))
+ if (ascii_isdigit(*rex.input) || !vim_isIDc(c)) {
status = RA_NOMATCH;
- else
+ } else {
ADVANCE_REGINPUT();
+ }
break;
case KWORD:
- if (!vim_iswordp_buf(reginput, rex.reg_buf)) {
+ if (!vim_iswordp_buf(rex.input, rex.reg_buf)) {
status = RA_NOMATCH;
} else {
ADVANCE_REGINPUT();
@@ -4032,8 +4073,8 @@ static int regmatch(
break;
case SKWORD:
- if (ascii_isdigit(*reginput)
- || !vim_iswordp_buf(reginput, rex.reg_buf)) {
+ if (ascii_isdigit(*rex.input)
+ || !vim_iswordp_buf(rex.input, rex.reg_buf)) {
status = RA_NOMATCH;
} else {
ADVANCE_REGINPUT();
@@ -4041,31 +4082,35 @@ static int regmatch(
break;
case FNAME:
- if (!vim_isfilec(c))
+ if (!vim_isfilec(c)) {
status = RA_NOMATCH;
- else
+ } else {
ADVANCE_REGINPUT();
+ }
break;
case SFNAME:
- if (ascii_isdigit(*reginput) || !vim_isfilec(c))
+ if (ascii_isdigit(*rex.input) || !vim_isfilec(c)) {
status = RA_NOMATCH;
- else
+ } else {
ADVANCE_REGINPUT();
+ }
break;
case PRINT:
- if (!vim_isprintc(PTR2CHAR(reginput)))
+ if (!vim_isprintc(PTR2CHAR(rex.input))) {
status = RA_NOMATCH;
- else
+ } else {
ADVANCE_REGINPUT();
+ }
break;
case SPRINT:
- if (ascii_isdigit(*reginput) || !vim_isprintc(PTR2CHAR(reginput)))
+ if (ascii_isdigit(*rex.input) || !vim_isprintc(PTR2CHAR(rex.input))) {
status = RA_NOMATCH;
- else
+ } else {
ADVANCE_REGINPUT();
+ }
break;
case WHITE:
@@ -4201,10 +4246,10 @@ static int regmatch(
opnd = OPERAND(scan);
// Inline the first byte, for speed.
- if (*opnd != *reginput
+ if (*opnd != *rex.input
&& (!rex.reg_ic
|| (!enc_utf8
- && mb_tolower(*opnd) != mb_tolower(*reginput)))) {
+ && mb_tolower(*opnd) != mb_tolower(*rex.input)))) {
status = RA_NOMATCH;
} else if (*opnd == NUL) {
// match empty string always works; happens when "~" is
@@ -4215,14 +4260,14 @@ static int regmatch(
} else {
// Need to match first byte again for multi-byte.
len = (int)STRLEN(opnd);
- if (cstrncmp(opnd, reginput, &len) != 0) {
+ if (cstrncmp(opnd, rex.input, &len) != 0) {
status = RA_NOMATCH;
}
}
// Check for following composing character, unless %C
// follows (skips over all composing chars).
if (status != RA_NOMATCH && enc_utf8
- && UTF_COMPOSINGLIKE(reginput, reginput + len)
+ && UTF_COMPOSINGLIKE(rex.input, rex.input + len)
&& !rex.reg_icombine
&& OP(next) != RE_COMPOSING) {
// raaron: This code makes a composing character get
@@ -4231,7 +4276,7 @@ static int regmatch(
status = RA_NOMATCH;
}
if (status != RA_NOMATCH) {
- reginput += len;
+ rex.input += len;
}
}
}
@@ -4248,54 +4293,52 @@ static int regmatch(
break;
case MULTIBYTECODE:
- if (has_mbyte) {
+ {
int i, len;
- char_u *opnd;
- int opndc = 0, inpc;
- opnd = OPERAND(scan);
+ const char_u *opnd = OPERAND(scan);
// Safety check (just in case 'encoding' was changed since
// compiling the program).
if ((len = (*mb_ptr2len)(opnd)) < 2) {
status = RA_NOMATCH;
break;
}
- if (enc_utf8) {
- opndc = utf_ptr2char(opnd);
- }
- if (enc_utf8 && utf_iscomposing(opndc)) {
- /* When only a composing char is given match at any
- * position where that composing char appears. */
+ const int opndc = utf_ptr2char(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; reginput[i] != NUL; i += utf_ptr2len(reginput + i)) {
- inpc = utf_ptr2char(reginput + i);
+ for (i = 0; rex.input[i] != NUL;
+ i += utf_ptr2len(rex.input + i)) {
+ const int inpc = utf_ptr2char(rex.input + i);
if (!utf_iscomposing(inpc)) {
if (i > 0) {
break;
}
} else if (opndc == inpc) {
// Include all following composing chars.
- len = i + utfc_ptr2len(reginput + i);
+ len = i + utfc_ptr2len(rex.input + i);
status = RA_MATCH;
break;
}
}
- } else
- for (i = 0; i < len; ++i)
- if (opnd[i] != reginput[i]) {
+ } else {
+ for (i = 0; i < len; i++) {
+ if (opnd[i] != rex.input[i]) {
status = RA_NOMATCH;
break;
}
- reginput += len;
- } else
- status = RA_NOMATCH;
+ }
+ }
+ rex.input += len;
+ }
break;
case RE_COMPOSING:
if (enc_utf8) {
// Skip composing characters.
- while (utf_iscomposing(utf_ptr2char(reginput))) {
- MB_CPTR_ADV(reginput);
+ while (utf_iscomposing(utf_ptr2char(rex.input))) {
+ MB_CPTR_ADV(rex.input);
}
}
break;
@@ -4458,7 +4501,7 @@ static int regmatch(
} else {
// Compare current input with back-ref in the same line.
len = (int)(rex.reg_endp[no] - rex.reg_startp[no]);
- if (cstrncmp(rex.reg_startp[no], reginput, &len) != 0) {
+ if (cstrncmp(rex.reg_startp[no], rex.input, &len) != 0) {
status = RA_NOMATCH;
}
}
@@ -4467,12 +4510,12 @@ static int regmatch(
// Backref was not set: Match an empty string.
len = 0;
} else {
- if (rex.reg_startpos[no].lnum == reglnum
- && rex.reg_endpos[no].lnum == reglnum) {
+ 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(regline + rex.reg_startpos[no].col,
- reginput, &len) != 0) {
+ if (cstrncmp(rex.line + rex.reg_startpos[no].col,
+ rex.input, &len) != 0) {
status = RA_NOMATCH;
}
} else {
@@ -4489,8 +4532,8 @@ static int regmatch(
}
}
- /* Matched the backref, skip over it. */
- reginput += len;
+ // Matched the backref, skip over it.
+ rex.input += len;
}
break;
@@ -4504,20 +4547,18 @@ static int regmatch(
case ZREF + 8:
case ZREF + 9:
{
- int len;
-
cleanup_zsubexpr();
no = op - ZREF;
if (re_extmatch_in != NULL
&& re_extmatch_in->matches[no] != NULL) {
- len = (int)STRLEN(re_extmatch_in->matches[no]);
- if (cstrncmp(re_extmatch_in->matches[no],
- reginput, &len) != 0)
+ int len = (int)STRLEN(re_extmatch_in->matches[no]);
+ if (cstrncmp(re_extmatch_in->matches[no], rex.input, &len) != 0) {
status = RA_NOMATCH;
- else
- reginput += len;
+ } else {
+ rex.input += len;
+ }
} else {
- /* Backref was not set: Match an empty string. */
+ // Backref was not set: Match an empty string.
}
}
break;
@@ -4723,15 +4764,17 @@ static int regmatch(
case BHPOS:
if (REG_MULTI) {
- if (behind_pos.rs_u.pos.col != (colnr_T)(reginput - regline)
- || behind_pos.rs_u.pos.lnum != reglnum)
+ 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 != reginput)
+ }
+ } else if (behind_pos.rs_u.ptr != rex.input) {
status = RA_NOMATCH;
+ }
break;
case NEWL:
- if ((c != NUL || !REG_MULTI || reglnum > rex.reg_maxline
+ 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) {
@@ -4944,7 +4987,7 @@ static int regmatch(
if (limit > 0
&& ((rp->rs_un.regsave.rs_u.pos.lnum
< behind_pos.rs_u.pos.lnum
- ? (colnr_T)STRLEN(regline)
+ ? (colnr_T)STRLEN(rex.line)
: behind_pos.rs_u.pos.col)
- rp->rs_un.regsave.rs_u.pos.col >= limit))
no = FAIL;
@@ -4958,7 +5001,7 @@ static int regmatch(
else {
reg_restore(&rp->rs_un.regsave, &backpos);
rp->rs_un.regsave.rs_u.pos.col =
- (colnr_T)STRLEN(regline);
+ (colnr_T)STRLEN(rex.line);
}
} else {
const char_u *const line =
@@ -4970,10 +5013,10 @@ static int regmatch(
+ 1;
}
} else {
- if (rp->rs_un.regsave.rs_u.ptr == regline) {
+ if (rp->rs_un.regsave.rs_u.ptr == rex.line) {
no = FAIL;
} else {
- MB_PTR_BACK(regline, rp->rs_un.regsave.rs_u.ptr);
+ MB_PTR_BACK(rex.line, rp->rs_un.regsave.rs_u.ptr);
if (limit > 0
&& (long)(behind_pos.rs_u.ptr
- rp->rs_un.regsave.rs_u.ptr) > limit) {
@@ -5037,18 +5080,18 @@ static int regmatch(
* didn't match -- back up one char. */
if (--rst->count < rst->minval)
break;
- if (reginput == regline) {
+ if (rex.input == rex.line) {
// backup to last char of previous line
- reglnum--;
- regline = reg_getline(reglnum);
+ rex.lnum--;
+ rex.line = reg_getline(rex.lnum);
// Just in case regrepeat() didn't count right.
- if (regline == NULL) {
+ if (rex.line == NULL) {
break;
}
- reginput = regline + STRLEN(regline);
+ rex.input = rex.line + STRLEN(rex.line);
fast_breakcheck();
} else {
- MB_PTR_BACK(regline, reginput);
+ MB_PTR_BACK(rex.line, rex.input);
}
} else {
/* Range is backwards, use shortest match first.
@@ -5065,9 +5108,9 @@ static int regmatch(
} else
status = RA_NOMATCH;
- /* If it could match, try it. */
- if (rst->nextb == NUL || *reginput == rst->nextb
- || *reginput == rst->nextb_ic) {
+ // 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;
@@ -5154,7 +5197,7 @@ static void regstack_pop(char_u **scan)
/*
* regrepeat - repeatedly match something simple, return how many.
- * Advances reginput (and reglnum) to just after the matched chars.
+ * Advances rex.input (and rex.lnum) to just after the matched chars.
*/
static int
regrepeat (
@@ -5163,12 +5206,11 @@ regrepeat (
)
{
long count = 0;
- char_u *scan;
char_u *opnd;
int mask;
int testval = 0;
- scan = reginput; /* Make local copy of reginput for speed. */
+ char_u *scan = rex.input; // Make local copy of rex.input for speed.
opnd = OPERAND(p);
switch (OP(p)) {
case ANY:
@@ -5180,15 +5222,16 @@ regrepeat (
count++;
MB_PTR_ADV(scan);
}
- if (!REG_MULTI || !WITH_NL(OP(p)) || reglnum > rex.reg_maxline
+ 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 = reginput;
- if (got_int)
+ scan = rex.input;
+ if (got_int) {
break;
+ }
}
break;
@@ -5202,14 +5245,15 @@ regrepeat (
if (vim_isIDc(PTR2CHAR(scan)) && (testval || !ascii_isdigit(*scan))) {
MB_PTR_ADV(scan);
} else if (*scan == NUL) {
- if (!REG_MULTI || !WITH_NL(OP(p)) || reglnum > rex.reg_maxline
+ if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline
|| rex.reg_line_lbr) {
break;
}
reg_nextline();
- scan = reginput;
- if (got_int)
+ scan = rex.input;
+ if (got_int) {
break;
+ }
} else if (rex.reg_line_lbr && *scan == '\n' && WITH_NL(OP(p))) {
scan++;
} else {
@@ -5230,12 +5274,12 @@ regrepeat (
&& (testval || !ascii_isdigit(*scan))) {
MB_PTR_ADV(scan);
} else if (*scan == NUL) {
- if (!REG_MULTI || !WITH_NL(OP(p)) || reglnum > rex.reg_maxline
+ if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline
|| rex.reg_line_lbr) {
break;
}
reg_nextline();
- scan = reginput;
+ scan = rex.input;
if (got_int) {
break;
}
@@ -5258,12 +5302,12 @@ regrepeat (
if (vim_isfilec(PTR2CHAR(scan)) && (testval || !ascii_isdigit(*scan))) {
MB_PTR_ADV(scan);
} else if (*scan == NUL) {
- if (!REG_MULTI || !WITH_NL(OP(p)) || reglnum > rex.reg_maxline
+ if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline
|| rex.reg_line_lbr) {
break;
}
reg_nextline();
- scan = reginput;
+ scan = rex.input;
if (got_int) {
break;
}
@@ -5284,12 +5328,12 @@ regrepeat (
case SPRINT + ADD_NL:
while (count < maxcount) {
if (*scan == NUL) {
- if (!REG_MULTI || !WITH_NL(OP(p)) || reglnum > rex.reg_maxline
+ if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline
|| rex.reg_line_lbr) {
break;
}
reg_nextline();
- scan = reginput;
+ scan = rex.input;
if (got_int) {
break;
}
@@ -5312,14 +5356,15 @@ do_class:
while (count < maxcount) {
int l;
if (*scan == NUL) {
- if (!REG_MULTI || !WITH_NL(OP(p)) || reglnum > rex.reg_maxline
+ if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline
|| rex.reg_line_lbr) {
break;
}
reg_nextline();
- scan = reginput;
- if (got_int)
+ scan = rex.input;
+ if (got_int) {
break;
+ }
} else if (has_mbyte && (l = (*mb_ptr2len)(scan)) > 1) {
if (testval != 0)
break;
@@ -5465,12 +5510,12 @@ do_class:
while (count < maxcount) {
int len;
if (*scan == NUL) {
- if (!REG_MULTI || !WITH_NL(OP(p)) || reglnum > rex.reg_maxline
+ if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline
|| rex.reg_line_lbr) {
break;
}
reg_nextline();
- scan = reginput;
+ scan = rex.input;
if (got_int) {
break;
}
@@ -5492,7 +5537,7 @@ do_class:
case NEWL:
while (count < maxcount
- && ((*scan == NUL && reglnum <= rex.reg_maxline && !rex.reg_line_lbr
+ && ((*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) {
@@ -5500,9 +5545,10 @@ do_class:
} else {
reg_nextline();
}
- scan = reginput;
- if (got_int)
+ scan = rex.input;
+ if (got_int) {
break;
+ }
}
break;
@@ -5514,7 +5560,7 @@ do_class:
break;
}
- reginput = scan;
+ rex.input = scan;
return (int)count;
}
@@ -5544,7 +5590,7 @@ static char_u *regnext(char_u *p)
/*
* Check the regexp program for its magic number.
- * Return TRUE if it's wrong.
+ * Return true if it's wrong.
*/
static int prog_magic_wrong(void)
{
@@ -5558,9 +5604,9 @@ static int prog_magic_wrong(void)
if (UCHARAT(((bt_regprog_T *)prog)->program) != REGMAGIC) {
EMSG(_(e_re_corr));
- return TRUE;
+ return true;
}
- return FALSE;
+ return false;
}
/*
@@ -5570,7 +5616,7 @@ static int prog_magic_wrong(void)
*/
static void cleanup_subexpr(void)
{
- if (need_clear_subexpr) {
+ if (rex.need_clear_subexpr) {
if (REG_MULTI) {
// Use 0xff to set lnum to -1
memset(rex.reg_startpos, 0xff, sizeof(lpos_T) * NSUBEXP);
@@ -5579,13 +5625,13 @@ static void cleanup_subexpr(void)
memset(rex.reg_startp, 0, sizeof(char_u *) * NSUBEXP);
memset(rex.reg_endp, 0, sizeof(char_u *) * NSUBEXP);
}
- need_clear_subexpr = FALSE;
+ rex.need_clear_subexpr = false;
}
}
static void cleanup_zsubexpr(void)
{
- if (need_clear_zsubexpr) {
+ if (rex.need_clear_zsubexpr) {
if (REG_MULTI) {
/* Use 0xff to set lnum to -1 */
memset(reg_startzpos, 0xff, sizeof(lpos_T) * NSUBEXP);
@@ -5594,23 +5640,20 @@ static void cleanup_zsubexpr(void)
memset(reg_startzp, 0, sizeof(char_u *) * NSUBEXP);
memset(reg_endzp, 0, sizeof(char_u *) * NSUBEXP);
}
- need_clear_zsubexpr = FALSE;
+ rex.need_clear_zsubexpr = false;
}
}
-/*
- * Save the current subexpr to "bp", so that they can be restored
- * later by restore_subexpr().
- */
+// 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
{
- int i;
-
- // When "need_clear_subexpr" is set we don't need to save the values, only
+ // 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 = need_clear_subexpr;
- if (!need_clear_subexpr) {
- for (i = 0; i < NSUBEXP; ++i) {
+ bp->save_need_clear_subexpr = rex.need_clear_subexpr;
+ if (!rex.need_clear_subexpr) {
+ 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];
@@ -5622,17 +5665,14 @@ static void save_subexpr(regbehind_T *bp)
}
}
-/*
- * Restore the subexpr from "bp".
- */
+// Restore the subexpr from "bp".
static void restore_subexpr(regbehind_T *bp)
+ FUNC_ATTR_NONNULL_ALL
{
- int i;
-
- /* Only need to restore saved values when they are not to be cleared. */
- need_clear_subexpr = bp->save_need_clear_subexpr;
- if (!need_clear_subexpr) {
- for (i = 0; i < NSUBEXP; ++i) {
+ // 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) {
+ 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;
@@ -5644,56 +5684,54 @@ static void restore_subexpr(regbehind_T *bp)
}
}
-/*
- * Advance reglnum, regline and reginput to the next line.
- */
+// Advance rex.lnum, rex.line and rex.input to the next line.
static void reg_nextline(void)
{
- regline = reg_getline(++reglnum);
- reginput = regline;
+ rex.line = reg_getline(++rex.lnum);
+ rex.input = rex.line;
fast_breakcheck();
}
-/*
- * Save the input line and position in a regsave_T.
- */
+// 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)(reginput - regline);
- save->rs_u.pos.lnum = reglnum;
- } else
- save->rs_u.ptr = reginput;
+ 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.
- */
+// 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 (reglnum != save->rs_u.pos.lnum) {
- /* only call reg_getline() when the line number changed to save
- * a bit of time */
- reglnum = save->rs_u.pos.lnum;
- regline = reg_getline(reglnum);
+ 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 = reg_getline(rex.lnum);
}
- reginput = regline + save->rs_u.pos.col;
- } else
- reginput = save->rs_u.ptr;
+ 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 int reg_save_equal(regsave_T *save)
+// 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 reglnum == save->rs_u.pos.lnum
- && reginput == regline + save->rs_u.pos.col;
- return reginput == save->rs_u.ptr;
+ 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;
}
/*
@@ -5706,14 +5744,14 @@ static int reg_save_equal(regsave_T *save)
static void save_se_multi(save_se_T *savep, lpos_T *posp)
{
savep->se_u.pos = *posp;
- posp->lnum = reglnum;
- posp->col = (colnr_T)(reginput - regline);
+ posp->lnum = rex.lnum;
+ posp->col = (colnr_T)(rex.input - rex.line);
}
static void save_se_one(save_se_T *savep, char_u **pp)
{
savep->se_u.ptr = *pp;
- *pp = reginput;
+ *pp = rex.input;
}
/*
@@ -5748,17 +5786,17 @@ static int match_with_backref(linenr_T start_lnum, colnr_T start_col, linenr_T e
for (;; ) {
/* Since getting one line may invalidate the other, need to make copy.
* Slow! */
- if (regline != reg_tofree) {
- len = (int)STRLEN(regline);
+ if (rex.line != reg_tofree) {
+ len = (int)STRLEN(rex.line);
if (reg_tofree == NULL || len >= (int)reg_tofreelen) {
len += 50; /* get some extra */
xfree(reg_tofree);
reg_tofree = xmalloc(len);
reg_tofreelen = len;
}
- STRCPY(reg_tofree, regline);
- reginput = reg_tofree + (reginput - regline);
- regline = reg_tofree;
+ STRCPY(reg_tofree, rex.line);
+ rex.input = reg_tofree + (rex.input - rex.line);
+ rex.line = reg_tofree;
}
/* Get the line to compare with. */
@@ -5770,14 +5808,16 @@ static int match_with_backref(linenr_T start_lnum, colnr_T start_col, linenr_T e
else
len = (int)STRLEN(p + ccol);
- if (cstrncmp(p + ccol, reginput, &len) != 0)
- return RA_NOMATCH; /* doesn't match */
- if (bytelen != NULL)
+ if (cstrncmp(p + ccol, rex.input, &len) != 0) {
+ return RA_NOMATCH; // doesn't match
+ }
+ if (bytelen != NULL) {
*bytelen += len;
+ }
if (clnum == end_lnum) {
break; // match and at end!
}
- if (reglnum >= rex.reg_maxline) {
+ if (rex.lnum >= rex.reg_maxline) {
return RA_NOMATCH; // text too short
}
@@ -5791,8 +5831,8 @@ static int match_with_backref(linenr_T start_lnum, colnr_T start_col, linenr_T e
return RA_FAIL;
}
- /* found a match! Note that regline may now point to a copy of the line,
- * that should not matter. */
+ // found a match! Note that rex.line may now point to a copy of the line,
+ // that should not matter.
return RA_MATCH;
}
@@ -6475,7 +6515,7 @@ char_u *regtilde(char_u *source, int magic)
return newsub;
}
-static int can_f_submatch = FALSE; /* TRUE when submatch() can be used */
+static bool can_f_submatch = false; // true when submatch() can be used
// These pointers are used for reg_submatch(). Needed for when the
// substitution string is an expression that contains a call to substitute()
@@ -6490,20 +6530,24 @@ typedef struct {
static regsubmatch_T rsm; // can only be used when can_f_submatch is true
-/// Put the submatches in "argv[0]" which is a list passed into call_func() by
-/// vim_regsub_both().
-static int fill_submatch_list(int argc, typval_T *argv, int argcount)
+/// Put the submatches in "argv[argskip]" which is a list passed into
+/// call_func() by vim_regsub_both().
+static int fill_submatch_list(int argc FUNC_ATTR_UNUSED, typval_T *argv,
+ int argskip, int argcount)
+ FUNC_ATTR_NONNULL_ALL
{
- if (argcount == 0) {
- // called function doesn't take an argument
- return 0;
+ typval_T *listarg = argv + argskip;
+
+ if (argcount == argskip) {
+ // called function doesn't take a submatches argument
+ return argskip;
}
// Relies on sl_list to be the first item in staticList10_T.
- tv_list_init_static10((staticList10_T *)argv->vval.v_list);
+ tv_list_init_static10((staticList10_T *)listarg->vval.v_list);
// There are always 10 list items in staticList10_T.
- listitem_T *li = tv_list_first(argv->vval.v_list);
+ listitem_T *li = tv_list_first(listarg->vval.v_list);
for (int i = 0; i < 10; i++) {
char_u *s = rsm.sm_match->startp[i];
if (s == NULL || rsm.sm_match->endp[i] == NULL) {
@@ -6515,7 +6559,7 @@ static int fill_submatch_list(int argc, typval_T *argv, int argcount)
TV_LIST_ITEM_TV(li)->vval.v_string = s;
li = TV_LIST_ITEM_NEXT(argv->vval.v_list, li);
}
- return 1;
+ return argskip + 1;
}
static void clear_submatch_list(staticList10_T *sl)
@@ -6528,11 +6572,11 @@ static void clear_submatch_list(staticList10_T *sl)
/// vim_regsub() - perform substitutions after a vim_regexec() or
/// vim_regexec_multi() match.
///
-/// If "copy" is TRUE really copy into "dest".
-/// If "copy" is FALSE nothing is copied, this is just to find out the length
+/// If "copy" is true really copy into "dest".
+/// If "copy" is false nothing is copied, this is just to find out the length
/// of the result.
///
-/// If "backslash" is TRUE, a backslash will be removed later, need to double
+/// If "backslash" is true, a backslash will be removed later, need to double
/// them to keep them, and insert a backslash before a CR to avoid it being
/// replaced with a line break later.
///
@@ -6624,8 +6668,8 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest,
if (expr != NULL || (source[0] == '\\' && source[1] == '=')) {
// To make sure that the length doesn't change between checking the
// length and copying the string, and to speed up things, the
- // resulting string is saved from the call with "copy" == FALSE to the
- // call with "copy" == TRUE.
+ // resulting string is saved from the call with "copy" == false to the
+ // call with "copy" == true.
if (copy) {
if (eval_result != NULL) {
STRCPY(dest, eval_result);
@@ -6633,7 +6677,7 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest,
XFREE_CLEAR(eval_result);
}
} else {
- int prev_can_f_submatch = can_f_submatch;
+ const bool prev_can_f_submatch = can_f_submatch;
regsubmatch_T rsm_save;
xfree(eval_result);
@@ -6678,10 +6722,15 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest,
// fill_submatch_list() was called.
clear_submatch_list(&matchList);
}
- char buf[NUMBUFLEN];
- eval_result = (char_u *)tv_get_string_buf_chk(&rettv, buf);
- if (eval_result != NULL) {
- eval_result = vim_strsave(eval_result);
+ if (rettv.v_type == VAR_UNKNOWN) {
+ // something failed, no need to report another error
+ eval_result = NULL;
+ } else {
+ char buf[NUMBUFLEN];
+ eval_result = (char_u *)tv_get_string_buf_chk(&rettv, buf);
+ if (eval_result != NULL) {
+ eval_result = vim_strsave(eval_result);
+ }
}
tv_clear(&rettv);
} else {
@@ -6689,7 +6738,7 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest,
}
if (eval_result != NULL) {
- int had_backslash = FALSE;
+ int had_backslash = false;
for (s = eval_result; *s != NUL; MB_PTR_ADV(s)) {
// Change NL to CR, so that it becomes a line break,
@@ -6767,22 +6816,24 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest,
}
if (c == '\\' && *src != NUL) {
- /* Check for abbreviations -- webb */
+ // Check for abbreviations -- webb
switch (*src) {
case 'r': c = CAR; ++src; break;
case 'n': c = NL; ++src; break;
case 't': c = TAB; ++src; break;
- /* Oh no! \e already has meaning in subst pat :-( */
- /* case 'e': c = ESC; ++src; break; */
+ // Oh no! \e already has meaning in subst pat :-(
+ // case 'e': c = ESC; ++src; break;
case 'b': c = Ctrl_H; ++src; break;
- /* If "backslash" is TRUE the backslash will be removed
- * later. Used to insert a literal CR. */
- default: if (backslash) {
- if (copy)
+ // If "backslash" is true the backslash will be removed
+ // later. Used to insert a literal CR.
+ default:
+ if (backslash) {
+ if (copy) {
*dst = '\\';
- ++dst;
- }
+ }
+ dst++;
+ }
c = *src++;
}
} else {
@@ -7152,8 +7203,10 @@ regprog_T *vim_regcomp(char_u *expr_arg, int re_flags)
regexp_engine = AUTOMATIC_ENGINE;
}
}
+#ifdef REGEXP_DEBUG
bt_regengine.expr = expr;
nfa_regengine.expr = expr;
+#endif
// reg_iswordc() uses rex.reg_buf
rex.reg_buf = curbuf;
@@ -7234,24 +7287,33 @@ static void report_re_switch(char_u *pat)
/// @param col the column to start looking for match
/// @param nl
///
-/// @return TRUE if there is a match, FALSE if not.
-static int vim_regexec_string(regmatch_T *rmp, char_u *line, colnr_T col,
- bool nl)
+/// @return true if there is a match, false if not.
+static bool vim_regexec_string(regmatch_T *rmp, char_u *line, colnr_T col,
+ bool nl)
{
regexec_T rex_save;
bool rex_in_use_save = rex_in_use;
+ // Cannot use the same prog recursively, it contains state.
+ if (rmp->regprog->re_in_use) {
+ EMSG(_(e_recursive));
+ return false;
+ }
+ rmp->regprog->re_in_use = true;
+
if (rex_in_use) {
// Being called recursively, save the state.
rex_save = rex;
}
rex_in_use = true;
+
rex.reg_startp = NULL;
rex.reg_endp = NULL;
rex.reg_startpos = NULL;
rex.reg_endpos = NULL;
int result = rmp->regprog->engine->regexec_nl(rmp, line, col, nl);
+ rmp->regprog->re_in_use = false;
// NFA engine aborted because it's very slow, use backtracking engine instead.
if (rmp->regprog->re_engine == AUTOMATIC_ENGINE
@@ -7265,7 +7327,9 @@ static int vim_regexec_string(regmatch_T *rmp, char_u *line, colnr_T col,
report_re_switch(pat);
rmp->regprog = vim_regcomp(pat, re_flags);
if (rmp->regprog != NULL) {
+ rmp->regprog->re_in_use = true;
result = rmp->regprog->engine->regexec_nl(rmp, line, col, nl);
+ rmp->regprog->re_in_use = false;
}
xfree(pat);
@@ -7281,27 +7345,27 @@ static int vim_regexec_string(regmatch_T *rmp, char_u *line, colnr_T col,
}
// Note: "*prog" may be freed and changed.
-// Return TRUE if there is a match, FALSE if not.
-int vim_regexec_prog(regprog_T **prog, bool ignore_case, char_u *line,
+// Return true if there is a match, false if not.
+bool vim_regexec_prog(regprog_T **prog, bool ignore_case, char_u *line,
colnr_T col)
{
regmatch_T regmatch = { .regprog = *prog, .rm_ic = ignore_case };
- int r = vim_regexec_string(&regmatch, line, col, false);
+ bool r = vim_regexec_string(&regmatch, line, col, false);
*prog = regmatch.regprog;
return r;
}
// Note: "rmp->regprog" may be freed and changed.
-// Return TRUE if there is a match, FALSE if not.
-int vim_regexec(regmatch_T *rmp, char_u *line, colnr_T col)
+// Return true if there is a match, false if not.
+bool vim_regexec(regmatch_T *rmp, char_u *line, colnr_T col)
{
return vim_regexec_string(rmp, line, col, false);
}
// 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.
-int vim_regexec_nl(regmatch_T *rmp, char_u *line, colnr_T col)
+// Return true if there is a match, false if not.
+bool vim_regexec_nl(regmatch_T *rmp, char_u *line, colnr_T col)
{
return vim_regexec_string(rmp, line, col, true);
}
@@ -7326,6 +7390,13 @@ long vim_regexec_multi(
regexec_T rex_save;
bool rex_in_use_save = rex_in_use;
+ // Cannot use the same prog recursively, it contains state.
+ if (rmp->regprog->re_in_use) {
+ EMSG(_(e_recursive));
+ return false;
+ }
+ rmp->regprog->re_in_use = true;
+
if (rex_in_use) {
// Being called recursively, save the state.
rex_save = rex;
@@ -7334,6 +7405,7 @@ long vim_regexec_multi(
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.
if (rmp->regprog->re_engine == AUTOMATIC_ENGINE
@@ -7352,8 +7424,10 @@ long vim_regexec_multi(
reg_do_extmatch = 0;
if (rmp->regprog != NULL) {
+ rmp->regprog->re_in_use = true;
result = rmp->regprog->engine->regexec_multi(rmp, win, buf, lnum, col,
tm, timed_out);
+ rmp->regprog->re_in_use = false;
}
xfree(pat);
diff --git a/src/nvim/regexp_defs.h b/src/nvim/regexp_defs.h
index 5e5b19b63f..a729a91555 100644
--- a/src/nvim/regexp_defs.h
+++ b/src/nvim/regexp_defs.h
@@ -72,6 +72,7 @@ struct regprog {
unsigned regflags;
unsigned re_engine; ///< Automatic, backtracking or NFA engine.
unsigned re_flags; ///< Second argument for vim_regcomp().
+ bool re_in_use; ///< prog is being executed
};
/*
@@ -84,14 +85,15 @@ typedef struct {
regengine_T *engine;
unsigned regflags;
unsigned re_engine;
- unsigned re_flags; ///< Second argument for vim_regcomp().
+ unsigned re_flags;
+ bool re_in_use;
int regstart;
char_u reganch;
char_u *regmust;
int regmlen;
char_u reghasz;
- char_u program[1]; /* actually longer.. */
+ char_u program[1]; // actually longer..
} bt_regprog_T;
// Structure representing a NFA state.
@@ -102,7 +104,7 @@ struct nfa_state {
nfa_state_T *out;
nfa_state_T *out1;
int id;
- int lastlist[2]; /* 0: normal, 1: recursive */
+ int lastlist[2]; // 0: normal, 1: recursive
int val;
};
@@ -114,21 +116,22 @@ typedef struct {
regengine_T *engine;
unsigned regflags;
unsigned re_engine;
- unsigned re_flags; ///< Second argument for vim_regcomp().
+ 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
+ char_u *match_text; // plain text to match with
- int has_zend; /* pattern contains \ze */
- int has_backref; /* pattern contains \1 .. \9 */
+ int has_zend; // pattern contains \ze
+ int has_backref; // pattern contains \1 .. \9
int reghasz;
char_u *pattern;
- int nsubexp; /* number of () */
+ int nsubexp; // number of ()
int nstate;
- nfa_state_T state[1]; /* actually longer.. */
+ nfa_state_T state[1]; // actually longer..
} nfa_regprog_T;
/*
diff --git a/src/nvim/regexp_nfa.c b/src/nvim/regexp_nfa.c
index 63640e723e..506c4e87db 100644
--- a/src/nvim/regexp_nfa.c
+++ b/src/nvim/regexp_nfa.c
@@ -230,7 +230,10 @@ enum {
NFA_CLASS_TAB,
NFA_CLASS_RETURN,
NFA_CLASS_BACKSPACE,
- NFA_CLASS_ESCAPE
+ NFA_CLASS_ESCAPE,
+ NFA_CLASS_IDENT,
+ NFA_CLASS_KEYWORD,
+ NFA_CLASS_FNAME,
};
/* Keep in sync with classchars. */
@@ -249,6 +252,7 @@ static char_u e_nul_found[] = N_(
static char_u e_misplaced[] = N_("E866: (NFA regexp) Misplaced %c");
static char_u e_ill_char_class[] = N_(
"E877: (NFA regexp) Invalid character class: %" PRId64);
+static char_u e_value_too_large[] = N_("E951: \\% value too large");
/* Since the out pointers in the list are always
* uninitialized, we use the pointers themselves
@@ -266,9 +270,9 @@ struct Frag {
typedef struct Frag Frag_T;
typedef struct {
- int in_use; /* number of subexpr with useful info */
+ int in_use; ///< number of subexpr with useful info
- /* When REG_MULTI is TRUE list.multi is used, otherwise list.line. */
+ // When REG_MULTI is true list.multi is used, otherwise list.line.
union {
struct multipos {
linenr_T start_lnum;
@@ -309,48 +313,27 @@ typedef struct {
regsubs_T subs; /* submatch info, only party used */
} nfa_thread_T;
-/* nfa_list_T contains the alternative NFA execution states. */
+// 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_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;
-/// re_flags passed to nfa_regcomp().
-static int nfa_re_flags;
-
-/* NFA regexp \ze operator encountered. */
-static int nfa_has_zend;
-
-/* NFA regexp \1 .. \9 encountered. */
-static int nfa_has_backref;
-
-/* NFA regexp has \z( ), set zsubexpr. */
-static int nfa_has_zsubexpr;
-
-/* Number of sub expressions actually being used during execution. 1 if only
- * the whole match (subexpr 0) is used. */
-static int nfa_nsubexpr;
-
-static int *post_start; /* holds the postfix form of r.e. */
+// 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;
-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() */
+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;
-/* listid is global, so that it increases on recursive calls to
- * nfa_regmatch(), which means we don't have to clear the lastlist field of
- * all the states. */
-static int nfa_listid;
-static int nfa_alt_listid;
-
/* 0 for first call to nfa_regmatch(), 1 for recursive call. */
static int nfa_ll_index = 0;
@@ -394,8 +377,8 @@ nfa_regcomp_start (
post_start = (int *)xmalloc(postfix_size);
post_ptr = post_start;
post_end = post_start + nstate_max;
- nfa_has_zend = FALSE;
- nfa_has_backref = FALSE;
+ rex.nfa_has_zend = false;
+ rex.nfa_has_backref = false;
/* shared with BT engine */
regcomp_start(expr, re_flags);
@@ -604,12 +587,10 @@ static int nfa_recognize_char_class(char_u *start, char_u *end, int extra_newl)
# define CLASS_o9 0x02
# define CLASS_underscore 0x01
- int newl = FALSE;
char_u *p;
int config = 0;
- if (extra_newl == TRUE)
- newl = TRUE;
+ bool newl = extra_newl == true;
if (*end != ']')
return FAIL;
@@ -654,13 +635,13 @@ static int nfa_recognize_char_class(char_u *start, char_u *end, int extra_newl)
}
p += 3;
} else if (p + 1 < end && *p == '\\' && *(p + 1) == 'n') {
- newl = TRUE;
+ newl = true;
p += 2;
} else if (*p == '_') {
config |= CLASS_underscore;
p++;
} else if (*p == '\n') {
- newl = TRUE;
+ newl = true;
p++;
} else
return FAIL;
@@ -669,8 +650,9 @@ static int nfa_recognize_char_class(char_u *start, char_u *end, int extra_newl)
if (p != end)
return FAIL;
- if (newl == TRUE)
+ if (newl == true) {
extra_newl = NFA_ADD_NL;
+ }
switch (config) {
case CLASS_o9:
@@ -1187,7 +1169,7 @@ static int nfa_regatom(void)
case Magic('$'):
EMIT(NFA_EOL);
- had_eol = TRUE;
+ had_eol = true;
break;
case Magic('<'):
@@ -1209,7 +1191,7 @@ static int nfa_regatom(void)
}
if (c == '$') { /* "\_$" is end-of-line */
EMIT(NFA_EOL);
- had_eol = TRUE;
+ had_eol = true;
break;
}
@@ -1256,7 +1238,7 @@ static int nfa_regatom(void)
if (p == NULL) {
if (extra == NFA_ADD_NL) {
EMSGN(_(e_ill_char_class), c);
- rc_did_emsg = TRUE;
+ rc_did_emsg = true;
return FAIL;
}
IEMSGN("INTERNAL: Unknown character class char: %" PRId64, c);
@@ -1345,7 +1327,7 @@ static int nfa_regatom(void)
return FAIL;
}
EMIT(NFA_BACKREF1 + refnum);
- nfa_has_backref = true;
+ rex.nfa_has_backref = true;
}
break;
@@ -1360,7 +1342,7 @@ static int nfa_regatom(void)
break;
case 'e':
EMIT(NFA_ZEND);
- nfa_has_zend = true;
+ rex.nfa_has_zend = true;
if (!re_mult_next("\\zs")) {
return false;
}
@@ -1379,8 +1361,8 @@ static int nfa_regatom(void)
EMSG_RET_FAIL(_(e_z1_not_allowed));
}
EMIT(NFA_ZREF1 + (no_Magic(c) - '1'));
- /* No need to set nfa_has_backref, the sub-matches don't
- * change when \z1 .. \z9 matches or not. */
+ // 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 '(':
@@ -1499,7 +1481,8 @@ static int nfa_regatom(void)
c = getchr();
while (ascii_isdigit(c)) {
if (n > (INT32_MAX - (c - '0')) / 10) {
- EMSG(_("E951: \\% value too large"));
+ // overflow.
+ EMSG(_(e_value_too_large));
return FAIL;
}
n = n * 10 + (c - '0');
@@ -1526,7 +1509,7 @@ static int nfa_regatom(void)
limit = INT32_MAX / MB_MAXBYTES;
}
if (n >= limit) {
- EMSG(_("E951: \\% value too large"));
+ EMSG(_(e_value_too_large));
return FAIL;
}
EMIT((int)n);
@@ -1596,12 +1579,12 @@ collection:
EMIT(NFA_CONCAT);
MB_PTR_ADV(regparse);
}
- /* Emit the OR branches for each character in the [] */
- emit_range = FALSE;
+ // Emit the OR branches for each character in the []
+ emit_range = false;
while (regparse < endp) {
oldstartc = startc;
startc = -1;
- got_coll_char = FALSE;
+ got_coll_char = false;
if (*regparse == '[') {
/* Check for [: :], [= =], [. .] */
equiclass = collclass = 0;
@@ -1663,6 +1646,15 @@ collection:
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;
@@ -1682,7 +1674,7 @@ collection:
/* Try a range like 'a-x' or '\t-z'. Also allows '-' as a
* start character. */
if (*regparse == '-' && oldstartc != -1) {
- emit_range = TRUE;
+ emit_range = true;
startc = oldstartc;
MB_PTR_ADV(regparse);
continue; // reading the end of the range
@@ -1762,7 +1754,7 @@ collection:
EMIT(NFA_CONCAT);
}
}
- emit_range = FALSE;
+ emit_range = false;
startc = -1;
} else {
/* This char (startc) is not part of a range. Just
@@ -1779,10 +1771,11 @@ collection:
if (!negated)
extra = NFA_ADD_NL;
} else {
- if (got_coll_char == TRUE && startc == 0)
+ if (got_coll_char == true && startc == 0) {
EMIT(0x0a);
- else
+ } else {
EMIT(startc);
+ }
EMIT(NFA_CONCAT);
}
}
@@ -1800,13 +1793,14 @@ collection:
regparse = endp;
MB_PTR_ADV(regparse);
- /* Mark end of the collection. */
- if (negated == TRUE)
+ // Mark end of the collection.
+ if (negated == true) {
EMIT(NFA_END_NEG_COLL);
- else
+ } else {
EMIT(NFA_END_COLL);
+ }
- /* \_[] also matches \n but it's not negated */
+ // \_[] also matches \n but it's not negated
if (extra == NFA_ADD_NL) {
EMIT(reg_string ? NL : NFA_NEWL);
EMIT(NFA_OR);
@@ -1875,7 +1869,7 @@ static int nfa_regpiece(void)
int op;
int ret;
long minval, maxval;
- int greedy = TRUE; /* Braces are prefixed with '-' ? */
+ bool greedy = true; // Braces are prefixed with '-' ?
parse_state_T old_state;
parse_state_T new_state;
int64_t c2;
@@ -1975,11 +1969,11 @@ static int nfa_regpiece(void)
* parenthesis have the same id
*/
- greedy = TRUE;
+ greedy = true;
c2 = peekchr();
if (c2 == '-' || c2 == Magic('-')) {
skipchr();
- greedy = FALSE;
+ greedy = false;
}
if (!read_limits(&minval, &maxval))
EMSG_RET_FAIL(_("E870: (NFA regexp) Error reading repetition limits"));
@@ -2017,7 +2011,7 @@ static int nfa_regpiece(void)
/* Save parse state after the repeated atom and the \{} */
save_parse_state(&new_state);
- quest = (greedy == TRUE ? NFA_QUEST : NFA_QUEST_NONGREEDY);
+ 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);
@@ -2071,8 +2065,8 @@ static int nfa_regpiece(void)
*/
static int nfa_regconcat(void)
{
- int cont = TRUE;
- int first = TRUE;
+ bool cont = true;
+ bool first = true;
while (cont) {
switch (peekchr()) {
@@ -2080,7 +2074,7 @@ static int nfa_regconcat(void)
case Magic('|'):
case Magic('&'):
case Magic(')'):
- cont = FALSE;
+ cont = false;
break;
case Magic('Z'):
@@ -2117,12 +2111,14 @@ static int nfa_regconcat(void)
break;
default:
- if (nfa_regpiece() == FAIL)
+ if (nfa_regpiece() == FAIL) {
return FAIL;
- if (first == FALSE)
+ }
+ if (first == false) {
EMIT(NFA_CONCAT);
- else
- first = FALSE;
+ } else {
+ first = false;
+ }
break;
}
}
@@ -2228,15 +2224,14 @@ nfa_reg (
else
EMSG_RET_FAIL(_("E873: (NFA regexp) proper termination error"));
}
- /*
- * Here we set the flag allowing back references to this set of
- * parentheses.
- */
+ // 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 */
+ had_endbrace[parno] = true; // have seen the close paren
EMIT(NFA_MOPEN + parno);
- } else if (paren == REG_ZPAREN)
+ } else if (paren == REG_ZPAREN) {
EMIT(NFA_ZOPEN + parno);
+ }
return OK;
}
@@ -2246,10 +2241,10 @@ static char_u code[50];
static void nfa_set_code(int c)
{
- int addnl = FALSE;
+ int addnl = false;
if (c >= NFA_FIRST_NL && c <= NFA_LAST_NL) {
- addnl = TRUE;
+ addnl = true;
c -= NFA_ADD_NL;
}
@@ -2424,6 +2419,9 @@ static void nfa_set_code(int c)
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;
@@ -2462,9 +2460,9 @@ static void nfa_set_code(int c)
code[5] = c;
}
- if (addnl == TRUE)
+ if (addnl == true) {
STRCAT(code, " + NEWLINE ");
-
+ }
}
static FILE *log_fd;
@@ -2846,11 +2844,8 @@ static int nfa_max_width(nfa_state_T *startstate, int depth)
case NFA_UPPER_IC:
case NFA_NUPPER_IC:
case NFA_ANY_COMPOSING:
- /* possibly non-ascii */
- if (has_mbyte)
- len += 3;
- else
- ++len;
+ // possibly non-ascii
+ len += 3;
break;
case NFA_START_INVISIBLE:
@@ -3017,12 +3012,12 @@ static nfa_state_T *post2nfa(int *postfix, int *end, int nfa_calc_size)
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; */
+ // 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();
@@ -3032,8 +3027,8 @@ static nfa_state_T *post2nfa(int *postfix, int *end, int nfa_calc_size)
break;
case NFA_OR:
- /* Alternation */
- if (nfa_calc_size == TRUE) {
+ // Alternation
+ if (nfa_calc_size == true) {
nstate++;
break;
}
@@ -3046,8 +3041,8 @@ static nfa_state_T *post2nfa(int *postfix, int *end, int nfa_calc_size)
break;
case NFA_STAR:
- /* Zero or more, prefer more */
- if (nfa_calc_size == TRUE) {
+ // Zero or more, prefer more
+ if (nfa_calc_size == true) {
nstate++;
break;
}
@@ -3060,8 +3055,8 @@ static nfa_state_T *post2nfa(int *postfix, int *end, int nfa_calc_size)
break;
case NFA_STAR_NONGREEDY:
- /* Zero or more, prefer zero */
- if (nfa_calc_size == TRUE) {
+ // Zero or more, prefer zero
+ if (nfa_calc_size == true) {
nstate++;
break;
}
@@ -3074,8 +3069,8 @@ static nfa_state_T *post2nfa(int *postfix, int *end, int nfa_calc_size)
break;
case NFA_QUEST:
- /* one or zero atoms=> greedy match */
- if (nfa_calc_size == TRUE) {
+ // one or zero atoms=> greedy match
+ if (nfa_calc_size == true) {
nstate++;
break;
}
@@ -3087,8 +3082,8 @@ static nfa_state_T *post2nfa(int *postfix, int *end, int nfa_calc_size)
break;
case NFA_QUEST_NONGREEDY:
- /* zero or one atoms => non-greedy match */
- if (nfa_calc_size == TRUE) {
+ // zero or one atoms => non-greedy match
+ if (nfa_calc_size == true) {
nstate++;
break;
}
@@ -3104,7 +3099,7 @@ static nfa_state_T *post2nfa(int *postfix, int *end, int nfa_calc_size)
/* 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) {
+ if (nfa_calc_size == true) {
nstate++;
break;
}
@@ -3118,10 +3113,10 @@ static nfa_state_T *post2nfa(int *postfix, int *end, int nfa_calc_size)
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; */
+ // 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();
@@ -3135,8 +3130,8 @@ static nfa_state_T *post2nfa(int *postfix, int *end, int nfa_calc_size)
break;
case NFA_EMPTY:
- /* 0-length, used in a repetition with max/min count of 0 */
- if (nfa_calc_size == TRUE) {
+ // 0-length, used in a repetition with max/min count of 0
+ if (nfa_calc_size == true) {
nstate++;
break;
}
@@ -3150,20 +3145,19 @@ static nfa_state_T *post2nfa(int *postfix, int *end, int nfa_calc_size)
{
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) {
+ // \%[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;
}
@@ -3233,7 +3227,7 @@ static nfa_state_T *post2nfa(int *postfix, int *end, int nfa_calc_size)
* Surrounds the preceding atom with START_INVISIBLE and
* END_INVISIBLE, similarly to MOPEN. */
- if (nfa_calc_size == TRUE) {
+ if (nfa_calc_size == true) {
nstate += pattern ? 4 : 2;
break;
}
@@ -3295,8 +3289,8 @@ static nfa_state_T *post2nfa(int *postfix, int *end, int nfa_calc_size)
case NFA_ZOPEN7:
case NFA_ZOPEN8:
case NFA_ZOPEN9:
- case NFA_NOPEN: /* \%( \) "Invisible Submatch" */
- if (nfa_calc_size == TRUE) {
+ case NFA_NOPEN: // \%( \) "Invisible Submatch"
+ if (nfa_calc_size == true) {
nstate += 2;
break;
}
@@ -3374,7 +3368,7 @@ static nfa_state_T *post2nfa(int *postfix, int *end, int nfa_calc_size)
case NFA_ZREF7:
case NFA_ZREF8:
case NFA_ZREF9:
- if (nfa_calc_size == TRUE) {
+ if (nfa_calc_size == true) {
nstate += 2;
break;
}
@@ -3403,7 +3397,7 @@ static nfa_state_T *post2nfa(int *postfix, int *end, int nfa_calc_size)
{
int n = *++p; /* lnum, col or mark name */
- if (nfa_calc_size == TRUE) {
+ if (nfa_calc_size == true) {
nstate += 1;
break;
}
@@ -3418,8 +3412,8 @@ static nfa_state_T *post2nfa(int *postfix, int *end, int nfa_calc_size)
case NFA_ZSTART:
case NFA_ZEND:
default:
- /* Operands */
- if (nfa_calc_size == TRUE) {
+ // Operands
+ if (nfa_calc_size == true) {
nstate++;
break;
}
@@ -3433,7 +3427,7 @@ static nfa_state_T *post2nfa(int *postfix, int *end, int nfa_calc_size)
} /* for(p = postfix; *p; ++p) */
- if (nfa_calc_size == TRUE) {
+ if (nfa_calc_size == true) {
nstate++;
goto theend; /* Return value when counting size is ignored anyway */
}
@@ -3487,11 +3481,11 @@ static void nfa_postprocess(nfa_regprog_T *prog)
|| 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 {
+ // 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);
@@ -3503,10 +3497,11 @@ static void nfa_postprocess(nfa_regprog_T *prog)
* 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
+ 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 */
@@ -3535,8 +3530,9 @@ static void nfa_postprocess(nfa_regprog_T *prog)
static void log_subsexpr(regsubs_T *subs)
{
log_subexpr(&subs->norm);
- if (nfa_has_zsubexpr)
+ if (rex.nfa_has_zsubexpr) {
log_subexpr(&subs->synt);
+ }
}
static void log_subexpr(regsub_T *sub)
@@ -3562,15 +3558,17 @@ static void log_subexpr(regsub_T *sub)
}
}
-static char *pim_info(nfa_pim_T *pim)
+static char *pim_info(const nfa_pim_T *pim)
{
static char buf[30];
- if (pim == NULL || pim->result == NFA_PIM_UNUSED)
+ if (pim == NULL || pim->result == NFA_PIM_UNUSED) {
buf[0] = NUL;
- else {
- sprintf(buf, " PIM col %d", REG_MULTI ? (int)pim->end.pos.col
- : (int)(pim->end.ptr - reginput));
+ } else {
+ snprintf(buf, sizeof(buf), " PIM col %d",
+ REG_MULTI
+ ? (int)pim->end.pos.col
+ : (int)(pim->end.ptr - rex.input));
}
return buf;
}
@@ -3589,19 +3587,21 @@ 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 (nfa_has_zsubexpr)
+ 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 */
+ if (REG_MULTI) {
+ // Use 0xff to set lnum to -1
memset(sub->list.multi, 0xff,
- sizeof(struct multipos) * nfa_nsubexpr);
- else
- memset(sub->list.line, 0, sizeof(struct linepos) * nfa_nsubexpr);
+ sizeof(struct multipos) * rex.nfa_nsubexpr);
+ } else {
+ memset(sub->list.line, 0, sizeof(struct linepos) * rex.nfa_nsubexpr);
+ }
sub->in_use = 0;
}
@@ -3649,7 +3649,7 @@ static void copy_sub_off(regsub_T *to, regsub_T *from)
*/
static void copy_ze_off(regsub_T *to, regsub_T *from)
{
- if (nfa_has_zend) {
+ if (rex.nfa_has_zend) {
if (REG_MULTI) {
if (from->list.multi[0].end_lnum >= 0){
to->list.multi[0].end_lnum = from->list.multi[0].end_lnum;
@@ -3662,9 +3662,9 @@ static void copy_ze_off(regsub_T *to, regsub_T *from)
}
}
-// Return TRUE if "sub1" and "sub2" have the same start positions.
+// Return true if "sub1" and "sub2" have the same start positions.
// When using back-references also check the end position.
-static int sub_equal(regsub_T *sub1, regsub_T *sub2)
+static bool sub_equal(regsub_T *sub1, regsub_T *sub2)
{
int i;
int todo;
@@ -3675,22 +3675,25 @@ static int sub_equal(regsub_T *sub1, regsub_T *sub2)
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)
+ for (i = 0; i < todo; i++) {
+ if (i < sub1->in_use) {
s1 = sub1->list.multi[i].start_lnum;
- else
+ } else {
s1 = -1;
- if (i < sub2->in_use)
+ }
+ if (i < sub2->in_use) {
s2 = sub2->list.multi[i].start_lnum;
- else
+ } else {
s2 = -1;
- if (s1 != s2)
- return FALSE;
+ }
+ if (s1 != s2) {
+ return false;
+ }
if (s1 != -1 && sub1->list.multi[i].start_col
- != sub2->list.multi[i].start_col)
- return FALSE;
-
- if (nfa_has_backref) {
+ != 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 {
@@ -3702,28 +3705,30 @@ static int sub_equal(regsub_T *sub1, regsub_T *sub2)
s2 = -1;
}
if (s1 != s2) {
- return FALSE;
+ return false;
}
if (s1 != -1
&& sub1->list.multi[i].end_col != sub2->list.multi[i].end_col) {
- return FALSE;
+ return false;
}
}
}
} else {
- for (i = 0; i < todo; ++i) {
- if (i < sub1->in_use)
+ for (i = 0; i < todo; i++) {
+ if (i < sub1->in_use) {
sp1 = sub1->list.line[i].start;
- else
+ } else {
sp1 = NULL;
- if (i < sub2->in_use)
+ }
+ if (i < sub2->in_use) {
sp2 = sub2->list.line[i].start;
- else
+ } else {
sp2 = NULL;
- if (sp1 != sp2)
- return FALSE;
-
- if (nfa_has_backref) {
+ }
+ if (sp1 != sp2) {
+ return false;
+ }
+ if (rex.nfa_has_backref) {
if (i < sub1->in_use) {
sp1 = sub1->list.line[i].end;
} else {
@@ -3735,13 +3740,13 @@ static int sub_equal(regsub_T *sub1, regsub_T *sub2)
sp2 = NULL;
}
if (sp1 != sp2) {
- return FALSE;
+ return false;
}
}
}
}
- return TRUE;
+ return true;
}
#ifdef REGEXP_DEBUG
@@ -3752,83 +3757,81 @@ static void report_state(char *action,
nfa_pim_T *pim) {
int col;
- if (sub->in_use <= 0)
+ if (sub->in_use <= 0) {
col = -1;
- else if (REG_MULTI)
+ } else if (REG_MULTI) {
col = sub->list.multi[0].start_col;
- else
- col = (int)(sub->list.line[0].start - regline);
+ } else {
+ col = (int)(sub->list.line[0].start - rex.line);
+ }
nfa_set_code(state->c);
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));
+ action, abs(state->id), lid, state->c, code, col,
+ pim_info(pim));
}
#endif
-/*
- * Return TRUE if the same state is already in list "l" with the same
- * positions as "subs".
- */
-static int
-has_state_with_pos (
- nfa_list_T *l, /* runtime state list */
- nfa_state_T *state, /* state to update */
- regsubs_T *subs, /* pointers to subexpressions */
- nfa_pim_T *pim /* postponed 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, // runtime state list
+ nfa_state_T *state, // state to update
+ regsubs_T *subs, // pointers to subexpressions
+ nfa_pim_T *pim // postponed match or NULL
)
+ FUNC_ATTR_NONNULL_ARG(1, 2, 3)
{
- nfa_thread_T *thread;
- int i;
-
- for (i = 0; i < l->n; ++i) {
- thread = &l->t[i];
+ 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)
- && (!nfa_has_zsubexpr
+ && (!rex.nfa_has_zsubexpr
|| sub_equal(&thread->subs.synt, &subs->synt))
- && pim_equal(&thread->pim, pim))
- return TRUE;
+ && pim_equal(&thread->pim, pim)) {
+ return true;
+ }
}
- return FALSE;
+ return false;
}
-/*
- * Return TRUE if "one" and "two" are equal. That includes when both are not
- * set.
- */
-static int pim_equal(nfa_pim_T *one, nfa_pim_T *two)
+// 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)
{
- int one_unused = (one == NULL || one->result == NFA_PIM_UNUSED);
- int two_unused = (two == NULL || two->result == NFA_PIM_UNUSED);
+ 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 */
+ 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)
+ }
+ 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 int match_follows(nfa_state_T *startstate, int depth)
+// 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
{
- nfa_state_T *state = startstate;
-
- /* avoid too much recursion */
- if (depth > 10)
- return FALSE;
+ const nfa_state_T *state = startstate;
+ // avoid too much recursion
+ if (depth > 10) {
+ return false;
+ }
while (state != NULL) {
switch (state->c) {
case NFA_MATCH:
@@ -3836,7 +3839,7 @@ static int match_follows(nfa_state_T *startstate, int depth)
case NFA_END_INVISIBLE:
case NFA_END_INVISIBLE_NEG:
case NFA_END_PATTERN:
- return TRUE;
+ return true;
case NFA_SPLIT:
return match_follows(state->out, depth + 1)
@@ -3890,39 +3893,38 @@ static int match_follows(nfa_state_T *startstate, int depth)
case NFA_START_COLL:
case NFA_START_NEG_COLL:
case NFA_NEWL:
- /* state will advance input */
- return FALSE;
+ // 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. */
+ 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;
+ return false;
}
-/*
- * Return TRUE if "state" is already in list "l".
- */
-static int
-state_in_list (
- nfa_list_T *l, /* runtime state list */
- nfa_state_T *state, /* state to update */
- regsubs_T *subs /* pointers to subexpressions */
+// Return true if "state" is already in list "l".
+static bool state_in_list(
+ nfa_list_T *l, // runtime state list
+ nfa_state_T *state, // state to update
+ regsubs_T *subs // pointers to subexpressions
)
+ FUNC_ATTR_NONNULL_ALL
{
if (state->lastlist[nfa_ll_index] == l->id) {
- if (!nfa_has_backref || has_state_with_pos(l, state, subs, NULL))
- return TRUE;
+ if (!rex.nfa_has_backref || has_state_with_pos(l, state, subs, NULL)) {
+ return true;
+ }
}
- return FALSE;
+ return false;
}
// Offset used for "off" by addstate_here().
@@ -3941,10 +3943,10 @@ static regsubs_T *addstate(
{
int subidx;
int off = off_arg;
- int add_here = FALSE;
+ int add_here = false;
int listindex = 0;
int k;
- int found = FALSE;
+ int found = false;
nfa_thread_T *thread;
struct multipos save_multipos;
int save_in_use;
@@ -3954,7 +3956,7 @@ static regsubs_T *addstate(
regsubs_T *subs = subs_arg;
static regsubs_T temp_subs;
#ifdef REGEXP_DEBUG
- int did_print = FALSE;
+ int did_print = false;
#endif
static int depth = 0;
@@ -4003,15 +4005,16 @@ static regsubs_T *addstate(
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 (reginput > regline
- && *reginput != NUL
+ // "^" 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
- || reglnum == nfa_endp->se_u.pos.lnum))
+ || rex.lnum == nfa_endp->se_u.pos.lnum)) {
goto skip_add;
+ }
FALLTHROUGH;
case NFA_MOPEN1:
@@ -4045,7 +4048,7 @@ static regsubs_T *addstate(
* 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 (!nfa_has_backref && pim == NULL && !l->has_pim
+ if (!rex.nfa_has_backref && pim == NULL && !l->has_pim
&& state->c != NFA_MATCH) {
/* When called from addstate_here() do insert before
@@ -4053,7 +4056,7 @@ static regsubs_T *addstate(
if (add_here) {
for (k = 0; k < l->n && k < listindex; ++k) {
if (l->t[k].state->id == state->id) {
- found = TRUE;
+ found = true;
break;
}
}
@@ -4090,11 +4093,12 @@ skip_add:
return NULL;
}
if (subs != &temp_subs) {
- /* "subs" may point into the current array, need to make a
- * copy before it becomes invalid. */
+ // "subs" may point into the current array, need to make a
+ // copy before it becomes invalid.
copy_sub(&temp_subs.norm, &subs->norm);
- if (nfa_has_zsubexpr)
+ if (rex.nfa_has_zsubexpr) {
copy_sub(&temp_subs.synt, &subs->synt);
+ }
subs = &temp_subs;
}
@@ -4111,14 +4115,15 @@ skip_add:
thread->pim.result = NFA_PIM_UNUSED;
else {
copy_pim(&thread->pim, pim);
- l->has_pim = TRUE;
+ l->has_pim = true;
}
copy_sub(&thread->subs.norm, &subs->norm);
- if (nfa_has_zsubexpr)
+ 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;
+ did_print = true;
#endif
}
@@ -4193,13 +4198,12 @@ skip_add:
sub->in_use = subidx + 1;
}
if (off == -1) {
- sub->list.multi[subidx].start_lnum = reglnum + 1;
+ sub->list.multi[subidx].start_lnum = rex.lnum + 1;
sub->list.multi[subidx].start_col = 0;
} else {
-
- sub->list.multi[subidx].start_lnum = reglnum;
+ sub->list.multi[subidx].start_lnum = rex.lnum;
sub->list.multi[subidx].start_col =
- (colnr_T)(reginput - regline + off);
+ (colnr_T)(rex.input - rex.line + off);
}
sub->list.multi[subidx].end_lnum = -1;
} else {
@@ -4214,7 +4218,7 @@ skip_add:
}
sub->in_use = subidx + 1;
}
- sub->list.line[subidx].start = reginput + off;
+ sub->list.line[subidx].start = rex.input + off;
}
subs = addstate(l, state->out, subs, pim, off_arg);
@@ -4239,9 +4243,10 @@ skip_add:
break;
case NFA_MCLOSE:
- if (nfa_has_zend && (REG_MULTI
- ? subs->norm.list.multi[0].end_lnum >= 0
- : subs->norm.list.line[0].end != NULL)) {
+ 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;
@@ -4286,18 +4291,18 @@ skip_add:
if (REG_MULTI) {
save_multipos = sub->list.multi[subidx];
if (off == -1) {
- sub->list.multi[subidx].end_lnum = reglnum + 1;
+ sub->list.multi[subidx].end_lnum = rex.lnum + 1;
sub->list.multi[subidx].end_col = 0;
} else {
- sub->list.multi[subidx].end_lnum = reglnum;
+ sub->list.multi[subidx].end_lnum = rex.lnum;
sub->list.multi[subidx].end_col =
- (colnr_T)(reginput - regline + off);
+ (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 = reginput + off;
+ sub->list.line[subidx].end = rex.input + off;
// avoid compiler warnings
memset(&save_multipos, 0, sizeof(save_multipos));
}
@@ -4484,6 +4489,21 @@ static int check_char_class(int class, int c)
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
@@ -4495,7 +4515,7 @@ static int check_char_class(int class, int c)
/*
* Check for a match with subexpression "subidx".
- * Return TRUE if it matches.
+ * Return true if it matches.
*/
static int
match_backref (
@@ -4510,49 +4530,49 @@ match_backref (
retempty:
/* backref was not set, match an empty string */
*bytelen = 0;
- return TRUE;
+ 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 == reglnum
- && sub->list.multi[subidx].end_lnum == reglnum) {
+ 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(regline + sub->list.multi[subidx].start_col,
- reginput, &len) == 0) {
+ if (cstrncmp(rex.line + sub->list.multi[subidx].start_col,
+ rex.input, &len) == 0) {
*bytelen = len;
- return TRUE;
+ 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;
+ 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(sub->list.line[subidx].start, reginput, &len) == 0) {
+ if (cstrncmp(sub->list.line[subidx].start, rex.input, &len) == 0) {
*bytelen = len;
- return TRUE;
+ return true;
}
}
- return FALSE;
+ return false;
}
/*
* Check for a match with \z subexpression "subidx".
- * Return TRUE if it matches.
+ * Return true if it matches.
*/
static int
match_zref (
@@ -4566,15 +4586,15 @@ match_zref (
if (re_extmatch_in == NULL || re_extmatch_in->matches[subidx] == NULL) {
/* backref was not set, match an empty string */
*bytelen = 0;
- return TRUE;
+ return true;
}
len = (int)STRLEN(re_extmatch_in->matches[subidx]);
- if (cstrncmp(re_extmatch_in->matches[subidx], reginput, &len) == 0) {
+ if (cstrncmp(re_extmatch_in->matches[subidx], rex.input, &len) == 0) {
*bytelen = len;
- return TRUE;
+ return true;
}
- return FALSE;
+ return false;
}
/*
@@ -4627,74 +4647,79 @@ static bool nfa_re_num_cmp(uintmax_t val, int op, uintmax_t pos)
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)
{
- int save_reginput_col = (int)(reginput - regline);
- int save_reglnum = reglnum;
- int save_nfa_match = nfa_match;
- int save_nfa_listid = nfa_listid;
- save_se_T *save_nfa_endp = nfa_endp;
+ 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 result;
- int need_restore = FALSE;
+ int need_restore = false;
if (pim != NULL) {
- /* start at the position where the postponed match was */
- if (REG_MULTI)
- reginput = regline + pim->end.pos.col;
- else
- reginput = pim->end.ptr;
+ // 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. */
+ // 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)(reginput - regline);
- endpos.se_u.pos.lnum = reglnum;
- } else
+ 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 = reginput;
- 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. */
+ // 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) {
- regline = reg_getline(--reglnum);
- if (regline == NULL)
- /* can't go before the first line */
- regline = reg_getline(++reglnum);
+ rex.line = reg_getline(--rex.lnum);
+ if (rex.line == NULL) {
+ // can't go before the first line
+ rex.line = reg_getline(++rex.lnum);
+ }
}
- reginput = regline;
+ rex.input = rex.line;
} else {
- if (REG_MULTI && (int)(reginput - regline) < state->val) {
- /* Not enough bytes in this line, go to end of
- * previous line. */
- regline = reg_getline(--reglnum);
- if (regline == NULL) {
- /* can't go before the first line */
- regline = reg_getline(++reglnum);
- reginput = regline;
- } else
- reginput = regline + STRLEN(regline);
+ if (REG_MULTI && (int)(rex.input - rex.line) < state->val) {
+ // Not enough bytes in this line, go to end of
+ // previous line.
+ rex.line = reg_getline(--rex.lnum);
+ if (rex.line == NULL) {
+ // can't go before the first line
+ rex.line = reg_getline(++rex.lnum);
+ rex.input = rex.line;
+ } else {
+ rex.input = rex.line + STRLEN(rex.line);
+ }
}
- if ((int)(reginput - regline) >= state->val) {
- reginput -= state->val;
- reginput -= utf_head_off(regline, reginput);
+ if ((int)(rex.input - rex.line) >= state->val) {
+ rex.input -= state->val;
+ rex.input -= utf_head_off(rex.line, rex.input);
} else {
- reginput = regline;
+ rex.input = rex.line;
}
}
}
@@ -4704,48 +4729,50 @@ static int recursive_regmatch(
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. */
+ // 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 < nstate) {
+ // 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) * nstate);
- *listids_len = nstate;
+ *listids = xmalloc(sizeof(**listids) * prog->nstate);
+ *listids_len = prog->nstate;
}
nfa_save_listids(prog, *listids);
- need_restore = TRUE;
- /* any value of nfa_listid will do */
+ 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 nfa_listid is different from a previous recursive
- * call, because some states may still have this ID. */
- ++nfa_ll_index;
- if (nfa_listid <= nfa_alt_listid)
- nfa_listid = nfa_alt_listid;
+ // 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 */
+ // 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;
- result = nfa_regmatch(prog, state->out, submatch, m);
+ const int result = nfa_regmatch(prog, state->out, submatch, m);
- if (need_restore)
+ if (need_restore) {
nfa_restore_listids(prog, *listids);
- else {
- --nfa_ll_index;
- nfa_alt_listid = nfa_listid;
+ } else {
+ nfa_ll_index--;
+ rex.nfa_alt_listid = rex.nfa_listid;
}
- /* restore position in input text */
- reglnum = save_reglnum;
- if (REG_MULTI)
- regline = reg_getline(reglnum);
- reginput = regline + save_reginput_col;
+ // restore position in input text
+ rex.lnum = save_reglnum;
+ if (REG_MULTI) {
+ rex.line = reg_getline(rex.lnum);
+ }
+ rex.input = rex.line + save_reginput_col;
if (result != NFA_TOO_EXPENSIVE) {
nfa_match = save_nfa_match;
- nfa_listid = save_nfa_listid;
+ rex.nfa_listid = save_nfa_listid;
}
nfa_endp = save_nfa_endp;
@@ -4754,7 +4781,7 @@ static int recursive_regmatch(
if (log_fd != NULL) {
fprintf(log_fd, "****************************\n");
fprintf(log_fd, "FINISHED RUNNING nfa_regmatch() recursively\n");
- fprintf(log_fd, "MATCH = %s\n", !result ? "FALSE" : "OK");
+ fprintf(log_fd, "MATCH = %s\n", !result ? "false" : "OK");
fprintf(log_fd, "****************************\n");
} else {
EMSG(_(e_log_open_failed));
@@ -4928,11 +4955,11 @@ static int failure_chance(nfa_state_T *state, int depth)
*/
static int skip_to_start(int c, colnr_T *colp)
{
- const char_u *const s = cstrchr(regline + *colp, c);
+ const char_u *const s = cstrchr(rex.line + *colp, c);
if (s == NULL) {
return FAIL;
}
- *colp = (int)(s - regline);
+ *colp = (int)(s - rex.line);
return OK;
}
@@ -4943,22 +4970,22 @@ static int skip_to_start(int c, colnr_T *colp)
*/
static long find_match_text(colnr_T startcol, int regstart, char_u *match_text)
{
-#define PTR2LEN(x) enc_utf8 ? utf_ptr2len(x) : MB_PTR2LEN(x)
+#define PTR2LEN(x) utf_ptr2len(x)
colnr_T col = startcol;
- int regstart_len = PTR2LEN(regline + startcol);
+ int regstart_len = PTR2LEN(rex.line + startcol);
for (;;) {
bool match = true;
char_u *s1 = match_text;
- char_u *s2 = regline + col + regstart_len; // skip regstart
+ char_u *s2 = rex.line + col + regstart_len; // skip regstart
while (*s1) {
int c1_len = PTR2LEN(s1);
int c1 = PTR2CHAR(s1);
int c2_len = PTR2LEN(s2);
int c2 = PTR2CHAR(s2);
- if ((c1 != c2 && (!rex.reg_ic || mb_tolower(c1) != mb_tolower(c2)))
+ if ((c1 != c2 && (!rex.reg_ic || utf_fold(c1) != utf_fold(c2)))
|| c1_len != c2_len) {
match = false;
break;
@@ -4971,12 +4998,12 @@ static long find_match_text(colnr_T startcol, int regstart, char_u *match_text)
&& !(enc_utf8 && utf_iscomposing(PTR2CHAR(s2)))) {
cleanup_subexpr();
if (REG_MULTI) {
- rex.reg_startpos[0].lnum = reglnum;
+ rex.reg_startpos[0].lnum = rex.lnum;
rex.reg_startpos[0].col = col;
- rex.reg_endpos[0].lnum = reglnum;
- rex.reg_endpos[0].col = s2 - regline;
+ rex.reg_endpos[0].lnum = rex.lnum;
+ rex.reg_endpos[0].col = s2 - rex.line;
} else {
- rex.reg_startp[0] = regline + col;
+ rex.reg_startp[0] = rex.line + col;
rex.reg_endp[0] = s2;
}
return 1L;
@@ -5006,17 +5033,18 @@ static int nfa_did_time_out(void)
/// Main matching routine.
///
-/// Run NFA to determine whether it matches reginput.
+/// 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,
+/// 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;
@@ -5061,11 +5089,11 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
nfa_match = false;
// Allocate memory for the lists of nodes.
- size_t size = (nstate + 1) * sizeof(nfa_thread_T);
+ size_t size = (prog->nstate + 1) * sizeof(nfa_thread_T);
list[0].t = xmalloc(size);
- list[0].len = nstate + 1;
+ list[0].len = prog->nstate + 1;
list[1].t = xmalloc(size);
- list[1].len = nstate + 1;
+ list[1].len = prog->nstate + 1;
#ifdef REGEXP_DEBUG
log_fd = fopen(NFA_REGEXP_RUN_LOG, "a");
@@ -5083,23 +5111,24 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
thislist = &list[0];
thislist->n = 0;
- thislist->has_pim = FALSE;
+ thislist->has_pim = false;
nextlist = &list[1];
nextlist->n = 0;
- nextlist->has_pim = FALSE;
+ nextlist->has_pim = false;
#ifdef REGEXP_DEBUG
fprintf(log_fd, "(---) STARTSTATE first\n");
#endif
- thislist->id = nfa_listid + 1;
+ thislist->id = rex.nfa_listid + 1;
- /* Inline optimized code for addstate(thislist, start, m, 0) if we know
- * it's the first MOPEN. */
+ // 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 = reglnum;
- m->norm.list.multi[0].start_col = (colnr_T)(reginput - regline);
- } else
- m->norm.list.line[0].start = reginput;
+ m->norm.list.multi[0].start_lnum = rex.lnum;
+ m->norm.list.multi[0].start_col = (colnr_T)(rex.input - rex.line);
+ } else {
+ m->norm.list.line[0].start = rex.input;
+ }
m->norm.in_use = 1;
r = addstate(thislist, start->out, m, NULL, 0);
} else {
@@ -5120,8 +5149,8 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
* Run for each character.
*/
for (;; ) {
- int curc = utf_ptr2char(reginput);
- int clen = utfc_ptr2len(reginput);
+ int curc = utf_ptr2char(rex.input);
+ int clen = utfc_ptr2len(rex.input);
if (curc == NUL) {
clen = 0;
go_to_nextline = false;
@@ -5132,20 +5161,20 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
nextlist = &list[flag ^= 1];
nextlist->n = 0; // clear nextlist
nextlist->has_pim = false;
- nfa_listid++;
+ rex.nfa_listid++;
if (prog->re_engine == AUTOMATIC_ENGINE
- && (nfa_listid >= NFA_MAX_STATES)) {
+ && (rex.nfa_listid >= NFA_MAX_STATES)) {
// Too many states, retry with old engine.
nfa_match = NFA_TOO_EXPENSIVE;
goto theend;
}
- thislist->id = nfa_listid;
- nextlist->id = nfa_listid + 1;
+ 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", reginput);
+ fprintf(log_fd, ">>> Reginput is \"%s\"\n", rex.input);
fprintf(log_fd,
">>> Advanced one character... Current char is %c (code %d) \n",
curc,
@@ -5198,7 +5227,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
} else if (REG_MULTI) {
col = t->subs.norm.list.multi[0].start_col;
} else {
- col = (int)(t->subs.norm.list.line[0].start - regline);
+ 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",
@@ -5224,64 +5253,66 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
}
nfa_match = true;
copy_sub(&submatch->norm, &t->subs.norm);
- if (nfa_has_zsubexpr)
+ 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 "reginput" is
- * correct. */
- if (nextlist->n == 0)
+ // 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.
- */
+ // 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)reglnum,
- (int)nfa_endp->se_u.pos.lnum,
- (int)(reginput - regline),
- nfa_endp->se_u.pos.col);
- else
+ 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)(reginput - regline),
- (int)(nfa_endp->se_u.ptr - reginput));
+ (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
- ? (reglnum != nfa_endp->se_u.pos.lnum
- || (int)(reginput - regline)
- != nfa_endp->se_u.pos.col)
- : reginput != nfa_endp->se_u.ptr))
+ // 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 \@! */
+ }
+ // do not set submatches for \@!
if (t->state->c != NFA_END_INVISIBLE_NEG) {
copy_sub(&m->norm, &t->subs.norm);
- if (nfa_has_zsubexpr)
+ if (rex.nfa_has_zsubexpr) {
copy_sub(&m->synt, &t->subs.synt);
+ }
}
#ifdef REGEXP_DEBUG
fprintf(log_fd, "Match found:\n");
@@ -5320,9 +5351,9 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
// Copy submatch info for the recursive call, opposite
// of what happens on success below.
copy_sub_off(&m->norm, &t->subs.norm);
- if (nfa_has_zsubexpr)
+ 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,
@@ -5333,7 +5364,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
}
// for \@! and \@<! it is a match when the result is
- // FALSE
+ // false
if (result != (t->state->c == NFA_START_INVISIBLE_NEG
|| t->state->c == NFA_START_INVISIBLE_NEG_FIRST
|| t->state->c
@@ -5342,8 +5373,9 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
== NFA_START_INVISIBLE_BEFORE_NEG_FIRST)) {
// Copy submatch info from the recursive call
copy_sub_off(&t->subs.norm, &m->norm);
- if (nfa_has_zsubexpr)
+ 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);
@@ -5367,11 +5399,11 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
pim.subs.norm.in_use = 0;
pim.subs.synt.in_use = 0;
if (REG_MULTI) {
- pim.end.pos.col = (int)(reginput - regline);
- pim.end.pos.lnum = reglnum;
- } else
- pim.end.ptr = reginput;
-
+ 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).
@@ -5424,7 +5456,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
// Copy submatch info to the recursive call, opposite of what
// happens afterwards.
copy_sub_off(&m->norm, &t->subs.norm);
- if (nfa_has_zsubexpr) {
+ if (rex.nfa_has_zsubexpr) {
copy_sub_off(&m->synt, &t->subs.synt);
}
@@ -5444,7 +5476,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
#endif
// Copy submatch info from the recursive call
copy_sub_off(&t->subs.norm, &m->norm);
- if (nfa_has_zsubexpr) {
+ if (rex.nfa_has_zsubexpr) {
copy_sub_off(&t->subs.synt, &m->synt);
}
// Now we need to skip over the matched text and then
@@ -5452,9 +5484,9 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
if (REG_MULTI) {
// TODO(RE): multi-line match
bytelen = m->norm.list.multi[0].end_col
- - (int)(reginput - regline);
+ - (int)(rex.input - rex.line);
} else {
- bytelen = (int)(m->norm.list.line[0].end - reginput);
+ bytelen = (int)(m->norm.list.line[0].end - rex.input);
}
#ifdef REGEXP_DEBUG
@@ -5483,7 +5515,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
}
case NFA_BOL:
- if (reginput == regline) {
+ if (rex.input == rex.line) {
add_here = true;
add_state = t->state->out;
}
@@ -5501,20 +5533,16 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
if (curc == NUL) {
result = false;
- } else if (has_mbyte) {
+ } else {
int this_class;
// Get class of current and previous char (if it exists).
- this_class = mb_get_class_tab(reginput, rex.reg_buf->b_chartab);
+ this_class = mb_get_class_tab(rex.input, rex.reg_buf->b_chartab);
if (this_class <= 1) {
result = false;
} else if (reg_prev_class() == this_class) {
result = false;
}
- } else if (!vim_iswordc_buf(curc, rex.reg_buf)
- || (reginput > regline
- && vim_iswordc_buf(reginput[-1], rex.reg_buf))) {
- result = false;
}
if (result) {
add_here = true;
@@ -5524,22 +5552,18 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
case NFA_EOW:
result = true;
- if (reginput == regline) {
+ if (rex.input == rex.line) {
result = false;
- } else if (has_mbyte) {
+ } else {
int this_class, prev_class;
// Get class of current and previous char (if it exists).
- this_class = mb_get_class_tab(reginput, rex.reg_buf->b_chartab);
+ this_class = mb_get_class_tab(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;
}
- } else if (!vim_iswordc_buf(reginput[-1], rex.reg_buf)
- || (reginput[0] != NUL
- && vim_iswordc_buf(curc, rex.reg_buf))) {
- result = false;
}
if (result) {
add_here = true;
@@ -5548,7 +5572,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
break;
case NFA_BOF:
- if (reglnum == 0 && reginput == regline
+ if (rex.lnum == 0 && rex.input == rex.line
&& (!REG_MULTI || rex.reg_firstlnum == 1)) {
add_here = true;
add_state = t->state->out;
@@ -5556,7 +5580,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
break;
case NFA_EOF:
- if (reglnum == rex.reg_maxline && curc == NUL) {
+ if (rex.lnum == rex.reg_maxline && curc == NUL) {
add_here = true;
add_state = t->state->out;
}
@@ -5601,7 +5625,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
// We don't care about the order of composing characters.
// Get them into cchars[] first.
while (len < clen) {
- mc = utf_ptr2char(reginput + len);
+ mc = utf_ptr2char(rex.input + len);
cchars[ccount++] = mc;
len += mb_char2len(mc);
if (ccount == MAX_MCO)
@@ -5632,7 +5656,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
case NFA_NEWL:
if (curc == NUL && !rex.reg_line_lbr && REG_MULTI
- && reglnum <= rex.reg_maxline) {
+ && 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.
@@ -5680,13 +5704,13 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
break;
}
if (rex.reg_ic) {
- int curc_low = mb_tolower(curc);
+ int curc_low = utf_fold(curc);
int done = false;
for (; c1 <= c2; c1++) {
- if (mb_tolower(c1) == curc_low) {
+ if (utf_fold(c1) == curc_low) {
result = result_if_matched;
- done = TRUE;
+ done = true;
break;
}
}
@@ -5696,8 +5720,8 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
}
} else if (state->c < 0 ? check_char_class(state->c, curc)
: (curc == state->c
- || (rex.reg_ic && mb_tolower(curc)
- == mb_tolower(state->c)))) {
+ || (rex.reg_ic
+ && utf_fold(curc) == utf_fold(state->c)))) {
result = result_if_matched;
break;
}
@@ -5744,13 +5768,13 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
break;
case NFA_KWORD: // \k
- result = vim_iswordp_buf(reginput, rex.reg_buf);
+ result = vim_iswordp_buf(rex.input, rex.reg_buf);
ADD_STATE_IF_MATCH(t->state);
break;
case NFA_SKWORD: // \K
result = !ascii_isdigit(curc)
- && vim_iswordp_buf(reginput, rex.reg_buf);
+ && vim_iswordp_buf(rex.input, rex.reg_buf);
ADD_STATE_IF_MATCH(t->state);
break;
@@ -5765,12 +5789,12 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
break;
case NFA_PRINT: // \p
- result = vim_isprintc(PTR2CHAR(reginput));
+ result = vim_isprintc(PTR2CHAR(rex.input));
ADD_STATE_IF_MATCH(t->state);
break;
case NFA_SPRINT: // \P
- result = !ascii_isdigit(curc) && vim_isprintc(PTR2CHAR(reginput));
+ result = !ascii_isdigit(curc) && vim_isprintc(PTR2CHAR(rex.input));
ADD_STATE_IF_MATCH(t->state);
break;
@@ -5957,14 +5981,14 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
case NFA_LNUM_LT:
assert(t->state->val >= 0
&& !((rex.reg_firstlnum > 0
- && reglnum > LONG_MAX - rex.reg_firstlnum)
+ && rex.lnum > LONG_MAX - rex.reg_firstlnum)
|| (rex.reg_firstlnum < 0
- && reglnum < LONG_MIN + rex.reg_firstlnum))
- && reglnum + 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)(reglnum + rex.reg_firstlnum)));
+ (uintmax_t)(rex.lnum + rex.reg_firstlnum)));
if (result) {
add_here = true;
add_state = t->state->out;
@@ -5975,11 +5999,11 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
case NFA_COL_GT:
case NFA_COL_LT:
assert(t->state->val >= 0
- && reginput >= regline
- && (uintmax_t)(reginput - regline) <= UINTMAX_MAX - 1);
+ && 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)(reginput - regline + 1));
+ (uintmax_t)(rex.input - rex.line + 1));
if (result) {
add_here = true;
add_state = t->state->out;
@@ -5991,7 +6015,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
case NFA_VCOL_LT:
{
int op = t->state->c - NFA_VCOL;
- colnr_T col = (colnr_T)(reginput - regline);
+ 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.
@@ -6012,7 +6036,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
result = col > t->state->val * ts;
}
if (!result) {
- uintmax_t lts = win_linetabsize(wp, regline, col);
+ uintmax_t lts = win_linetabsize(wp, rex.line, col);
assert(t->state->val >= 0);
result = nfa_re_num_cmp((uintmax_t)t->state->val, op, lts + 1);
}
@@ -6032,13 +6056,13 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
// Compare the mark position to the match position.
result = (pos != NULL // mark doesn't exist
&& pos->lnum > 0 // mark isn't set in reg_buf
- && (pos->lnum == reglnum + rex.reg_firstlnum
- ? (pos->col == (colnr_T)(reginput - regline)
+ && (pos->lnum == rex.lnum + rex.reg_firstlnum
+ ? (pos->col == (colnr_T)(rex.input - rex.line)
? t->state->c == NFA_MARK
- : (pos->col < (colnr_T)(reginput - regline)
+ : (pos->col < (colnr_T)(rex.input - rex.line)
? t->state->c == NFA_MARK_GT
: t->state->c == NFA_MARK_LT))
- : (pos->lnum < reglnum + rex.reg_firstlnum
+ : (pos->lnum < rex.lnum + rex.reg_firstlnum
? t->state->c == NFA_MARK_GT
: t->state->c == NFA_MARK_LT)));
if (result) {
@@ -6049,10 +6073,9 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
}
case NFA_CURSOR:
- result = (rex.reg_win != NULL
- && (reglnum + rex.reg_firstlnum == rex.reg_win->w_cursor.lnum)
- && ((colnr_T)(reginput - regline)
- == rex.reg_win->w_cursor.col));
+ 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;
@@ -6104,13 +6127,13 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
result = (c == curc);
if (!result && rex.reg_ic) {
- result = mb_tolower(c) == mb_tolower(curc);
+ 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 && enc_utf8 && !rex.reg_icombine) {
- clen = utf_ptr2len(reginput);
+ clen = utf_ptr2len(rex.input);
}
ADD_STATE_IF_MATCH(t->state);
@@ -6141,7 +6164,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
&listids, &listids_len);
pim->result = result ? NFA_PIM_MATCH : NFA_PIM_NOMATCH;
// for \@! and \@<! it is a match when the result is
- // FALSE
+ // false
if (result != (pim->state->c == NFA_START_INVISIBLE_NEG
|| pim->state->c == NFA_START_INVISIBLE_NEG_FIRST
|| pim->state->c
@@ -6150,8 +6173,9 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
== NFA_START_INVISIBLE_BEFORE_NEG_FIRST)) {
// Copy submatch info from the recursive call
copy_sub_off(&pim->subs.norm, &m->norm);
- if (nfa_has_zsubexpr)
+ if (rex.nfa_has_zsubexpr) {
copy_sub_off(&pim->subs.synt, &m->synt);
+ }
}
} else {
result = (pim->result == NFA_PIM_MATCH);
@@ -6161,12 +6185,12 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
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, "MATCH = %s\n", result ? "OK" : "false");
fprintf(log_fd, "\n");
#endif
}
- // for \@! and \@<! it is a match when result is FALSE
+ // 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
@@ -6175,8 +6199,9 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
== NFA_START_INVISIBLE_BEFORE_NEG_FIRST)) {
// Copy submatch info from the recursive call
copy_sub_off(&t->subs.norm, &pim->subs.norm);
- if (nfa_has_zsubexpr)
+ 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;
@@ -6220,29 +6245,28 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
// Also don't start a match past the first line.
if (!nfa_match
&& ((toplevel
- && reglnum == 0
+ && rex.lnum == 0
&& clen != 0
&& (rex.reg_maxcol == 0
- || (colnr_T)(reginput - regline) < rex.reg_maxcol))
+ || (colnr_T)(rex.input - rex.line) < rex.reg_maxcol))
|| (nfa_endp != NULL
&& (REG_MULTI
- ? (reglnum < nfa_endp->se_u.pos.lnum
- || (reglnum == nfa_endp->se_u.pos.lnum
- && (int)(reginput - regline)
+ ? (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))
- : reginput < nfa_endp->se_u.ptr)))) {
+ : 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;
- int c;
+ int add = true;
if (prog->regstart != NUL && clen != 0) {
if (nextlist->n == 0) {
- colnr_T col = (colnr_T)(reginput - regline) + clen;
+ 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.
@@ -6251,30 +6275,32 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
}
#ifdef REGEXP_DEBUG
fprintf(log_fd, " Skipping ahead %d bytes to regstart\n",
- col - ((colnr_T)(reginput - regline) + clen));
+ col - ((colnr_T)(rex.input - rex.line) + clen));
#endif
- reginput = regline + col - clen;
+ rex.input = rex.line + col - clen;
} else {
// Checking if the required start character matches is
// cheaper than adding a state that won't match.
- c = PTR2CHAR(reginput + clen);
- if (c != prog->regstart && (!rex.reg_ic || mb_tolower(c)
- != mb_tolower(prog->regstart))) {
+ const int c = PTR2CHAR(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;
+ add = false;
}
}
}
if (add) {
- if (REG_MULTI)
+ if (REG_MULTI) {
m->norm.list.multi[0].start_col =
- (colnr_T)(reginput - regline) + clen;
- else
- m->norm.list.line[0].start = reginput + clen;
+ (colnr_T)(rex.input - rex.line) + clen;
+ } 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;
@@ -6303,9 +6329,9 @@ nextchar:
// Advance to the next character, or advance to the next line, or
// finish.
if (clen != 0) {
- reginput += clen;
+ rex.input += clen;
} else if (go_to_nextline || (nfa_endp != NULL && REG_MULTI
- && reglnum < nfa_endp->se_u.pos.lnum)) {
+ && rex.lnum < nfa_endp->se_u.pos.lnum)) {
reg_nextline();
} else {
break;
@@ -6344,7 +6370,7 @@ theend:
return nfa_match;
}
-// Try match of "prog" with at regline["col"].
+// Try match of "prog" with at rex.line["col"].
// Returns <= 0 for failure, number of lines contained in the match otherwise.
static long nfa_regtry(nfa_regprog_T *prog,
colnr_T col,
@@ -6358,7 +6384,7 @@ static long nfa_regtry(nfa_regprog_T *prog,
FILE *f;
#endif
- reginput = regline + col;
+ rex.input = rex.line + col;
nfa_time_limit = tm;
nfa_timed_out = timed_out;
nfa_time_count = 0;
@@ -6371,7 +6397,7 @@ static long nfa_regtry(nfa_regprog_T *prog,
#ifdef REGEXP_DEBUG
fprintf(f, "\tRegexp is \"%s\"\n", nfa_regengine.expr);
#endif
- fprintf(f, "\tInput text is \"%s\" \n", reginput);
+ fprintf(f, "\tInput text is \"%s\" \n", rex.input);
fprintf(f, "\t=======================================================\n\n");
nfa_print_state(f, start);
fprintf(f, "\n\n");
@@ -6409,11 +6435,11 @@ static long nfa_regtry(nfa_regprog_T *prog,
}
if (rex.reg_endpos[0].lnum < 0) {
// pattern has a \ze but it didn't match, use current end
- rex.reg_endpos[0].lnum = reglnum;
- rex.reg_endpos[0].col = (int)(reginput - regline);
+ rex.reg_endpos[0].lnum = rex.lnum;
+ rex.reg_endpos[0].col = (int)(rex.input - rex.line);
} else {
// Use line number of "\ze".
- reglnum = rex.reg_endpos[0].lnum;
+ rex.lnum = rex.reg_endpos[0].lnum;
}
} else {
for (i = 0; i < subs.norm.in_use; i++) {
@@ -6422,10 +6448,10 @@ static long nfa_regtry(nfa_regprog_T *prog,
}
if (rex.reg_startp[0] == NULL) {
- rex.reg_startp[0] = regline + col;
+ rex.reg_startp[0] = rex.line + col;
}
if (rex.reg_endp[0] == NULL) {
- rex.reg_endp[0] = reginput;
+ rex.reg_endp[0] = rex.input;
}
}
@@ -6460,7 +6486,7 @@ static long nfa_regtry(nfa_regprog_T *prog,
}
}
- return 1 + reglnum;
+ return 1 + rex.lnum;
}
/// Match a regexp against a string ("line" points to the string) or multiple
@@ -6478,7 +6504,6 @@ static long nfa_regexec_both(char_u *line, colnr_T startcol,
{
nfa_regprog_T *prog;
long retval = 0L;
- int i;
colnr_T col = startcol;
if (REG_MULTI) {
@@ -6510,26 +6535,30 @@ static long nfa_regexec_both(char_u *line, colnr_T startcol,
rex.reg_icombine = true;
}
- regline = line;
- reglnum = 0; /* relative to line */
+ rex.line = line;
+ rex.lnum = 0; // relative to line
- nfa_has_zend = prog->has_zend;
- nfa_has_backref = prog->has_backref;
- nfa_nsubexpr = prog->nsubexp;
- nfa_listid = 1;
- nfa_alt_listid = 2;
+ 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;
- need_clear_subexpr = TRUE;
- /* Clear the external match subpointers if necessary. */
+ rex.need_clear_subexpr = true;
+ // Clear the external match subpointers if necessary.
if (prog->reghasz == REX_SET) {
- nfa_has_zsubexpr = TRUE;
- need_clear_zsubexpr = TRUE;
- } else
- nfa_has_zsubexpr = FALSE;
+ 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.
@@ -6549,8 +6578,10 @@ static long nfa_regexec_both(char_u *line, colnr_T startcol,
goto theend;
}
- nstate = prog->nstate;
- for (i = 0; i < nstate; ++i) {
+ // 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;
@@ -6558,7 +6589,9 @@ static long nfa_regexec_both(char_u *line, colnr_T startcol,
retval = nfa_regtry(prog, col, tm, timed_out);
+#ifdef REGEXP_DEBUG
nfa_regengine.expr = NULL;
+#endif
theend:
return retval;
@@ -6576,7 +6609,9 @@ static regprog_T *nfa_regcomp(char_u *expr, int re_flags)
if (expr == NULL)
return NULL;
+#ifdef REGEXP_DEBUG
nfa_regengine.expr = expr;
+#endif
nfa_re_flags = re_flags;
init_class_tab();
@@ -6613,26 +6648,27 @@ static regprog_T *nfa_regcomp(char_u *expr, int re_flags)
* PASS 1
* Count number of NFA states in "nstate". Do not build the NFA.
*/
- post2nfa(postfix, post_ptr, TRUE);
+ post2nfa(postfix, post_ptr, true);
/* allocate the regprog with space for the compiled regexp */
size_t prog_size = sizeof(nfa_regprog_T) + sizeof(nfa_state_T) * (nstate - 1);
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)
+ 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 = nfa_has_zend;
- prog->has_backref = nfa_has_backref;
+ prog->has_zend = rex.nfa_has_zend;
+ prog->has_backref = rex.nfa_has_backref;
prog->nsubexp = regnpar;
nfa_postprocess(prog);
@@ -6648,7 +6684,9 @@ static regprog_T *nfa_regcomp(char_u *expr, int re_flags)
/* Remember whether this pattern has any \z specials in it. */
prog->reghasz = re_has_z;
prog->pattern = vim_strsave(expr);
+#ifdef REGEXP_DEBUG
nfa_regengine.expr = NULL;
+#endif
out:
xfree(post_start);
@@ -6660,8 +6698,8 @@ fail:
XFREE_CLEAR(prog);
#ifdef REGEXP_DEBUG
nfa_postfix_dump(expr, FAIL);
-#endif
nfa_regengine.expr = NULL;
+#endif
goto out;
}
diff --git a/src/nvim/screen.c b/src/nvim/screen.c
index 5bcd2c808d..c5723035d6 100644
--- a/src/nvim/screen.c
+++ b/src/nvim/screen.c
@@ -87,6 +87,7 @@
#include "nvim/highlight.h"
#include "nvim/main.h"
#include "nvim/mark.h"
+#include "nvim/extmark.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
@@ -116,15 +117,16 @@
#include "nvim/window.h"
#include "nvim/os/time.h"
#include "nvim/api/private/helpers.h"
+#include "nvim/api/vim.h"
+#include "nvim/lua/executor.h"
#define MB_FILLER_CHAR '<' /* character used when a double-width character
* doesn't fit. */
-#define W_ENDCOL(wp) (wp->w_wincol + wp->w_width)
-#define W_ENDROW(wp) (wp->w_winrow + wp->w_height)
// temporary buffer for rendering a single screenline, so it can be
-// comparared with previous contents to calulate smallest delta.
+// compared with previous contents to calculate smallest delta.
+// Per-cell attributes
static size_t linebuf_size = 0;
static schar_T *linebuf_char = NULL;
static sattr_T *linebuf_attr = NULL;
@@ -232,6 +234,22 @@ void redraw_buf_line_later(buf_T *buf, linenr_T line)
}
}
+void redraw_buf_range_later(buf_T *buf, linenr_T firstline, linenr_T lastline)
+{
+ FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
+ if (wp->w_buffer == buf
+ && lastline >= wp->w_topline && firstline < wp->w_botline) {
+ if (wp->w_redraw_top == 0 || wp->w_redraw_top > firstline) {
+ wp->w_redraw_top = firstline;
+ }
+ if (wp->w_redraw_bot == 0 || wp->w_redraw_bot < lastline) {
+ wp->w_redraw_bot = lastline;
+ }
+ redraw_win_later(wp, VALID);
+ }
+ }
+}
+
/*
* Changed something in the current window, at buffer line "lnum", that
* requires that line and possibly other lines to be redrawn.
@@ -286,6 +304,11 @@ int update_screen(int type)
return FAIL;
}
+ // May have postponed updating diffs.
+ if (need_diff_redraw) {
+ diff_redraw(true);
+ }
+
if (must_redraw) {
if (type < must_redraw) /* use maximal type */
type = must_redraw;
@@ -311,10 +334,10 @@ int update_screen(int type)
}
return FAIL;
}
+ updating_screen = 1;
- updating_screen = TRUE;
- ++display_tick; /* let syntax code know we're in a next round of
- * display updating */
+ display_tick++; // let syntax code know we're in a next round of
+ // display updating
// Tricky: vim code can reset msg_scrolled behind our back, so need
// separate bookkeeping for now.
@@ -347,6 +370,17 @@ int update_screen(int type)
grid_clear_line(&default_grid, default_grid.line_offset[i],
Columns, false);
}
+ FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
+ if (wp->w_floating) {
+ continue;
+ }
+ if (W_ENDROW(wp) > valid) {
+ wp->w_redr_type = MAX(wp->w_redr_type, NOT_VALID);
+ }
+ if (W_ENDROW(wp) + wp->w_status_height > valid) {
+ wp->w_redr_status = true;
+ }
+ }
}
msg_grid_set_pos(Rows-p_ch, false);
msg_grid_invalid = false;
@@ -383,7 +417,7 @@ int update_screen(int type)
need_wait_return = false;
}
- win_ui_flush_positions();
+ win_ui_flush();
msg_ext_check_clear();
/* reset cmdline_row now (may have been changed temporarily) */
@@ -472,6 +506,19 @@ int update_screen(int type)
if (wwp == wp && syntax_present(wp)) {
syn_stack_apply_changes(wp->w_buffer);
}
+
+ buf_T *buf = wp->w_buffer;
+ if (buf->b_luahl && buf->b_luahl_window != LUA_NOREF) {
+ Error err = ERROR_INIT;
+ FIXED_TEMP_ARRAY(args, 2);
+ args.items[0] = BUFFER_OBJ(buf->handle);
+ args.items[1] = INTEGER_OBJ(display_tick);
+ executor_exec_lua_cb(buf->b_luahl_start, "start", args, false, &err);
+ if (ERROR_SET(&err)) {
+ ELOG("error in luahl start: %s", err.msg);
+ api_clear_error(&err);
+ }
+ }
}
}
@@ -518,7 +565,7 @@ int update_screen(int type)
wp->w_buffer->b_mod_set = false;
}
- updating_screen = FALSE;
+ updating_screen = 0;
/* Clear or redraw the command line. Done last, because scrolling may
* mess up the command line. */
@@ -577,11 +624,25 @@ void conceal_check_cursor_line(void)
/// Whether cursorline is drawn in a special way
///
/// If true, both old and new cursorline will need
-/// need to be redrawn when moving cursor within windows.
+/// to be redrawn when moving cursor within windows.
+/// TODO(bfredl): VIsual_active shouldn't be needed, but is used to fix a glitch
+/// caused by scrolling.
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));
+ return wp->w_p_cul
+ || (wp->w_p_cole > 0 && (VIsual_active || !conceal_cursor_line(wp)));
+}
+
+static DecorationRedrawState decorations;
+bool decorations_active = false;
+
+void decorations_add_luahl_attr(int attr_id,
+ int start_row, int start_col,
+ int end_row, int end_col)
+{
+ kv_push(decorations.active,
+ ((HlRange){ start_row, start_col, end_row, end_col, attr_id, NULL }));
}
/*
@@ -871,7 +932,7 @@ static void win_update(win_T *wp)
if (wp->w_lines[0].wl_lnum != wp->w_topline)
i += diff_check_fill(wp, wp->w_lines[0].wl_lnum)
- wp->w_old_topfill;
- if (i < wp->w_grid.Rows - 2) { // less than a screen off
+ if (i != 0 && i < wp->w_grid.Rows - 2) { // less than a screen off
// Try to insert the correct number of lines.
// If not the last window, delete the lines at the bottom.
// win_ins_lines may fail when the terminal can't do it.
@@ -1173,7 +1234,31 @@ static void win_update(win_T *wp)
idx = 0; /* first entry in w_lines[].wl_size */
row = 0;
srow = 0;
- lnum = wp->w_topline; /* first line shown in window */
+ lnum = wp->w_topline; // first line shown in window
+
+ decorations_active = decorations_redraw_reset(buf, &decorations);
+
+ if (buf->b_luahl && buf->b_luahl_window != LUA_NOREF) {
+ Error err = ERROR_INIT;
+ FIXED_TEMP_ARRAY(args, 4);
+ linenr_T knownmax = ((wp->w_valid & VALID_BOTLINE)
+ ? wp->w_botline
+ : (wp->w_topline + wp->w_height_inner));
+ args.items[0] = WINDOW_OBJ(wp->handle);
+ args.items[1] = BUFFER_OBJ(buf->handle);
+ // TODO(bfredl): we are not using this, but should be first drawn line?
+ args.items[2] = INTEGER_OBJ(wp->w_topline-1);
+ args.items[3] = INTEGER_OBJ(knownmax);
+ // TODO(bfredl): we could allow this callback to change mod_top, mod_bot.
+ // For now the "start" callback is expected to use nvim__buf_redraw_range.
+ executor_exec_lua_cb(buf->b_luahl_window, "window", args, false, &err);
+ if (ERROR_SET(&err)) {
+ ELOG("error in luahl window: %s", err.msg);
+ api_clear_error(&err);
+ }
+ }
+
+
for (;; ) {
/* stop updating when reached the end of the window (check for _past_
* the end of the window is at the end of the loop) */
@@ -1544,6 +1629,7 @@ static void win_update(win_T *wp)
* changes are relevant).
*/
wp->w_valid |= VALID_BOTLINE;
+ wp->w_viewport_invalid = true;
if (wp == curwin && wp->w_botline != old_botline && !recursive) {
recursive = TRUE;
curwin->w_valid &= ~VALID_TOPLINE;
@@ -1563,7 +1649,7 @@ static void win_update(win_T *wp)
/* restore got_int, unless CTRL-C was hit while redrawing */
if (!got_int)
got_int = save_got_int;
-}
+} // NOLINT(readability/fn_size)
/// Returns width of the signcolumn that should be used for the whole window
///
@@ -1655,11 +1741,36 @@ static int advance_color_col(int vcol, int **color_cols)
return **color_cols >= 0;
}
+// Returns the next grid column.
+static int text_to_screenline(win_T *wp, char_u *text, int col, int off)
+ FUNC_ATTR_NONNULL_ALL
+{
+ int idx = wp->w_p_rl ? off : off + col;
+ LineState s = LINE_STATE(text);
+
+ while (*s.p != NUL) {
+ // TODO(bfredl): cargo-culted from the old Vim code:
+ // if(col + cells > wp->w_width - (wp->w_p_rl ? col : 0)) { break; }
+ // This is obvious wrong. If Vim ever fixes this, solve for "cells" again
+ // in the correct condition.
+ const int maxcells = wp->w_grid.Columns - col - (wp->w_p_rl ? col : 0);
+ const int cells = line_putchar(&s, &linebuf_char[idx], maxcells,
+ wp->w_p_rl);
+ if (cells == -1) {
+ break;
+ }
+ col += cells;
+ idx += cells;
+ }
+
+ return col;
+}
+
// Compute the width of the foldcolumn. Based on 'foldcolumn' and how much
// space is available for window "wp", minus "col".
static int compute_foldcolumn(win_T *wp, int col)
{
- int fdc = wp->w_p_fdc;
+ int fdc = win_fdccol_count(wp);
int wmw = wp == curwin && p_wmw == 0 ? 1 : p_wmw;
int wwidth = wp->w_grid.Columns;
@@ -1755,27 +1866,6 @@ static void fold_line(win_T *wp, long fold_count, foldinfo_T *foldinfo, linenr_T
col++;
}
- // 2. Add the 'foldcolumn'
- // Reduce the width when there is not enough space.
- fdc = compute_foldcolumn(wp, col);
- if (fdc > 0) {
- fill_foldcolumn(buf, wp, TRUE, lnum);
- if (wp->w_p_rl) {
- int i;
-
- copy_text_attr(off + wp->w_grid.Columns - fdc - col, buf, fdc,
- win_hl_attr(wp, HLF_FC));
- // reverse the fold column
- for (i = 0; i < fdc; i++) {
- schar_from_ascii(linebuf_char[off + wp->w_grid.Columns - i - 1 - col],
- buf[i]);
- }
- } else {
- copy_text_attr(off + col, buf, fdc, win_hl_attr(wp, HLF_FC));
- }
- col += fdc;
- }
-
# define RL_MEMSET(p, v, l) \
do { \
if (wp->w_p_rl) { \
@@ -1789,6 +1879,25 @@ static void fold_line(win_T *wp, long fold_count, foldinfo_T *foldinfo, linenr_T
} \
} while (0)
+ // 2. Add the 'foldcolumn'
+ // Reduce the width when there is not enough space.
+ fdc = compute_foldcolumn(wp, col);
+ if (fdc > 0) {
+ fill_foldcolumn(buf, wp, true, lnum);
+ const char_u *it = &buf[0];
+ for (int i = 0; i < fdc; i++) {
+ int mb_c = mb_ptr2char_adv(&it);
+ if (wp->w_p_rl) {
+ schar_from_char(linebuf_char[off + wp->w_grid.Columns - i - 1 - col],
+ mb_c);
+ } else {
+ schar_from_char(linebuf_char[off + col + i], mb_c);
+ }
+ }
+ RL_MEMSET(col, win_hl_attr(wp, HLF_FC), fdc);
+ col += fdc;
+ }
+
/* Set all attributes of the 'number' or 'relativenumber' column and the
* text */
RL_MEMSET(col, win_hl_attr(wp, HLF_FL), wp->w_grid.Columns - col);
@@ -1858,29 +1967,7 @@ static void fold_line(win_T *wp, long fold_count, foldinfo_T *foldinfo, linenr_T
// 5. move the text to linebuf_char[off]. Fill up with "fold".
// Right-left text is put in columns 0 - number-col, normal text is put
// in columns number-col - window-width.
- int idx;
-
- if (wp->w_p_rl) {
- idx = off;
- } else {
- idx = off + col;
- }
-
- LineState s = LINE_STATE(text);
-
- while (*s.p != NUL) {
- // TODO(bfredl): cargo-culted from the old Vim code:
- // if(col + cells > wp->w_width - (wp->w_p_rl ? col : 0)) { break; }
- // This is obvious wrong. If Vim ever fixes this, solve for "cells" again
- // in the correct condition.
- int maxcells = wp->w_grid.Columns - col - (wp->w_p_rl ? col : 0);
- int cells = line_putchar(&s, &linebuf_char[idx], maxcells, wp->w_p_rl);
- if (cells == -1) {
- break;
- }
- col += cells;
- idx += cells;
- }
+ col = text_to_screenline(wp, text, col, off);
/* Fill the rest of the line with the fold filler */
if (wp->w_p_rl)
@@ -2009,58 +2096,73 @@ static void copy_text_attr(int off, char_u *buf, int len, int attr)
}
}
-/*
- * Fill the foldcolumn at "p" for window "wp".
- * Only to be called when 'foldcolumn' > 0.
- */
-static void
-fill_foldcolumn (
+/// 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
+static size_t
+fill_foldcolumn(
char_u *p,
win_T *wp,
- int closed, /* TRUE of FALSE */
- linenr_T lnum /* current line number */
+ int closed,
+ linenr_T lnum
)
{
int i = 0;
int level;
int first_level;
- int empty;
- int fdc = compute_foldcolumn(wp, 0);
-
+ int fdc = compute_foldcolumn(wp, 0); // available cell width
+ size_t char_counter = 0;
+ int symbol = 0;
+ int len = 0;
// Init to all spaces.
- memset(p, ' ', (size_t)fdc);
+ memset(p, ' ', MAX_MCO * fdc + 1);
level = win_foldinfo.fi_level;
- if (level > 0) {
- // If there is only one column put more info in it.
- empty = (fdc == 1) ? 0 : 1;
-
- // If the column is too narrow, we start at the lowest level that
- // fits and use numbers to indicated the depth.
- first_level = level - fdc - closed + 1 + empty;
- if (first_level < 1) {
- first_level = 1;
- }
-
- for (i = 0; i + empty < fdc; i++) {
- if (win_foldinfo.fi_lnum == lnum
- && first_level + i >= win_foldinfo.fi_low_level) {
- p[i] = '-';
- } else if (first_level == 1) {
- p[i] = '|';
- } else if (first_level + i <= 9) {
- p[i] = '0' + first_level + i;
- } else {
- p[i] = '>';
- }
- if (first_level + i == level) {
- break;
- }
+
+ // If the column is too narrow, we start at the lowest level that
+ // fits and use numbers to indicated the depth.
+ first_level = level - fdc - closed + 1;
+ if (first_level < 1) {
+ first_level = 1;
+ }
+
+ for (i = 0; i < MIN(fdc, level); i++) {
+ if (win_foldinfo.fi_lnum == lnum
+ && first_level + i >= win_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 += len;
+ if (first_level + i >= level) {
+ i++;
+ break;
}
}
+
if (closed) {
- p[i >= fdc ? i - 1 : i] = '+';
+ if (symbol != 0) {
+ // rollback previous write
+ char_counter -= len;
+ memset(&p[char_counter], ' ', len);
+ }
+ len = utf_char2bytes(wp->w_p_fcs_chars.foldclosed, &p[char_counter]);
+ char_counter += len;
}
+
+ return MAX(char_counter + (fdc-i), (size_t)fdc);
}
/*
@@ -2117,10 +2219,10 @@ win_line (
int n_skip = 0; /* nr of chars to skip for 'nowrap' */
- int fromcol = 0, tocol = 0; // start/end of inverting
+ int fromcol = -10; // start of inverting
+ int tocol = MAXCOL; // end of inverting
int fromcol_prev = -2; // start of inverting after cursor
- int noinvcur = false; // don't invert the cursor
- pos_T *top, *bot;
+ bool noinvcur = false; // don't invert the cursor
int lnum_in_visual_area = false;
pos_T pos;
long v;
@@ -2165,7 +2267,7 @@ win_line (
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
- int need_showbreak = false; // overlong line, skip first x chars
+ bool need_showbreak = false; // overlong line, skip first x chars
int line_attr = 0; // attribute for the whole line
int line_attr_lowprio = 0; // low-priority attribute for the line
matchitem_T *cur; // points to the match list
@@ -2179,8 +2281,7 @@ win_line (
int prev_c1 = 0; // first composing char for prev_c
bool search_attr_from_match = false; // if search_attr is from :match
- BufhlLineInfo bufhl_info; // bufhl data for this line
- bool has_bufhl = false; // this buffer has highlight matches
+ bool has_decorations = false; // this buffer has decorations
bool do_virttext = false; // draw virtual text for this line
/* draw_state: items that are drawn in sequence: */
@@ -2221,6 +2322,10 @@ win_line (
row = startrow;
+ char *luatext = NULL;
+
+ buf_T *buf = wp->w_buffer;
+
if (!number_only) {
// To speed up the loop below, set extra_check when there is linebreak,
// trailing white space and/or syntax processing to be done.
@@ -2242,13 +2347,34 @@ win_line (
}
}
- if (bufhl_start_line(wp->w_buffer, lnum, &bufhl_info)) {
- if (kv_size(bufhl_info.line->items)) {
- has_bufhl = true;
+ if (decorations_active) {
+ if (buf->b_luahl && buf->b_luahl_line != LUA_NOREF) {
+ Error err = ERROR_INIT;
+ FIXED_TEMP_ARRAY(args, 3);
+ args.items[0] = WINDOW_OBJ(wp->handle);
+ args.items[1] = BUFFER_OBJ(buf->handle);
+ args.items[2] = INTEGER_OBJ(lnum-1);
+ lua_attr_active = true;
extra_check = true;
+ Object o = executor_exec_lua_cb(buf->b_luahl_line, "line",
+ args, true, &err);
+ lua_attr_active = false;
+ if (o.type == kObjectTypeString) {
+ // TODO(bfredl): this is a bit of a hack. A final API should use an
+ // "unified" interface where luahl can add both bufhl and virttext
+ luatext = o.data.string.data;
+ do_virttext = true;
+ } else if (ERROR_SET(&err)) {
+ ELOG("error in luahl line: %s", err.msg);
+ luatext = err.msg;
+ do_virttext = true;
+ }
}
- if (kv_size(bufhl_info.line->virt_text)) {
- do_virttext = true;
+
+ has_decorations = decorations_redraw_line(wp->w_buffer, lnum-1,
+ &decorations);
+ if (has_decorations) {
+ extra_check = true;
}
}
@@ -2294,27 +2420,28 @@ win_line (
capcol_lnum = 0;
}
- //
- // handle visual active in this window
- //
- fromcol = -10;
- tocol = MAXCOL;
+ // handle Visual active in this window
if (VIsual_active && wp->w_buffer == curwin->w_buffer) {
- // Visual is after curwin->w_cursor
+ pos_T *top, *bot;
+
if (ltoreq(curwin->w_cursor, VIsual)) {
+ // Visual is after curwin->w_cursor
top = &curwin->w_cursor;
bot = &VIsual;
- } else { // Visual is before curwin->w_cursor
+ } else {
+ // Visual is before curwin->w_cursor
top = &VIsual;
bot = &curwin->w_cursor;
}
lnum_in_visual_area = (lnum >= top->lnum && lnum <= bot->lnum);
- if (VIsual_mode == Ctrl_V) { // block mode
+ if (VIsual_mode == Ctrl_V) {
+ // block mode
if (lnum_in_visual_area) {
fromcol = wp->w_old_cursor_fcol;
tocol = wp->w_old_cursor_lcol;
}
- } else { // non-block mode
+ } else {
+ // non-block mode
if (lnum > top->lnum && lnum <= bot->lnum) {
fromcol = 0;
} else if (lnum == top->lnum) {
@@ -2372,8 +2499,6 @@ win_line (
pos.lnum = lnum;
pos.col = search_match_endcol;
getvcol(curwin, &pos, (colnr_T *)&tocol, NULL, NULL);
- } else {
- tocol = MAXCOL;
}
// do at least one character; happens when past end of line
if (fromcol == tocol) {
@@ -2403,10 +2528,10 @@ win_line (
filler_todo = filler_lines;
// Cursor line highlighting for 'cursorline' in the current window.
- if (wp->w_p_cul && lnum == wp->w_cursor.lnum) {
+ if (lnum == wp->w_cursor.lnum) {
// Do not show the cursor line when Visual mode is active, because it's
// not clear what is selected then.
- if (!(wp == curwin && VIsual_active)) {
+ if (wp->w_p_cul && !(wp == curwin && VIsual_active)) {
int cul_attr = win_hl_attr(wp, HLF_CUL);
HlAttrs ae = syn_attr2entry(cul_attr);
@@ -2544,11 +2669,12 @@ win_line (
else if (fromcol >= 0 && fromcol < vcol)
fromcol = vcol;
- /* When w_skipcol is non-zero, first line needs 'showbreak' */
- if (wp->w_p_wrap)
- 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. */
+ // When w_skipcol is non-zero, first line needs 'showbreak'
+ if (wp->w_p_wrap) {
+ 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) {
size_t len;
colnr_T linecol = (colnr_T)(ptr - line);
@@ -2678,8 +2804,8 @@ win_line (
off += col;
}
- // wont highlight after 1024 columns
- int term_attrs[1024] = {0};
+ // wont highlight after TERM_ATTRS_MAX columns
+ int term_attrs[TERM_ATTRS_MAX] = { 0 };
if (wp->w_buffer->terminal) {
terminal_get_line_attributes(wp->w_buffer->terminal, wp, lnum, term_attrs);
extra_check = true;
@@ -2711,9 +2837,8 @@ win_line (
// Draw the 'foldcolumn'. Allocate a buffer, "extra" may
// already be in use.
xfree(p_extra_free);
- p_extra_free = xmalloc(12 + 1);
- fill_foldcolumn(p_extra_free, wp, false, lnum);
- n_extra = fdc;
+ p_extra_free = xmalloc(MAX_MCO * fdc + 1);
+ n_extra = fill_foldcolumn(p_extra_free, wp, false, lnum);
p_extra_free[n_extra] = NUL;
p_extra = p_extra_free;
c_extra = NUL;
@@ -2841,11 +2966,11 @@ win_line (
}
}
- if (wp->w_p_brisbr && draw_state == WL_BRI - 1
+ if (wp->w_briopt_sbr && draw_state == WL_BRI - 1
&& n_extra == 0 && *p_sbr != NUL) {
// draw indent after showbreak value
draw_state = WL_BRI;
- } else if (wp->w_p_brisbr && draw_state == WL_SBR && n_extra == 0) {
+ } else if (wp->w_briopt_sbr && draw_state == WL_SBR && n_extra == 0) {
// after the showbreak, draw the breakindent
draw_state = WL_BRI - 1;
}
@@ -2866,11 +2991,23 @@ win_line (
}
p_extra = NULL;
c_extra = ' ';
- n_extra = get_breakindent_win(wp, ml_get_buf(wp->w_buffer, lnum, FALSE));
- /* Correct end of highlighted area for 'breakindent',
- required wen 'linebreak' is also set. */
- if (tocol == vcol)
+ 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;
+ }
}
}
@@ -2899,7 +3036,9 @@ win_line (
c_final = NUL;
n_extra = (int)STRLEN(p_sbr);
char_attr = win_hl_attr(wp, HLF_AT);
- need_showbreak = false;
+ if (wp->w_skipcol == 0 || !wp->w_p_wrap) {
+ need_showbreak = false;
+ }
vcol_sbr = vcol + MB_CHARLEN(p_sbr);
/* Correct end of highlighted area for 'showbreak',
* required when 'linebreak' is also set. */
@@ -2929,10 +3068,12 @@ win_line (
}
// 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
- && filler_todo <= 0)
- || (number_only && draw_state > WL_NR)) {
+ if (((dollar_vcol >= 0
+ && wp == curwin
+ && lnum == wp->w_cursor.lnum
+ && vcol >= (long)wp->w_virtcol)
+ || (number_only && draw_state > WL_NR))
+ && filler_todo <= 0) {
grid_put_linebuf(grid, row, 0, col, -grid->Columns, wp->w_p_rl, wp,
wp->w_hl_attr_normal, false);
// Pretend we have finished updating the window. Except when
@@ -2991,7 +3132,7 @@ win_line (
if (shl->startcol != MAXCOL
&& v >= (long)shl->startcol
&& v < (long)shl->endcol) {
- int tmp_col = v + MB_PTR2LEN(ptr);
+ int tmp_col = v + utfc_ptr2len(ptr);
if (shl->endcol < tmp_col) {
shl->endcol = tmp_col;
@@ -3031,8 +3172,8 @@ win_line (
shl->endcol += (*mb_ptr2len)(line + shl->endcol);
}
- /* Loop to check if the match starts at the
- * current position */
+ // Loop to check if the match starts at the
+ // current position
continue;
}
}
@@ -3176,9 +3317,7 @@ win_line (
} else {
int c0;
- if (p_extra_free != NULL) {
- XFREE_CLEAR(p_extra_free);
- }
+ XFREE_CLEAR(p_extra_free);
// Get a character from the line itself.
c0 = c = *ptr;
@@ -3339,6 +3478,7 @@ win_line (
* Only do this when there is no syntax highlighting, the
* @Spell cluster is not used or the current syntax item
* contains the @Spell cluster. */
+ v = (long)(ptr - line);
if (has_spell && v >= word_end && v > cur_checked_col) {
spell_attr = 0;
if (!attr_pri) {
@@ -3410,13 +3550,14 @@ win_line (
char_attr = hl_combine_attr(spell_attr, char_attr);
}
- if (has_bufhl && v > 0) {
- int bufhl_attr = bufhl_get_attr(&bufhl_info, (colnr_T)v);
- if (bufhl_attr != 0) {
+ if (has_decorations && v > 0) {
+ int extmark_attr = decorations_redraw_col(wp->w_buffer, (colnr_T)v-1,
+ &decorations);
+ if (extmark_attr != 0) {
if (!attr_pri) {
- char_attr = hl_combine_attr(char_attr, bufhl_attr);
+ char_attr = hl_combine_attr(char_attr, extmark_attr);
} else {
- char_attr = hl_combine_attr(bufhl_attr, char_attr);
+ char_attr = hl_combine_attr(extmark_attr, char_attr);
}
}
}
@@ -3520,8 +3661,9 @@ win_line (
tab_len += n_extra - tab_len;
}
- /* if n_extra > 0, it gives the number of chars to use for
- * a tab, else we need to calculate the width for a tab */
+ // if n_extra > 0, it gives the number of chars
+ // to use for a tab, else we need to calculate the width
+ // for a tab
int len = (tab_len * mb_char2len(wp->w_p_lcs_chars.tab2));
if (n_extra > 0) {
len += n_extra - tab_len;
@@ -3533,10 +3675,16 @@ win_line (
xfree(p_extra_free);
p_extra_free = p;
for (i = 0; i < tab_len; i++) {
- utf_char2bytes(wp->w_p_lcs_chars.tab2, p);
- p += mb_char2len(wp->w_p_lcs_chars.tab2);
- n_extra += mb_char2len(wp->w_p_lcs_chars.tab2)
- - (saved_nextra > 0 ? 1: 0);
+ int lcs = wp->w_p_lcs_chars.tab2;
+
+ // if tab3 is given, need to change the char
+ // for tab
+ if (wp->w_p_lcs_chars.tab3 && i == tab_len - 1) {
+ lcs = wp->w_p_lcs_chars.tab3;
+ }
+ utf_char2bytes(lcs, p);
+ p += mb_char2len(lcs);
+ n_extra += mb_char2len(lcs) - (saved_nextra > 0 ? 1 : 0);
}
p_extra = p_extra_free;
@@ -3749,6 +3897,7 @@ win_line (
}
wp->w_wrow = row;
did_wcol = true;
+ curwin->w_valid |= VALID_WCOL|VALID_WROW|VALID_VIRTCOL;
}
// Don't override visual selection highlighting.
@@ -3756,14 +3905,12 @@ win_line (
char_attr = hl_combine_attr(char_attr, extra_attr);
}
- /*
- * Handle the case where we are in column 0 but not on the first
- * character of the line and the user wants us to show us a
- * special character (via 'listchars' option "precedes:<char>".
- */
+ // Handle the case where we are in column 0 but not on the first
+ // character of the line and the user wants us to show us a
+ // special character (via 'listchars' option "precedes:<char>".
if (lcs_prec_todo != NUL
&& wp->w_p_list
- && (wp->w_p_wrap ? wp->w_skipcol > 0 : wp->w_leftcol > 0)
+ && (wp->w_p_wrap ? (wp->w_skipcol > 0 && row == 0) : wp->w_leftcol > 0)
&& filler_todo <= 0
&& draw_state > WL_NR
&& c != NUL) {
@@ -3901,6 +4048,18 @@ win_line (
if (draw_color_col)
draw_color_col = advance_color_col(VCOL_HLC, &color_cols);
+ VirtText virt_text = KV_INITIAL_VALUE;
+ if (luatext) {
+ kv_push(virt_text, ((VirtTextChunk){ .text = luatext, .hl_id = 0 }));
+ do_virttext = true;
+ } else if (has_decorations) {
+ VirtText *vp = decorations_redraw_virt_text(wp->w_buffer, &decorations);
+ if (vp) {
+ virt_text = *vp;
+ do_virttext = true;
+ }
+ }
+
if (((wp->w_p_cuc
&& (int)wp->w_virtcol >= VCOL_HLC - eol_hl_off
&& (int)wp->w_virtcol <
@@ -3911,8 +4070,6 @@ win_line (
int rightmost_vcol = 0;
int i;
- VirtText virt_text = do_virttext ? bufhl_info.line->virt_text
- : (VirtText)KV_INITIAL_VALUE;
size_t virt_pos = 0;
LineState s = LINE_STATE((char_u *)"");
int virt_attr = 0;
@@ -4007,7 +4164,7 @@ win_line (
break;
}
- ++vcol;
+ vcol += cells;
}
}
@@ -4015,10 +4172,13 @@ win_line (
if (wp->w_buffer->terminal) {
// terminal buffers may need to highlight beyond the end of the
// logical line
- while (col < grid->Columns) {
+ int n = wp->w_p_rl ? -1 : 1;
+ while (col >= 0 && col < grid->Columns) {
schar_from_ascii(linebuf_char[off], ' ');
- linebuf_attr[off++] = term_attrs[vcol++];
- col++;
+ linebuf_attr[off] = vcol >= TERM_ATTRS_MAX ? 0 : term_attrs[vcol];
+ off += n;
+ vcol += n;
+ col += n;
}
}
grid_put_linebuf(grid, row, 0, col, grid->Columns, wp->w_p_rl, wp,
@@ -4310,6 +4470,7 @@ win_line (
}
xfree(p_extra_free);
+ xfree(luatext);
return row;
}
@@ -4353,7 +4514,7 @@ static int grid_char_needs_redraw(ScreenGrid *grid, int off_from, int off_to,
|| (line_off2cells(linebuf_char, off_from, off_from + cols) > 1
&& schar_cmp(linebuf_char[off_from + 1],
grid->chars[off_to + 1])))
- || p_wd < 0));
+ || rdb_flags & RDB_NODELTA));
}
/// Move one buffered line to the window grid, but only the characters that
@@ -5103,6 +5264,8 @@ win_redr_custom (
win_T *ewp;
int p_crb_save;
+ ScreenGrid *grid = &default_grid;
+
/* There is a tiny chance that this gets called recursively: When
* redrawing a status line triggers redrawing the ruler or tabline.
* Avoid trouble by not allowing recursion. */
@@ -5142,10 +5305,11 @@ win_redr_custom (
}
maxwidth = wp->w_width - col;
if (!wp->w_status_height) {
+ grid = &msg_grid_adj;
row = Rows - 1;
maxwidth--; // writing in last column may cause scrolling
fillchar = ' ';
- attr = 0;
+ attr = HL_ATTR(HLF_MSG);
}
use_sandbox = was_set_insecurely((char_u *)"rulerformat", 0);
@@ -5195,13 +5359,13 @@ win_redr_custom (
/*
* Draw each snippet with the specified highlighting.
*/
- grid_puts_line_start(&default_grid, row);
+ grid_puts_line_start(grid, row);
curattr = attr;
p = buf;
for (n = 0; hltab[n].start != NULL; n++) {
int textlen = (int)(hltab[n].start - p);
- grid_puts_len(&default_grid, p, textlen, row, col, curattr);
+ grid_puts_len(grid, p, textlen, row, col, curattr);
col += vim_strnsize(p, textlen);
p = hltab[n].start;
@@ -5215,7 +5379,7 @@ win_redr_custom (
curattr = highlight_user[hltab[n].userhl - 1];
}
// Make sure to use an empty string instead of p, if p is beyond buf + len.
- grid_puts(&default_grid, p >= buf + len ? (char_u *)"" : p, row, col,
+ grid_puts(grid, p >= buf + len ? (char_u *)"" : p, row, col,
curattr);
grid_puts_line_flush(false);
@@ -5559,6 +5723,7 @@ void grid_puts_line_flush(bool set_cursor)
static void start_search_hl(void)
{
if (p_hls && !no_hlsearch) {
+ end_search_hl(); // just in case it wasn't called before
last_pat_prog(&search_hl.rm);
// Set the time limit to 'redrawtime'.
search_hl.tm = profile_setlimit(p_rdt);
@@ -5581,12 +5746,11 @@ static void end_search_hl(void)
* Init for calling prepare_search_hl().
*/
static void init_search_hl(win_T *wp)
+ FUNC_ATTR_NONNULL_ALL
{
- matchitem_T *cur;
-
- /* Setup for match and 'hlsearch' highlighting. Disable any previous
- * match */
- cur = wp->w_match_head;
+ // Setup for match and 'hlsearch' highlighting. Disable any previous
+ // match
+ matchitem_T *cur = wp->w_match_head;
while (cur != NULL) {
cur->hl.rm = cur->match;
if (cur->hlg_id == 0)
@@ -5596,7 +5760,7 @@ static void init_search_hl(win_T *wp)
cur->hl.buf = wp->w_buffer;
cur->hl.lnum = 0;
cur->hl.first_lnum = 0;
- /* Set the time limit to 'redrawtime'. */
+ // Set the time limit to 'redrawtime'.
cur->hl.tm = profile_setlimit(p_rdt);
cur = cur->next;
}
@@ -5612,18 +5776,16 @@ static void init_search_hl(win_T *wp)
* Advance to the match in window "wp" line "lnum" or past it.
*/
static void prepare_search_hl(win_T *wp, linenr_T lnum)
+ FUNC_ATTR_NONNULL_ALL
{
- matchitem_T *cur; /* points to the match list */
- match_T *shl; /* points to search_hl or a match */
- int shl_flag; /* flag to indicate whether search_hl
- has been processed or not */
- int n;
-
- /*
- * When using a multi-line pattern, start searching at the top
- * of the window or just after a closed fold.
- * Do this both for search_hl and the match list.
- */
+ matchitem_T *cur; // points to the match list
+ match_T *shl; // points to search_hl or a match
+ bool shl_flag; // flag to indicate whether search_hl
+ // has been processed or not
+
+ // When using a multi-line pattern, start searching at the top
+ // of the window or just after a closed fold.
+ // Do this both for search_hl and the match list.
cur = wp->w_match_head;
shl_flag = false;
while (cur != NULL || shl_flag == false) {
@@ -5650,7 +5812,7 @@ static void prepare_search_hl(win_T *wp, linenr_T lnum)
}
bool pos_inprogress = true; // mark that a position match search is
// in progress
- n = 0;
+ int n = 0;
while (shl->first_lnum < lnum && (shl->rm.regprog != NULL
|| (cur != NULL && pos_inprogress))) {
next_search_hl(wp, shl, shl->first_lnum, (colnr_T)n,
@@ -5688,6 +5850,7 @@ next_search_hl (
colnr_T mincol, /* minimal column for a match */
matchitem_T *cur /* to retrieve match positions if any */
)
+ FUNC_ATTR_NONNULL_ARG(2)
{
linenr_T l;
colnr_T matchcol;
@@ -5695,11 +5858,10 @@ next_search_hl (
int save_called_emsg = called_emsg;
if (shl->lnum != 0) {
- /* Check for three situations:
- * 1. If the "lnum" is below a previous match, start a new search.
- * 2. If the previous match includes "mincol", use it.
- * 3. Continue after the previous match.
- */
+ // Check for three situations:
+ // 1. If the "lnum" is below a previous match, start a new search.
+ // 2. If the previous match includes "mincol", use it.
+ // 3. Continue after the previous match.
l = shl->lnum + shl->rm.endpos[0].lnum - shl->rm.startpos[0].lnum;
if (lnum > l)
shl->lnum = 0;
@@ -5713,22 +5875,21 @@ next_search_hl (
*/
called_emsg = FALSE;
for (;; ) {
- /* Stop searching after passing the time limit. */
+ // Stop searching after passing the time limit.
if (profile_passed_limit(shl->tm)) {
shl->lnum = 0; /* no match found in time */
break;
}
- /* Three situations:
- * 1. No useful previous match: search from start of line.
- * 2. Not Vi compatible or empty match: continue at next character.
- * Break the loop if this is beyond the end of the line.
- * 3. Vi compatible searching: continue at end of previous match.
- */
- if (shl->lnum == 0)
+ // Three situations:
+ // 1. No useful previous match: search from start of line.
+ // 2. Not Vi compatible or empty match: continue at next character.
+ // Break the loop if this is beyond the end of the line.
+ // 3. Vi compatible searching: continue at end of previous match.
+ if (shl->lnum == 0) {
matchcol = 0;
- else if (vim_strchr(p_cpo, CPO_SEARCH) == NULL
- || (shl->rm.endpos[0].lnum == 0
- && shl->rm.endpos[0].col <= shl->rm.startpos[0].col)) {
+ } else if (vim_strchr(p_cpo, CPO_SEARCH) == NULL
+ || (shl->rm.endpos[0].lnum == 0
+ && shl->rm.endpos[0].col <= shl->rm.startpos[0].col)) {
char_u *ml;
matchcol = shl->rm.startpos[0].col;
@@ -5745,8 +5906,8 @@ next_search_hl (
shl->lnum = lnum;
if (shl->rm.regprog != NULL) {
- /* Remember whether shl->rm is using a copy of the regprog in
- * cur->match. */
+ // Remember whether shl->rm is using a copy of the regprog in
+ // cur->match.
bool regprog_is_copy = (shl != &search_hl
&& cur != NULL
&& shl == &cur->hl
@@ -5775,7 +5936,7 @@ next_search_hl (
nmatched = next_search_hl_pos(shl, lnum, &(cur->pos), matchcol);
}
if (nmatched == 0) {
- shl->lnum = 0; /* no match found */
+ shl->lnum = 0; // no match found
break;
}
if (shl->rm.startpos[0].lnum > 0
@@ -5783,7 +5944,7 @@ next_search_hl (
|| nmatched > 1
|| shl->rm.endpos[0].col > mincol) {
shl->lnum += shl->rm.startpos[0].lnum;
- break; /* useful match found */
+ break; // useful match found
}
// Restore called_emsg for assert_fails().
@@ -5800,6 +5961,7 @@ next_search_hl_pos(
posmatch_T *posmatch, // match positions
colnr_T mincol // minimal column for a match
)
+ FUNC_ATTR_NONNULL_ALL
{
int i;
int found = -1;
@@ -5954,9 +6116,10 @@ void check_for_delay(int check_msg_scroll)
&& emsg_silent == 0) {
ui_flush();
os_delay(1000L, true);
- emsg_on_display = FALSE;
- if (check_msg_scroll)
- msg_scroll = FALSE;
+ emsg_on_display = false;
+ if (check_msg_scroll) {
+ msg_scroll = false;
+ }
}
}
@@ -7060,7 +7223,7 @@ static void win_redr_ruler(win_T *wp, int always)
} else {
row = Rows - 1;
fillchar = ' ';
- attr = 0;
+ attr = HL_ATTR(HLF_MSG);
width = Columns;
off = 0;
}
diff --git a/src/nvim/screen.h b/src/nvim/screen.h
index 61ed98247d..5f339b9ae2 100644
--- a/src/nvim/screen.h
+++ b/src/nvim/screen.h
@@ -32,6 +32,9 @@ EXTERN ScreenGrid default_grid INIT(= SCREEN_GRID_INIT);
#define DEFAULT_GRID_HANDLE 1 // handle for the default_grid
+// Maximum columns for terminal highlight attributes
+#define TERM_ATTRS_MAX 1024
+
/// Status line click definition
typedef struct {
enum {
@@ -56,6 +59,9 @@ extern StlClickDefinition *tab_page_click_defs;
/// Size of the tab_page_click_defs array
extern long tab_page_click_defs_size;
+#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
diff --git a/src/nvim/search.c b/src/nvim/search.c
index 7d1c19d68c..b105d99d7c 100644
--- a/src/nvim/search.c
+++ b/src/nvim/search.c
@@ -516,19 +516,17 @@ 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, /* window to search in, can be NULL for a
- buffer without a window! */
+ win_T *win, // window to search in; can be NULL for a
+ // buffer without a window!
buf_T *buf,
pos_T *pos,
- pos_T *end_pos, // set to end of the match, unless NULL
+ pos_T *end_pos, // set to end of the match, unless NULL
Direction dir,
char_u *pat,
long count,
int options,
- int pat_use, // which pattern to use when "pat" is empty
- linenr_T stop_lnum, // stop after this line number when != 0
- proftime_T *tm, // timeout limit or NULL
- int *timed_out // set when timed out or NULL
+ int pat_use, // which pattern to use when "pat" is empty
+ searchit_arg_T *extra_arg // optional extra arguments, can be NULL
)
{
int found;
@@ -548,7 +546,16 @@ int searchit(
int submatch = 0;
bool first_match = true;
int save_called_emsg = called_emsg;
- int break_loop = FALSE;
+ int break_loop = false;
+ linenr_T stop_lnum = 0; // stop after this line number when != 0
+ proftime_T *tm = NULL; // timeout limit or NULL
+ int *timed_out = NULL; // set when timed out or NULL
+
+ if (extra_arg != NULL) {
+ stop_lnum = extra_arg->sa_stop_lnum;
+ tm = extra_arg->sa_tm;
+ timed_out = &extra_arg->sa_timed_out;
+ }
if (search_regcomp(pat, RE_SEARCH, pat_use,
(options & (SEARCH_HIS + SEARCH_KEEP)), &regmatch) == FAIL) {
@@ -681,7 +688,7 @@ int searchit(
}
if (matchcol == matchpos.col && ptr[matchcol] != NUL) {
- matchcol += MB_PTR2LEN(ptr + matchcol);
+ matchcol += utfc_ptr2len(ptr + matchcol);
}
if (matchcol == 0 && (options & SEARCH_START)) {
@@ -889,6 +896,9 @@ int searchit(
give_warning((char_u *)_(dir == BACKWARD
? top_bot_msg : bot_top_msg), true);
}
+ if (extra_arg != NULL) {
+ extra_arg->sa_wrapped = true;
+ }
}
if (got_int || called_emsg
|| (timed_out != NULL && *timed_out)
@@ -983,8 +993,7 @@ int do_search(
char_u *pat,
long count,
int options,
- proftime_T *tm, // timeout limit or NULL
- int *timed_out // flag set on timeout or NULL
+ searchit_arg_T *sia // optional arguments or NULL
)
{
pos_T pos; /* position of the last match */
@@ -1183,6 +1192,7 @@ int do_search(
len = STRLEN(p) + off_len + 3;
}
+ xfree(msgbuf);
msgbuf = xmalloc(len);
{
memset(msgbuf, ' ', len);
@@ -1271,7 +1281,7 @@ int do_search(
& (SEARCH_KEEP + SEARCH_PEEK + SEARCH_HIS + SEARCH_MSG
+ SEARCH_START
+ ((pat != NULL && *pat == ';') ? 0 : SEARCH_NOOF)))),
- RE_LAST, (linenr_T)0, tm, timed_out);
+ RE_LAST, sia);
if (dircp != NULL) {
*dircp = dirc; // restore second '/' or '?' for normal_cmd()
@@ -1755,7 +1765,7 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel)
find_mps_values(&initc, &findc, &backwards, FALSE);
if (findc)
break;
- pos.col += MB_PTR2LEN(linep + pos.col);
+ pos.col += utfc_ptr2len(linep + pos.col);
}
if (!findc) {
/* no brace in the line, maybe use " #if" then */
@@ -2166,17 +2176,14 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel)
* Return MAXCOL if not, otherwise return the column.
* TODO: skip strings.
*/
-static int check_linecomment(char_u *line)
+static int check_linecomment(const char_u *line)
{
- char_u *p;
-
- p = line;
- /* skip Lispish one-line comments */
+ const char_u *p = line; // scan from start
+ // skip Lispish one-line comments
if (curbuf->b_p_lisp) {
if (vim_strchr(p, ';') != NULL) { /* there may be comments */
int in_str = FALSE; /* inside of string */
- p = line; /* scan from start */
while ((p = vim_strpbrk(p, (char_u *)"\";")) != NULL) {
if (*p == '"') {
if (in_str) {
@@ -2221,6 +2228,8 @@ showmatch(
pos_T *lpos, save_cursor;
pos_T mpos;
colnr_T vcol;
+ long *so = curwin->w_p_so >= 0 ? &curwin->w_p_so : &p_so;
+ long *siso = curwin->w_p_siso >= 0 ? &curwin->w_p_siso : &p_siso;
long save_so;
long save_siso;
int save_state;
@@ -2234,14 +2243,14 @@ showmatch(
for (p = curbuf->b_p_mps; *p != NUL; ++p) {
if (PTR2CHAR(p) == c && (curwin->w_p_rl ^ p_ri))
break;
- p += MB_PTR2LEN(p) + 1;
- if (PTR2CHAR(p) == c
- && !(curwin->w_p_rl ^ p_ri)
- )
+ p += utfc_ptr2len(p) + 1;
+ if (PTR2CHAR(p) == c && !(curwin->w_p_rl ^ p_ri)) {
break;
- p += MB_PTR2LEN(p);
- if (*p == NUL)
+ }
+ p += utfc_ptr2len(p);
+ if (*p == NUL) {
return;
+ }
}
if ((lpos = findmatch(NULL, NUL)) == NULL) { // no match, so beep
@@ -2256,23 +2265,24 @@ showmatch(
&& vcol < curwin->w_leftcol + curwin->w_width_inner)) {
mpos = *lpos; // save the pos, update_screen() may change it
save_cursor = curwin->w_cursor;
- save_so = p_so;
- save_siso = p_siso;
- /* Handle "$" in 'cpo': If the ')' is typed on top of the "$",
- * stop displaying the "$". */
- if (dollar_vcol >= 0 && dollar_vcol == curwin->w_virtcol)
+ save_so = *so;
+ save_siso = *siso;
+ // Handle "$" in 'cpo': If the ')' is typed on top of the "$",
+ // stop displaying the "$".
+ if (dollar_vcol >= 0 && dollar_vcol == curwin->w_virtcol) {
dollar_vcol = -1;
- ++curwin->w_virtcol; /* do display ')' just before "$" */
- update_screen(VALID); /* show the new char first */
+ }
+ curwin->w_virtcol++; // do display ')' just before "$"
+ update_screen(VALID); // show the new char first
save_dollar_vcol = dollar_vcol;
save_state = State;
State = SHOWMATCH;
- ui_cursor_shape(); /* may show different cursor shape */
- curwin->w_cursor = mpos; /* move to matching char */
- p_so = 0; /* don't use 'scrolloff' here */
- p_siso = 0; /* don't use 'sidescrolloff' here */
- showruler(FALSE);
+ ui_cursor_shape(); // may show different cursor shape
+ curwin->w_cursor = mpos; // move to matching char
+ *so = 0; // don't use 'scrolloff' here
+ *siso = 0; // don't use 'sidescrolloff' here
+ showruler(false);
setcursor();
ui_flush();
/* Restore dollar_vcol(), because setcursor() may call curs_rows()
@@ -2288,11 +2298,11 @@ showmatch(
os_delay(p_mat * 100L, true);
else if (!char_avail())
os_delay(p_mat * 100L, false);
- curwin->w_cursor = save_cursor; /* restore cursor position */
- p_so = save_so;
- p_siso = save_siso;
+ curwin->w_cursor = save_cursor; // restore cursor position
+ *so = save_so;
+ *siso = save_siso;
State = save_state;
- ui_cursor_shape(); /* may show different cursor shape */
+ ui_cursor_shape(); // may show different cursor shape
}
}
}
@@ -2316,9 +2326,9 @@ int findsent(Direction dir, long count)
func = decl;
while (count--) {
- /*
- * if on an empty line, skip up to a non-empty line
- */
+ const pos_T prev_pos = pos;
+
+ // if on an empty line, skip up to a non-empty line
if (gchar_pos(&pos) == NUL) {
do {
if ((*func)(&pos) == -1) {
@@ -2401,6 +2411,17 @@ found:
while (!noskip && ((c = gchar_pos(&pos)) == ' ' || c == '\t'))
if (incl(&pos) == -1)
break;
+
+ if (equalpos(prev_pos, pos)) {
+ // didn't actually move, advance one character and try again
+ if ((*func)(&pos) == -1) {
+ if (count) {
+ return FAIL;
+ }
+ break;
+ }
+ count++;
+ }
}
setpcmark();
@@ -3768,30 +3789,31 @@ find_prev_quote(
return col_start;
}
-/*
- * Find quote under the cursor, cursor at end.
- * Returns TRUE if found, else FALSE.
- */
-int
-current_quote(
+// Find quote under the cursor, cursor at end.
+// Returns true if found, else false.
+bool current_quote(
oparg_T *oap,
long count,
- int include, /* TRUE == include quote char */
- int quotechar /* Quote character */
+ bool include, // true == include quote char
+ int quotechar // Quote character
)
+ FUNC_ATTR_NONNULL_ALL
{
char_u *line = get_cursor_line_ptr();
int col_end;
int col_start = curwin->w_cursor.col;
bool inclusive = false;
- int vis_empty = true; // Visual selection <= 1 char
- int vis_bef_curs = false; // Visual starts before cursor
- int inside_quotes = false; // Looks like "i'" done before
- int selected_quote = false; // Has quote inside selection
+ bool vis_empty = true; // Visual selection <= 1 char
+ bool vis_bef_curs = false; // Visual starts before cursor
+ bool did_exclusive_adj = false; // adjusted pos for 'selection'
+ bool inside_quotes = false; // Looks like "i'" done before
+ bool selected_quote = false; // Has quote inside selection
int i;
- int restore_vis_bef = false; // resotre VIsual on abort
+ bool restore_vis_bef = false; // resotre VIsual on abort
- // Correct cursor when 'selection' is "exclusive".
+ // When 'selection' is "exclusive" move the cursor to where it would be
+ // with 'selection' "inclusive", so that the logic is the same for both.
+ // The cursor then is moved forward after adjusting the area.
if (VIsual_active) {
// this only works within one line
if (VIsual.lnum != curwin->w_cursor.lnum) {
@@ -3799,8 +3821,17 @@ current_quote(
}
vis_bef_curs = lt(VIsual, curwin->w_cursor);
+ vis_empty = equalpos(VIsual, curwin->w_cursor);
if (*p_sel == 'e') {
- if (!vis_bef_curs) {
+ if (vis_bef_curs) {
+ dec_cursor();
+ did_exclusive_adj = true;
+ } else if (!vis_empty) {
+ dec(&VIsual);
+ did_exclusive_adj = true;
+ }
+ vis_empty = equalpos(VIsual, curwin->w_cursor);
+ if (!vis_bef_curs && !vis_empty) {
// VIsual needs to be start of Visual selection.
pos_T t = curwin->w_cursor;
@@ -3809,9 +3840,7 @@ current_quote(
vis_bef_curs = true;
restore_vis_bef = true;
}
- dec_cursor();
}
- vis_empty = equalpos(VIsual, curwin->w_cursor);
}
if (!vis_empty) {
@@ -3836,7 +3865,7 @@ current_quote(
/* Find out if we have a quote in the selection. */
while (i <= col_end)
if (line[i++] == quotechar) {
- selected_quote = TRUE;
+ selected_quote = true;
break;
}
}
@@ -3925,8 +3954,8 @@ current_quote(
}
}
- /* When "include" is TRUE, include spaces after closing quote or before
- * the starting quote. */
+ // When "include" is true, include spaces after closing quote or before
+ // the starting quote.
if (include) {
if (ascii_iswhite(line[col_end + 1]))
while (ascii_iswhite(line[col_end + 1]))
@@ -3972,9 +4001,10 @@ current_quote(
inclusive = true;
if (VIsual_active) {
if (vis_empty || vis_bef_curs) {
- /* decrement cursor when 'selection' is not exclusive */
- if (*p_sel != 'e')
+ // decrement cursor when 'selection' is not exclusive
+ if (*p_sel != 'e') {
dec_cursor();
+ }
} else {
/* Cursor is at start of Visual area. Set the end of the Visual
* area when it was just inside quotes or it didn't end at a
@@ -3998,11 +4028,13 @@ current_quote(
oap->inclusive = inclusive;
}
- return OK;
+ return true;
abort_search:
if (VIsual_active && *p_sel == 'e') {
- inc_cursor();
+ if (did_exclusive_adj) {
+ inc_cursor();
+ }
if (restore_vis_bef) {
pos_T t = curwin->w_cursor;
@@ -4028,9 +4060,6 @@ current_search(
bool old_p_ws = p_ws;
pos_T save_VIsual = VIsual;
- /* wrapping should not occur */
- p_ws = false;
-
/* Correct cursor when 'selection' is exclusive */
if (VIsual_active && *p_sel == 'e' && lt(VIsual, curwin->w_cursor))
dec_cursor();
@@ -4040,25 +4069,21 @@ current_search(
pos_T pos; // position after the pattern
int result; // result of various function calls
+ orig_pos = pos = curwin->w_cursor;
if (VIsual_active) {
- orig_pos = pos = curwin->w_cursor;
-
// Searching further will extend the match.
if (forward) {
incl(&pos);
} else {
decl(&pos);
}
- } else {
- orig_pos = pos = curwin->w_cursor;
}
// Is the pattern is zero-width?, this time, don't care about the direction
- int one_char = is_one_char(spats[last_idx].pat, true, &curwin->w_cursor,
- FORWARD);
- if (one_char == -1) {
- p_ws = old_p_ws;
- return FAIL; /* pattern not found */
+ int zero_width = is_zero_width(spats[last_idx].pat, true, &curwin->w_cursor,
+ FORWARD);
+ if (zero_width == -1) {
+ return FAIL; // pattern not found
}
/*
@@ -4070,15 +4095,22 @@ current_search(
int dir = forward ? i : !i;
int flags = 0;
- if (!dir && !one_char) {
+ if (!dir && !zero_width) {
flags = SEARCH_END;
}
end_pos = pos;
+ // wrapping should not occur in the first round
+ if (i == 0) {
+ p_ws = false;
+ }
+
result = searchit(curwin, curbuf, &pos, &end_pos,
(dir ? FORWARD : BACKWARD),
spats[last_idx].pat, i ? count : 1,
- SEARCH_KEEP | flags, RE_SEARCH, 0, NULL, NULL);
+ SEARCH_KEEP | flags, RE_SEARCH, NULL);
+
+ p_ws = old_p_ws;
// First search may fail, but then start searching from the
// beginning of the file (cursor might be on the search match)
@@ -4088,7 +4120,6 @@ current_search(
curwin->w_cursor = orig_pos;
if (VIsual_active)
VIsual = save_VIsual;
- p_ws = old_p_ws;
return FAIL;
} else if (i == 0 && !result) {
if (forward) { // try again from start of buffer
@@ -4100,26 +4131,24 @@ current_search(
ml_get(curwin->w_buffer->b_ml.ml_line_count));
}
}
- p_ws = old_p_ws;
}
pos_T start_pos = pos;
- p_ws = old_p_ws;
-
if (!VIsual_active) {
VIsual = start_pos;
}
// put cursor on last character of match
curwin->w_cursor = end_pos;
- if (lt(VIsual, end_pos)) {
+ if (lt(VIsual, end_pos) && forward) {
dec_cursor();
+ } else if (VIsual_active && lt(curwin->w_cursor, VIsual)) {
+ curwin->w_cursor = pos; // put the cursor on the start of the match
}
VIsual_active = true;
VIsual_mode = 'v';
- redraw_curbuf_later(INVERTED); // Update the inversion.
if (*p_sel == 'e') {
// Correction for exclusive selection depends on the direction.
if (forward && ltoreq(VIsual, curwin->w_cursor)) {
@@ -4141,13 +4170,13 @@ current_search(
return OK;
}
-/// Check if the pattern is one character long or zero-width.
+/// Check if the pattern is zero-width.
/// If move is true, check from the beginning of the buffer,
/// else from position "cur".
/// "direction" is FORWARD or BACKWARD.
/// Returns TRUE, FALSE or -1 for failure.
-static int is_one_char(char_u *pattern, bool move, pos_T *cur,
- Direction direction)
+static int
+is_zero_width(char_u *pattern, int move, pos_T *cur, Direction direction)
{
regmmatch_T regmatch;
int nmatched = 0;
@@ -4175,7 +4204,7 @@ static int is_one_char(char_u *pattern, bool move, pos_T *cur,
flag = SEARCH_START;
}
if (searchit(curwin, curbuf, &pos, NULL, direction, pattern, 1,
- SEARCH_KEEP + flag, RE_SEARCH, 0, NULL, NULL) != FAIL) {
+ SEARCH_KEEP + flag, RE_SEARCH, NULL) != FAIL) {
// Zero-width pattern should match somewhere, then we can check if
// start and end are in the same position.
called_emsg = false;
@@ -4184,7 +4213,7 @@ static int is_one_char(char_u *pattern, bool move, pos_T *cur,
nmatched = vim_regexec_multi(&regmatch, curwin, curbuf,
pos.lnum, regmatch.startpos[0].col,
NULL, NULL);
- if (!nmatched) {
+ if (nmatched != 0) {
break;
}
} while (direction == FORWARD
@@ -4195,10 +4224,6 @@ static int is_one_char(char_u *pattern, bool move, pos_T *cur,
result = (nmatched != 0
&& regmatch.startpos[0].lnum == regmatch.endpos[0].lnum
&& regmatch.startpos[0].col == regmatch.endpos[0].col);
- // one char width
- if (!result && inc(&pos) >= 0 && pos.col == regmatch.endpos[0].col) {
- result = true;
- }
}
}
@@ -4262,7 +4287,7 @@ static void search_stat(int dirc, pos_T *pos,
start = profile_setlimit(20L);
while (!got_int && searchit(curwin, curbuf, &lastpos, NULL,
FORWARD, NULL, 1, SEARCH_KEEP, RE_LAST,
- (linenr_T)0, NULL, NULL) != FAIL) {
+ NULL) != FAIL) {
// Stop after passing the time limit.
if (profile_passed_limit(start)) {
cnt = OUT_OF_TIME;
@@ -4458,7 +4483,8 @@ find_pattern_in_path(
if (i == max_path_depth) {
break;
}
- if (path_full_compare(new_fname, files[i].name, true) & kEqualFiles) {
+ if (path_full_compare(new_fname, files[i].name,
+ true, true) & kEqualFiles) {
if (type != CHECK_PATH
&& action == ACTION_SHOW_ALL && files[i].matched) {
msg_putchar('\n'); // cursor below last one */
@@ -4844,7 +4870,7 @@ exit_matched:
&& action == ACTION_EXPAND
&& !(compl_cont_status & CONT_SOL)
&& *startp != NUL
- && *(p = startp + MB_PTR2LEN(startp)) != NUL)
+ && *(p = startp + utfc_ptr2len(startp)) != NUL)
goto search_line;
}
line_breakcheck();
diff --git a/src/nvim/search.h b/src/nvim/search.h
index cb094aab8c..0366aee8a1 100644
--- a/src/nvim/search.h
+++ b/src/nvim/search.h
@@ -70,6 +70,15 @@ typedef struct spat {
dict_T *additional_data; ///< Additional data from ShaDa file.
} SearchPattern;
+/// Optional extra arguments for searchit().
+typedef struct {
+ linenr_T sa_stop_lnum; ///< stop after this line number when != 0
+ proftime_T *sa_tm; ///< timeout limit or NULL
+ int sa_timed_out; ///< set when timed out
+ int sa_wrapped; ///< search wrapped around
+} searchit_arg_T;
+
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "search.h.generated.h"
#endif
diff --git a/src/nvim/shada.c b/src/nvim/shada.c
index 2306da94c6..aa19d1db1f 100644
--- a/src/nvim/shada.c
+++ b/src/nvim/shada.c
@@ -177,7 +177,7 @@ typedef enum {
/// Possible results when reading ShaDa file
typedef enum {
- kSDReadStatusSuccess, ///< Reading was successfull.
+ kSDReadStatusSuccess, ///< Reading was successful.
kSDReadStatusFinished, ///< Nothing more to read.
kSDReadStatusReadError, ///< Failed to read from file.
kSDReadStatusNotShaDa, ///< Input is most likely not a ShaDa file.
@@ -186,11 +186,11 @@ typedef enum {
/// Possible results of shada_write function.
typedef enum {
- kSDWriteSuccessfull, ///< Writing was successfull.
- kSDWriteReadNotShada, ///< Writing was successfull, but when reading it
+ kSDWriteSuccessfull, ///< Writing was successful.
+ kSDWriteReadNotShada, ///< Writing was successful, but when reading it
///< attempted to read file that did not look like
///< a ShaDa file.
- kSDWriteFailed, ///< Writing was not successfull (e.g. because there
+ kSDWriteFailed, ///< Writing was not successful (e.g. because there
///< was no space left on device).
kSDWriteIgnError, ///< Writing resulted in a error which can be ignored
///< (e.g. when trying to dump a function reference or
@@ -2207,8 +2207,12 @@ static inline ShaDaWriteResult shada_read_when_writing(
shada_free_shada_entry(&entry);
break;
}
- hms_insert(&wms->hms[entry.data.history_item.histtype], entry, true,
- true);
+ if (wms->hms[entry.data.history_item.histtype].hmll.size != 0) {
+ hms_insert(&wms->hms[entry.data.history_item.histtype], entry, true,
+ true);
+ } else {
+ shada_free_shada_entry(&entry);
+ }
break;
}
case kSDItemRegister: {
@@ -2676,6 +2680,36 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer,
if (name == NULL) {
break;
}
+ switch (vartv.v_type) {
+ case VAR_FUNC:
+ case VAR_PARTIAL:
+ tv_clear(&vartv);
+ continue;
+ case VAR_DICT:
+ {
+ dict_T *di = vartv.vval.v_dict;
+ int copyID = get_copyID();
+ if (!set_ref_in_ht(&di->dv_hashtab, copyID, NULL)
+ && copyID == di->dv_copyID) {
+ tv_clear(&vartv);
+ continue;
+ }
+ break;
+ }
+ case VAR_LIST:
+ {
+ list_T *l = vartv.vval.v_list;
+ int copyID = get_copyID();
+ if (!set_ref_in_list(l, copyID, NULL)
+ && copyID == l->lv_copyID) {
+ tv_clear(&vartv);
+ continue;
+ }
+ break;
+ }
+ default:
+ break;
+ }
typval_T tgttv;
tv_copy(&vartv, &tgttv);
ShaDaWriteResult spe_ret;
@@ -3005,7 +3039,7 @@ shada_write_exit:
/// location is used.
/// @param[in] nomerge If true then old file is ignored.
///
-/// @return OK if writing was successfull, FAIL otherwise.
+/// @return OK if writing was successful, FAIL otherwise.
int shada_write_file(const char *const file, bool nomerge)
{
if (shada_disabled()) {
@@ -3341,7 +3375,7 @@ static ShaDaReadResult fread_len(ShaDaReadDef *const sd_reader,
/// @param[in] sd_reader Structure containing file reader definition.
/// @param[out] result Location where result is saved.
///
-/// @return kSDReadStatusSuccess if reading was successfull,
+/// @return kSDReadStatusSuccess if reading was successful,
/// kSDReadStatusNotShaDa if there were not enough bytes to read or
/// kSDReadStatusReadError if reading failed for whatever reason.
static ShaDaReadResult msgpack_read_uint64(ShaDaReadDef *const sd_reader,
diff --git a/src/nvim/sign.c b/src/nvim/sign.c
index 23dd447744..ab5d04d39b 100644
--- a/src/nvim/sign.c
+++ b/src/nvim/sign.c
@@ -198,7 +198,7 @@ static void insert_sign(
// column for signs.
if (buf->b_signlist == NULL) {
redraw_buf_later(buf, NOT_VALID);
- changed_cline_bef_curs();
+ changed_line_abv_curs();
}
// first sign in signlist
@@ -265,6 +265,81 @@ dict_T * sign_get_info(signlist_T *sign)
return d;
}
+// Sort the signs placed on the same line as "sign" by priority. Invoked after
+// changing the priority of an already placed sign. Assumes the signs in the
+// buffer are sorted by line number and priority.
+static void sign_sort_by_prio_on_line(buf_T *buf, signlist_T *sign)
+ FUNC_ATTR_NONNULL_ALL
+{
+ // If there is only one sign in the buffer or only one sign on the line or
+ // the sign is already sorted by priority, then return.
+ if ((sign->prev == NULL
+ || sign->prev->lnum != sign->lnum
+ || sign->prev->priority > sign->priority)
+ && (sign->next == NULL
+ || sign->next->lnum != sign->lnum
+ || sign->next->priority < sign->priority)) {
+ return;
+ }
+
+ // One or more signs on the same line as 'sign'
+ // Find a sign after which 'sign' should be inserted
+
+ // First search backward for a sign with higher priority on the same line
+ signlist_T *p = sign;
+ while (p->prev != NULL
+ && p->prev->lnum == sign->lnum
+ && p->prev->priority <= sign->priority) {
+ p = p->prev;
+ }
+ if (p == sign) {
+ // Sign not found. Search forward for a sign with priority just before
+ // 'sign'.
+ p = sign->next;
+ while (p->next != NULL
+ && p->next->lnum == sign->lnum
+ && p->next->priority > sign->priority) {
+ p = p->next;
+ }
+ }
+
+ // Remove 'sign' from the list
+ if (buf->b_signlist == sign) {
+ buf->b_signlist = sign->next;
+ }
+ if (sign->prev != NULL) {
+ sign->prev->next = sign->next;
+ }
+ if (sign->next != NULL) {
+ sign->next->prev = sign->prev;
+ }
+ sign->prev = NULL;
+ sign->next = NULL;
+
+ // Re-insert 'sign' at the right place
+ if (p->priority <= sign->priority) {
+ // 'sign' has a higher priority and should be inserted before 'p'
+ sign->prev = p->prev;
+ sign->next = p;
+ p->prev = sign;
+ if (sign->prev != NULL) {
+ sign->prev->next = sign;
+ }
+ if (buf->b_signlist == p) {
+ buf->b_signlist = sign;
+ }
+ } else {
+ // 'sign' has a lower priority and should be inserted after 'p'
+ sign->prev = p;
+ sign->next = p->next;
+ p->next = sign;
+ if (sign->next != NULL) {
+ sign->next->prev = sign;
+ }
+ }
+}
+
+
/// Add the sign into the signlist. Find the right spot to do it though.
void buf_addsign(
buf_T *buf, // buffer to store sign in
@@ -284,6 +359,8 @@ void buf_addsign(
&& sign_in_group(sign, groupname)) {
// Update an existing sign
sign->typenr = typenr;
+ sign->priority = prio;
+ sign_sort_by_prio_on_line(buf, sign);
return;
} else if (lnum < sign->lnum) {
insert_sign_by_lnum_prio(buf, prev, id, groupname, prio, lnum, typenr);
@@ -418,11 +495,11 @@ linenr_T buf_delsign(
}
}
- // When deleted the last sign needs to redraw the windows to remove the
- // sign column.
+ // When deleting the last sign the cursor position may change, because the
+ // sign columns no longer shows. And the 'signcolumn' may be hidden.
if (buf->b_signlist == NULL) {
redraw_buf_later(buf, NOT_VALID);
- changed_cline_bef_curs();
+ changed_line_abv_curs();
}
return lnum;
@@ -495,7 +572,7 @@ void buf_delete_signs(buf_T *buf, char_u *group)
// When deleting the last sign need to redraw the windows to remove the
// sign column. Not when curwin is NULL (this means we're exiting).
if (buf->b_signlist != NULL && curwin != NULL) {
- changed_cline_bef_curs();
+ changed_line_abv_curs();
}
lastp = &buf->b_signlist;
@@ -754,6 +831,14 @@ int sign_define_by_name(
} else {
sp_prev->sn_next = sp;
}
+ } else {
+ // Signs may already exist, a redraw is needed in windows with a
+ // non-empty sign list.
+ FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
+ if (wp->w_buffer->b_signlist != NULL) {
+ redraw_buf_later(wp->w_buffer, NOT_VALID);
+ }
+ }
}
// set values for a defined sign.
@@ -1531,10 +1616,44 @@ static enum
EXP_SUBCMD, // expand :sign sub-commands
EXP_DEFINE, // expand :sign define {name} args
EXP_PLACE, // expand :sign place {id} args
+ EXP_LIST, // expand :sign place args
EXP_UNPLACE, // expand :sign unplace"
- EXP_SIGN_NAMES // expand with name of placed signs
+ EXP_SIGN_NAMES, // expand with name of placed signs
+ EXP_SIGN_GROUPS, // expand with name of placed sign groups
} expand_what;
+// Return the n'th sign name (used for command line completion)
+static char_u *get_nth_sign_name(int idx)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ // Complete with name of signs already defined
+ int current_idx = 0;
+ for (sign_T *sp = first_sign; sp != NULL; sp = sp->sn_next) {
+ if (current_idx++ == idx) {
+ return sp->sn_name;
+ }
+ }
+ return NULL;
+}
+
+// Return the n'th sign group name (used for command line completion)
+static char_u *get_nth_sign_group_name(int idx)
+{
+ // Complete with name of sign groups already defined
+ int current_idx = 0;
+ int todo = (int)sg_table.ht_used;
+ for (hashitem_T *hi = sg_table.ht_array; todo > 0; hi++) {
+ if (!HASHITEM_EMPTY(hi)) {
+ todo--;
+ if (current_idx++ == idx) {
+ signgroup_T *const group = HI2SG(hi);
+ return group->sg_name;
+ }
+ }
+ }
+ return NULL;
+}
+
/// Function given to ExpandGeneric() to obtain the sign command
/// expansion.
char_u * get_sign_name(expand_T *xp, int idx)
@@ -1552,20 +1671,18 @@ char_u * get_sign_name(expand_T *xp, int idx)
"buffer=", NULL };
return (char_u *)place_arg[idx];
}
+ case EXP_LIST: {
+ char *list_arg[] = { "group=", "file=", "buffer=", NULL };
+ return (char_u *)list_arg[idx];
+ }
case EXP_UNPLACE: {
char *unplace_arg[] = { "group=", "file=", "buffer=", NULL };
return (char_u *)unplace_arg[idx];
}
- case EXP_SIGN_NAMES: {
- // Complete with name of signs already defined
- int current_idx = 0;
- for (sign_T *sp = first_sign; sp != NULL; sp = sp->sn_next) {
- if (current_idx++ == idx) {
- return sp->sn_name;
- }
- }
- }
- return NULL;
+ case EXP_SIGN_NAMES:
+ return get_nth_sign_name(idx);
+ case EXP_SIGN_GROUPS:
+ return get_nth_sign_group_name(idx);
default:
return NULL;
}
@@ -1574,7 +1691,6 @@ char_u * get_sign_name(expand_T *xp, int idx)
/// Handle command line completion for :sign command.
void set_context_in_sign_cmd(expand_T *xp, char_u *arg)
{
- char_u *p;
char_u *end_subcmd;
char_u *last;
int cmd_idx;
@@ -1598,26 +1714,6 @@ void set_context_in_sign_cmd(expand_T *xp, char_u *arg)
// |
// begin_subcmd_args
begin_subcmd_args = skipwhite(end_subcmd);
- p = skiptowhite(begin_subcmd_args);
- if (*p == NUL) {
- //
- // Expand first argument of subcmd when possible.
- // For ":jump {id}" and ":unplace {id}", we could
- // possibly expand the ids of all signs already placed.
- //
- xp->xp_pattern = begin_subcmd_args;
- switch (cmd_idx) {
- case SIGNCMD_LIST:
- case SIGNCMD_UNDEFINE:
- // :sign list <CTRL-D>
- // :sign undefine <CTRL-D>
- expand_what = EXP_SIGN_NAMES;
- break;
- default:
- xp->xp_context = EXPAND_NOTHING;
- }
- return;
- }
// Expand last argument of subcmd.
//
@@ -1626,6 +1722,7 @@ void set_context_in_sign_cmd(expand_T *xp, char_u *arg)
// p
// Loop until reaching last argument.
+ char_u *p = begin_subcmd_args;
do {
p = skipwhite(p);
last = p;
@@ -1645,7 +1742,20 @@ void set_context_in_sign_cmd(expand_T *xp, char_u *arg)
expand_what = EXP_DEFINE;
break;
case SIGNCMD_PLACE:
- expand_what = EXP_PLACE;
+ // List placed signs
+ if (ascii_isdigit(*begin_subcmd_args)) {
+ // :sign place {id} {args}...
+ expand_what = EXP_PLACE;
+ } else {
+ // :sign place {args}...
+ expand_what = EXP_LIST;
+ }
+ break;
+ case SIGNCMD_LIST:
+ case SIGNCMD_UNDEFINE:
+ // :sign list <CTRL-D>
+ // :sign undefine <CTRL-D>
+ expand_what = EXP_SIGN_NAMES;
break;
case SIGNCMD_JUMP:
case SIGNCMD_UNPLACE:
@@ -1659,19 +1769,33 @@ void set_context_in_sign_cmd(expand_T *xp, char_u *arg)
xp->xp_pattern = p + 1;
switch (cmd_idx) {
case SIGNCMD_DEFINE:
- if (STRNCMP(last, "texthl", p - last) == 0
- || STRNCMP(last, "linehl", p - last) == 0
- || STRNCMP(last, "numhl", p - last) == 0) {
+ if (STRNCMP(last, "texthl", 6) == 0
+ || STRNCMP(last, "linehl", 6) == 0
+ || STRNCMP(last, "numhl", 5) == 0) {
xp->xp_context = EXPAND_HIGHLIGHT;
- } else if (STRNCMP(last, "icon", p - last) == 0) {
+ } else if (STRNCMP(last, "icon", 4) == 0) {
xp->xp_context = EXPAND_FILES;
} else {
xp->xp_context = EXPAND_NOTHING;
}
break;
case SIGNCMD_PLACE:
- if (STRNCMP(last, "name", p - last) == 0) {
+ if (STRNCMP(last, "name", 4) == 0) {
expand_what = EXP_SIGN_NAMES;
+ } else if (STRNCMP(last, "group", 5) == 0) {
+ expand_what = EXP_SIGN_GROUPS;
+ } else if (STRNCMP(last, "file", 4) == 0) {
+ xp->xp_context = EXPAND_BUFFERS;
+ } else {
+ xp->xp_context = EXPAND_NOTHING;
+ }
+ break;
+ case SIGNCMD_UNPLACE:
+ case SIGNCMD_JUMP:
+ if (STRNCMP(last, "group", 5) == 0) {
+ expand_what = EXP_SIGN_GROUPS;
+ } else if (STRNCMP(last, "file", 4) == 0) {
+ xp->xp_context = EXPAND_BUFFERS;
} else {
xp->xp_context = EXPAND_NOTHING;
}
diff --git a/src/nvim/spell.c b/src/nvim/spell.c
index 724a0332bc..95948dac78 100644
--- a/src/nvim/spell.c
+++ b/src/nvim/spell.c
@@ -848,7 +848,7 @@ static void find_word(matchinf_T *mip, int mode)
mip->mi_compflags[mip->mi_complen] = ((unsigned)flags >> 24);
mip->mi_compflags[mip->mi_complen + 1] = NUL;
if (word_ends) {
- char_u fword[MAXWLEN];
+ char_u fword[MAXWLEN] = { 0 };
if (slang->sl_compsylmax < MAXWLEN) {
// "fword" is only needed for checking syllables.
@@ -1026,26 +1026,25 @@ match_checkcompoundpattern (
// Returns true if "flags" is a valid sequence of compound flags and "word"
// does not have too many syllables.
-static bool can_compound(slang_T *slang, char_u *word, char_u *flags)
+static bool can_compound(slang_T *slang, const char_u *word,
+ const char_u *flags)
+ FUNC_ATTR_NONNULL_ALL
{
- char_u uflags[MAXWLEN * 2];
- int i;
- char_u *p;
+ char_u uflags[MAXWLEN * 2] = { 0 };
- if (slang->sl_compprog == NULL)
+ if (slang->sl_compprog == NULL) {
return false;
- if (enc_utf8) {
- // Need to convert the single byte flags to utf8 characters.
- p = uflags;
- for (i = 0; flags[i] != NUL; i++) {
- p += utf_char2bytes(flags[i], p);
- }
- *p = NUL;
- p = uflags;
- } else
- p = flags;
- if (!vim_regexec_prog(&slang->sl_compprog, false, p, 0))
+ }
+ // Need to convert the single byte flags to utf8 characters.
+ char_u *p = uflags;
+ for (int i = 0; flags[i] != NUL; i++) {
+ p += utf_char2bytes(flags[i], p);
+ }
+ *p = NUL;
+ p = uflags;
+ if (!vim_regexec_prog(&slang->sl_compprog, false, p, 0)) {
return false;
+ }
// Count the number of syllables. This may be slow, do it last. If there
// are too many syllables AND the number of compound words is above
@@ -1910,11 +1909,11 @@ int init_syl_tab(slang_T *slang)
// Count the number of syllables in "word".
// When "word" contains spaces the syllables after the last space are counted.
// Returns zero if syllables are not defines.
-static int count_syllables(slang_T *slang, char_u *word)
+static int count_syllables(slang_T *slang, const char_u *word)
+ FUNC_ATTR_NONNULL_ALL
{
int cnt = 0;
bool skip = false;
- char_u *p;
int len;
syl_item_T *syl;
int c;
@@ -1922,7 +1921,7 @@ static int count_syllables(slang_T *slang, char_u *word)
if (slang->sl_syllable == NULL)
return 0;
- for (p = word; *p != NUL; p += len) {
+ for (const char_u *p = word; *p != NUL; p += len) {
// When running into a space reset counter.
if (*p == ' ') {
len = 1;
@@ -2008,6 +2007,10 @@ char_u *did_set_spelllang(win_T *wp)
region = NULL;
len = (int)STRLEN(lang);
+ if (!valid_spelllang(lang)) {
+ continue;
+ }
+
if (STRCMP(lang, "cjk") == 0) {
wp->w_s->b_cjk = 1;
continue;
@@ -2031,7 +2034,8 @@ char_u *did_set_spelllang(win_T *wp)
// Check if we loaded this language before.
for (slang = first_lang; slang != NULL; slang = slang->sl_next) {
- if (path_full_compare(lang, slang->sl_fname, false) == kEqualFiles) {
+ if (path_full_compare(lang, slang->sl_fname, false, true)
+ == kEqualFiles) {
break;
}
}
@@ -2076,7 +2080,7 @@ char_u *did_set_spelllang(win_T *wp)
// Loop over the languages, there can be several files for "lang".
for (slang = first_lang; slang != NULL; slang = slang->sl_next) {
if (filename
- ? path_full_compare(lang, slang->sl_fname, false) == kEqualFiles
+ ? path_full_compare(lang, slang->sl_fname, false, true) == kEqualFiles
: STRICMP(lang, slang->sl_name) == 0) {
region_mask = REGION_ALL;
if (!filename && region != NULL) {
@@ -2129,7 +2133,7 @@ char_u *did_set_spelllang(win_T *wp)
for (c = 0; c < ga.ga_len; ++c) {
p = LANGP_ENTRY(ga, c)->lp_slang->sl_fname;
if (p != NULL
- && path_full_compare(spf_name, p, false) == kEqualFiles) {
+ && path_full_compare(spf_name, p, false, true) == kEqualFiles) {
break;
}
}
@@ -2139,7 +2143,8 @@ char_u *did_set_spelllang(win_T *wp)
// Check if it was loaded already.
for (slang = first_lang; slang != NULL; slang = slang->sl_next) {
- if (path_full_compare(spf_name, slang->sl_fname, false) == kEqualFiles) {
+ if (path_full_compare(spf_name, slang->sl_fname, false, true)
+ == kEqualFiles) {
break;
}
}
@@ -2568,36 +2573,33 @@ void init_spell_chartab(void)
/// Thus this only works properly when past the first character of the word.
///
/// @param wp Buffer used.
-static bool spell_iswordp(char_u *p, win_T *wp)
+static bool spell_iswordp(const char_u *p, const win_T *wp)
+ FUNC_ATTR_NONNULL_ALL
{
- char_u *s;
- int l;
int c;
- if (has_mbyte) {
- l = MB_PTR2LEN(p);
- s = p;
- if (l == 1) {
- // be quick for ASCII
- if (wp->w_s->b_spell_ismw[*p])
- s = p + 1; // skip a mid-word character
- } else {
- c = utf_ptr2char(p);
- if (c < 256 ? wp->w_s->b_spell_ismw[c]
- : (wp->w_s->b_spell_ismw_mb != NULL
- && vim_strchr(wp->w_s->b_spell_ismw_mb, c) != NULL)) {
- s = p + l;
- }
+ const int l = utfc_ptr2len(p);
+ const char_u *s = p;
+ if (l == 1) {
+ // be quick for ASCII
+ if (wp->w_s->b_spell_ismw[*p]) {
+ s = p + 1; // skip a mid-word character
}
-
- c = utf_ptr2char(s);
- if (c > 255) {
- return spell_mb_isword_class(mb_get_class(s), wp);
+ } else {
+ c = utf_ptr2char(p);
+ if (c < 256
+ ? wp->w_s->b_spell_ismw[c]
+ : (wp->w_s->b_spell_ismw_mb != NULL
+ && vim_strchr(wp->w_s->b_spell_ismw_mb, c) != NULL)) {
+ s = p + l;
}
- return spelltab.st_isw[c];
}
- return spelltab.st_isw[wp->w_s->b_spell_ismw[*p] ? p[1] : p[0]];
+ c = utf_ptr2char(s);
+ if (c > 255) {
+ return spell_mb_isword_class(mb_get_class(s), wp);
+ }
+ return spelltab.st_isw[c];
}
// Returns true if "p" points to a word character.
@@ -2615,7 +2617,8 @@ bool spell_iswordp_nmw(const char_u *p, win_T *wp)
// Only for characters above 255.
// Unicode subscript and superscript are not considered word characters.
// See also utf_class() in mbyte.c.
-static bool spell_mb_isword_class(int cl, win_T *wp)
+static bool spell_mb_isword_class(int cl, const win_T *wp)
+ FUNC_ATTR_PURE FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
{
if (wp->w_s->b_cjk)
// East Asian characters are not considered word characters.
@@ -2625,9 +2628,10 @@ static bool spell_mb_isword_class(int cl, win_T *wp)
// Returns true if "p" points to a word character.
// Wide version of spell_iswordp().
-static bool spell_iswordp_w(int *p, win_T *wp)
+static bool spell_iswordp_w(const int *p, const win_T *wp)
+ FUNC_ATTR_NONNULL_ALL
{
- int *s;
+ const int *s;
if (*p < 256 ? wp->w_s->b_spell_ismw[*p]
: (wp->w_s->b_spell_ismw_mb != NULL
@@ -2823,9 +2827,6 @@ void spell_suggest(int count)
smsg(_("Sorry, only %" PRId64 " suggestions"),
(int64_t)sug.su_ga.ga_len);
} else {
- XFREE_CLEAR(repl_from);
- XFREE_CLEAR(repl_to);
-
// When 'rightleft' is set the list is drawn right-left.
cmdmsg_rl = curwin->w_p_rl;
if (cmdmsg_rl)
@@ -2905,6 +2906,9 @@ void spell_suggest(int count)
if (selected > 0 && selected <= sug.su_ga.ga_len && u_save_cursor() == OK) {
// Save the from and to text for :spellrepall.
+ XFREE_CLEAR(repl_from);
+ XFREE_CLEAR(repl_to);
+
stp = &SUG(sug.su_ga, selected - 1);
if (sug.su_badlen > stp->st_orglen) {
// Replacing less than "su_badlen", append the remainder to
@@ -2926,8 +2930,6 @@ void spell_suggest(int count)
memmove(p, line, c);
STRCPY(p + c, stp->st_word);
STRCAT(p, sug.su_badptr + stp->st_orglen);
- ml_replace(curwin->w_cursor.lnum, p, false);
- curwin->w_cursor.col = c;
// For redo we use a change-word command.
ResetRedobuff();
@@ -2936,7 +2938,10 @@ void spell_suggest(int count)
stp->st_wordlen + sug.su_badlen - stp->st_orglen);
AppendCharToRedobuff(ESC);
- // After this "p" may be invalid.
+ // "p" may be freed here
+ ml_replace(curwin->w_cursor.lnum, p, false);
+ curwin->w_cursor.col = c;
+
changed_bytes(curwin->w_cursor.lnum, c);
} else
curwin->w_cursor = prev_cursor;
@@ -3031,7 +3036,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, NULL) == 0
+ if (do_search(NULL, '/', frompat, 1L, SEARCH_KEEP, NULL) == 0
|| u_save_cursor() == FAIL) {
break;
}
@@ -3602,7 +3607,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so
{
char_u tword[MAXWLEN]; // good word collected so far
trystate_T stack[MAXWLEN];
- char_u preword[MAXWLEN * 3]; // word found with proper case;
+ char_u preword[MAXWLEN * 3] = { 0 }; // word found with proper case;
// concatenation of prefix compound
// words and split word. NUL terminated
// when going deeper but not when coming
@@ -3757,7 +3762,8 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so
tword[sp->ts_twordlen] = NUL;
if (sp->ts_prefixdepth <= PFD_NOTSPECIAL
- && (sp->ts_flags & TSF_PREFIXOK) == 0) {
+ && (sp->ts_flags & TSF_PREFIXOK) == 0
+ && pbyts != NULL) {
// There was a prefix before the word. Check that the prefix
// can be used with this word.
// Count the length of the NULs in the prefix. If there are
@@ -4116,7 +4122,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so
&& goodword_ends) {
int l;
- l = MB_PTR2LEN(fword + sp->ts_fidx);
+ l = utfc_ptr2len(fword + sp->ts_fidx);
if (fword_ends) {
// Copy the skipped character to preword.
memmove(preword + sp->ts_prewordlen,
@@ -4262,14 +4268,13 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so
// Correct ts_fidx for the byte length of the
// character (we didn't check that before).
sp->ts_fidx = sp->ts_fcharstart
- + MB_PTR2LEN(fword + sp->ts_fcharstart);
+ + utfc_ptr2len(fword + sp->ts_fcharstart);
// For changing a composing character adjust
// the score from SCORE_SUBST to
// SCORE_SUBCOMP.
- if (enc_utf8
- && utf_iscomposing(utf_ptr2char(tword + sp->ts_twordlen
- - sp->ts_tcharlen))
+ if (utf_iscomposing(utf_ptr2char(tword + sp->ts_twordlen
+ - sp->ts_tcharlen))
&& utf_iscomposing(utf_ptr2char(fword
+ sp->ts_fcharstart))) {
sp->ts_score -= SCORE_SUBST - SCORE_SUBCOMP;
@@ -4361,7 +4366,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so
// a bit illogical for soundfold tree but it does give better
// results.
c = utf_ptr2char(fword + sp->ts_fidx);
- stack[depth].ts_fidx += MB_PTR2LEN(fword + sp->ts_fidx);
+ 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)) {
@@ -4531,9 +4536,9 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so
case STATE_UNSWAP:
// Undo the STATE_SWAP swap: "21" -> "12".
p = fword + sp->ts_fidx;
- n = MB_PTR2LEN(p);
+ n = utfc_ptr2len(p);
c = utf_ptr2char(p + n);
- memmove(p + MB_PTR2LEN(p + n), p, n);
+ memmove(p + utfc_ptr2len(p + n), p, n);
utf_char2bytes(c, p);
FALLTHROUGH;
@@ -4587,11 +4592,11 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so
case STATE_UNSWAP3:
// Undo STATE_SWAP3: "321" -> "123"
p = fword + sp->ts_fidx;
- n = MB_PTR2LEN(p);
+ n = utfc_ptr2len(p);
c2 = utf_ptr2char(p + n);
- fl = MB_PTR2LEN(p + n);
+ fl = utfc_ptr2len(p + n);
c = utf_ptr2char(p + n + fl);
- tl = MB_PTR2LEN(p + n + fl);
+ tl = utfc_ptr2len(p + n + fl);
memmove(p + fl + tl, p, n);
utf_char2bytes(c, p);
utf_char2bytes(c2, p + tl);
@@ -4635,10 +4640,10 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so
case STATE_UNROT3L:
// Undo ROT3L: "231" -> "123"
p = fword + sp->ts_fidx;
- n = MB_PTR2LEN(p);
- n += MB_PTR2LEN(p + n);
+ n = utfc_ptr2len(p);
+ n += utfc_ptr2len(p + n);
c = utf_ptr2char(p + n);
- tl = MB_PTR2LEN(p + n);
+ tl = utfc_ptr2len(p + n);
memmove(p + tl, p, n);
utf_char2bytes(c, p);
@@ -4673,9 +4678,9 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so
// Undo ROT3R: "312" -> "123"
p = fword + sp->ts_fidx;
c = utf_ptr2char(p);
- tl = MB_PTR2LEN(p);
- n = MB_PTR2LEN(p + tl);
- n += MB_PTR2LEN(p + tl + n);
+ tl = utfc_ptr2len(p);
+ n = utfc_ptr2len(p + tl);
+ n += utfc_ptr2len(p + tl + n);
memmove(p, p + tl, n);
utf_char2bytes(c, p + n);
@@ -5758,19 +5763,22 @@ cleanup_suggestions (
int maxscore,
int keep // nr of suggestions to keep
)
+ FUNC_ATTR_NONNULL_ALL
{
suggest_T *stp = &SUG(*gap, 0);
- // Sort the list.
- qsort(gap->ga_data, (size_t)gap->ga_len, sizeof(suggest_T), sug_compare);
+ if (gap->ga_len > 0) {
+ // Sort the list.
+ qsort(gap->ga_data, (size_t)gap->ga_len, sizeof(suggest_T), sug_compare);
- // Truncate the list to the number of suggestions that will be displayed.
- if (gap->ga_len > keep) {
- for (int i = keep; i < gap->ga_len; ++i) {
- xfree(stp[i].st_word);
+ // Truncate the list to the number of suggestions that will be displayed.
+ if (gap->ga_len > keep) {
+ for (int i = keep; i < gap->ga_len; i++) {
+ xfree(stp[i].st_word);
+ }
+ gap->ga_len = keep;
+ return stp[keep - 1].st_score;
}
- gap->ga_len = keep;
- return stp[keep - 1].st_score;
}
return maxscore;
}
@@ -5834,10 +5842,7 @@ void spell_soundfold(slang_T *slang, char_u *inword, bool folded, char_u *res)
word = fword;
}
- if (has_mbyte)
- spell_soundfold_wsal(slang, word, res);
- else
- spell_soundfold_sal(slang, word, res);
+ spell_soundfold_wsal(slang, word, res);
}
}
@@ -5857,7 +5862,7 @@ static void spell_soundfold_sofo(slang_T *slang, char_u *inword, char_u *res)
// 255, sl_sal the rest.
for (s = inword; *s != NUL; ) {
c = mb_cptr2char_adv((const char_u **)&s);
- if (enc_utf8 ? utf_class(c) == 0 : ascii_iswhite(c)) {
+ if (utf_class(c) == 0) {
c = ' ';
} else if (c < 256) {
c = slang->sl_sal_first[c];
@@ -5902,250 +5907,13 @@ static void spell_soundfold_sofo(slang_T *slang, char_u *inword, char_u *res)
res[ri] = NUL;
}
-static void spell_soundfold_sal(slang_T *slang, char_u *inword, char_u *res)
-{
- salitem_T *smp;
- char_u word[MAXWLEN];
- char_u *s = inword;
- char_u *t;
- char_u *pf;
- int i, j, z;
- int reslen;
- int n, k = 0;
- int z0;
- int k0;
- int n0;
- int c;
- int pri;
- int p0 = -333;
- int c0;
-
- // Remove accents, if wanted. We actually remove all non-word characters.
- // But keep white space. We need a copy, the word may be changed here.
- if (slang->sl_rem_accents) {
- t = word;
- while (*s != NUL) {
- if (ascii_iswhite(*s)) {
- *t++ = ' ';
- s = skipwhite(s);
- } else {
- if (spell_iswordp_nmw(s, curwin))
- *t++ = *s;
- ++s;
- }
- }
- *t = NUL;
- } else
- STRLCPY(word, s, MAXWLEN);
-
- smp = (salitem_T *)slang->sl_sal.ga_data;
-
- // This comes from Aspell phonet.cpp. Converted from C++ to C.
- // Changed to keep spaces.
- i = reslen = z = 0;
- while ((c = word[i]) != NUL) {
- // Start with the first rule that has the character in the word.
- n = slang->sl_sal_first[c];
- z0 = 0;
-
- if (n >= 0) {
- // check all rules for the same letter
- for (; (s = smp[n].sm_lead)[0] == c; ++n) {
- // Quickly skip entries that don't match the word. Most
- // entries are less then three chars, optimize for that.
- k = smp[n].sm_leadlen;
- if (k > 1) {
- if (word[i + 1] != s[1])
- continue;
- if (k > 2) {
- for (j = 2; j < k; ++j)
- if (word[i + j] != s[j])
- break;
- if (j < k)
- continue;
- }
- }
-
- if ((pf = smp[n].sm_oneof) != NULL) {
- // Check for match with one of the chars in "sm_oneof".
- while (*pf != NUL && *pf != word[i + k])
- ++pf;
- if (*pf == NUL)
- continue;
- ++k;
- }
- s = smp[n].sm_rules;
- pri = 5; // default priority
-
- p0 = *s;
- k0 = k;
- while (*s == '-' && k > 1) {
- k--;
- s++;
- }
- if (*s == '<')
- s++;
- if (ascii_isdigit(*s)) {
- // determine priority
- pri = *s - '0';
- s++;
- }
- if (*s == '^' && *(s + 1) == '^')
- s++;
-
- if (*s == NUL
- || (*s == '^'
- && (i == 0 || !(word[i - 1] == ' '
- || spell_iswordp(word + i - 1, curwin)))
- && (*(s + 1) != '$'
- || (!spell_iswordp(word + i + k0, curwin))))
- || (*s == '$' && i > 0
- && spell_iswordp(word + i - 1, curwin)
- && (!spell_iswordp(word + i + k0, curwin)))) {
- // search for followup rules, if:
- // followup and k > 1 and NO '-' in searchstring
- c0 = word[i + k - 1];
- n0 = slang->sl_sal_first[c0];
-
- if (slang->sl_followup && k > 1 && n0 >= 0
- && p0 != '-' && word[i + k] != NUL) {
- // test follow-up rule for "word[i + k]"
- for (; (s = smp[n0].sm_lead)[0] == c0; ++n0) {
- // Quickly skip entries that don't match the word.
- k0 = smp[n0].sm_leadlen;
- if (k0 > 1) {
- if (word[i + k] != s[1])
- continue;
- if (k0 > 2) {
- pf = word + i + k + 1;
- for (j = 2; j < k0; ++j)
- if (*pf++ != s[j])
- break;
- if (j < k0)
- continue;
- }
- }
- k0 += k - 1;
-
- if ((pf = smp[n0].sm_oneof) != NULL) {
- // Check for match with one of the chars in
- // "sm_oneof".
- while (*pf != NUL && *pf != word[i + k0])
- ++pf;
- if (*pf == NUL)
- continue;
- ++k0;
- }
-
- p0 = 5;
- s = smp[n0].sm_rules;
- while (*s == '-') {
- // "k0" gets NOT reduced because
- // "if (k0 == k)"
- s++;
- }
- if (*s == '<')
- s++;
- if (ascii_isdigit(*s)) {
- p0 = *s - '0';
- s++;
- }
-
- if (*s == NUL
- // *s == '^' cuts
- || (*s == '$'
- && !spell_iswordp(word + i + k0,
- curwin))) {
- if (k0 == k)
- // this is just a piece of the string
- continue;
-
- if (p0 < pri)
- // priority too low
- continue;
- // rule fits; stop search
- break;
- }
- }
-
- if (p0 >= pri && smp[n0].sm_lead[0] == c0)
- continue;
- }
-
- // replace string
- s = smp[n].sm_to;
- if (s == NULL)
- s = (char_u *)"";
- pf = smp[n].sm_rules;
- p0 = (vim_strchr(pf, '<') != NULL) ? 1 : 0;
- if (p0 == 1 && z == 0) {
- // rule with '<' is used
- if (reslen > 0 && *s != NUL && (res[reslen - 1] == c
- || res[reslen - 1] == *s))
- reslen--;
- z0 = 1;
- z = 1;
- k0 = 0;
- while (*s != NUL && word[i + k0] != NUL) {
- word[i + k0] = *s;
- k0++;
- s++;
- }
- if (k > k0)
- STRMOVE(word + i + k0, word + i + k);
-
- // new "actual letter"
- c = word[i];
- } else {
- // no '<' rule used
- i += k - 1;
- z = 0;
- while (*s != NUL && s[1] != NUL && reslen < MAXWLEN) {
- if (reslen == 0 || res[reslen - 1] != *s)
- res[reslen++] = *s;
- s++;
- }
- // new "actual letter"
- c = *s;
- if (strstr((char *)pf, "^^") != NULL) {
- if (c != NUL)
- res[reslen++] = c;
- STRMOVE(word, word + i + 1);
- i = 0;
- z0 = 1;
- }
- }
- break;
- }
- }
- } else if (ascii_iswhite(c)) {
- c = ' ';
- k = 1;
- }
-
- if (z0 == 0) {
- if (k && !p0 && reslen < MAXWLEN && c != NUL
- && (!slang->sl_collapse || reslen == 0
- || res[reslen - 1] != c))
- // condense only double letters
- res[reslen++] = c;
-
- i++;
- z = 0;
- k = 0;
- }
- }
-
- res[reslen] = NUL;
-}
-
// Turn "inword" into its sound-a-like equivalent in "res[MAXWLEN]".
// Multi-byte version of spell_soundfold().
static void spell_soundfold_wsal(slang_T *slang, char_u *inword, char_u *res)
{
salitem_T *smp = (salitem_T *)slang->sl_sal.ga_data;
- int word[MAXWLEN];
- int wres[MAXWLEN];
+ int word[MAXWLEN] = { 0 };
+ int wres[MAXWLEN] = { 0 };
int l;
int *ws;
int *pf;
@@ -6171,9 +5939,10 @@ static void spell_soundfold_wsal(slang_T *slang, char_u *inword, char_u *res)
const char_u *t = s;
c = mb_cptr2char_adv((const char_u **)&s);
if (slang->sl_rem_accents) {
- if (enc_utf8 ? utf_class(c) == 0 : ascii_iswhite(c)) {
- if (did_white)
+ if (utf_class(c) == 0) {
+ if (did_white) {
continue;
+ }
c = ' ';
did_white = true;
} else {
diff --git a/src/nvim/spell_defs.h b/src/nvim/spell_defs.h
index e83b21b219..034c580b3e 100644
--- a/src/nvim/spell_defs.h
+++ b/src/nvim/spell_defs.h
@@ -261,20 +261,15 @@ typedef struct trystate_S {
// Use our own character-case definitions, because the current locale may
// differ from what the .spl file uses.
// These must not be called with negative number!
-#include <wchar.h> // for towupper() and towlower()
// Multi-byte implementation. For Unicode we can call utf_*(), but don't do
// that for ASCII, because we don't want to use 'casemap' here. Otherwise use
// the "w" library function for characters above 255.
-#define SPELL_TOFOLD(c) (enc_utf8 && (c) >= 128 ? utf_fold(c) \
- : (c) < \
- 256 ? (int)spelltab.st_fold[c] : (int)towlower(c))
+#define SPELL_TOFOLD(c) ((c) >= 128 ? utf_fold(c) : (int)spelltab.st_fold[c])
-#define SPELL_TOUPPER(c) (enc_utf8 && (c) >= 128 ? mb_toupper(c) \
- : (c) < \
- 256 ? (int)spelltab.st_upper[c] : (int)towupper(c))
+#define SPELL_TOUPPER(c) ((c) >= 128 ? mb_toupper(c) \
+ : (int)spelltab.st_upper[c])
-#define SPELL_ISUPPER(c) (enc_utf8 && (c) >= 128 ? mb_isupper(c) \
- : (c) < 256 ? spelltab.st_isu[c] : iswupper(c))
+#define SPELL_ISUPPER(c) ((c) >= 128 ? mb_isupper(c) : spelltab.st_isu[c])
// First language that is loaded, start of the linked list of loaded
// languages.
diff --git a/src/nvim/spellfile.c b/src/nvim/spellfile.c
index 01daafa09e..6b9348e55d 100644
--- a/src/nvim/spellfile.c
+++ b/src/nvim/spellfile.c
@@ -265,6 +265,8 @@
// follow; never used in prefix tree
#define BY_SPECIAL BY_FLAGS2 // highest special byte value
+#define ZERO_FLAG 65009 // used when flag is zero: "0"
+
// Flags used in .spl file for soundsalike flags.
#define SAL_F0LLOWUP 1
#define SAL_COLLAPSE 2
@@ -302,9 +304,6 @@
static char *e_spell_trunc = N_("E758: Truncated spell file");
static char *e_afftrailing = N_("Trailing text in %s line %d: %s");
static char *e_affname = N_("Affix name too long in %s line %d: %s");
-static char *e_affform = N_("E761: Format error in affix file FOL, LOW or UPP");
-static char *e_affrange = N_(
- "E762: Character in FOL, LOW or UPP is out of range");
static char *msg_compressing = N_("Compressing word tree...");
#define MAXLINELEN 500 // Maximum length in bytes of a line in a .aff
@@ -623,7 +622,7 @@ spell_load_file (
switch (scms_ret) {
case SP_FORMERROR:
case SP_TRUNCERROR: {
- emsgf(_("E757: This does not look like a spell file"));
+ emsgf("%s", _("E757: This does not look like a spell file"));
goto endFAIL;
}
case SP_OTHERERROR: {
@@ -1135,7 +1134,6 @@ static int read_sal_section(FILE *fd, slang_T *slang)
salitem_T *smp;
int ccnt;
char_u *p;
- int c = NUL;
slang->sl_sofo = false;
@@ -1159,7 +1157,9 @@ static int read_sal_section(FILE *fd, slang_T *slang)
ga_grow(gap, cnt + 1);
// <sal> : <salfromlen> <salfrom> <saltolen> <salto>
- for (; gap->ga_len < cnt; ++gap->ga_len) {
+ for (; gap->ga_len < cnt; gap->ga_len++) {
+ int c = NUL;
+
smp = &((salitem_T *)gap->ga_data)[gap->ga_len];
ccnt = getc(fd); // <salfromlen>
if (ccnt < 0)
@@ -1236,17 +1236,14 @@ static int read_sal_section(FILE *fd, slang_T *slang)
p = xmalloc(1);
p[0] = NUL;
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 = p;
smp->sm_to = NULL;
- if (has_mbyte) {
- smp->sm_lead_w = mb_str2wide(smp->sm_lead);
- smp->sm_leadlen = 0;
- smp->sm_oneof_w = NULL;
- smp->sm_to_w = NULL;
- }
- ++gap->ga_len;
+ smp->sm_to_w = NULL;
+ gap->ga_len++;
}
// Fill the first-index table.
@@ -1387,8 +1384,7 @@ static int read_compound(FILE *fd, slang_T *slang, int len)
// Inserting backslashes may double the length, "^\(\)$<Nul>" is 7 bytes.
// Conversion to utf-8 may double the size.
c = todo * 2 + 7;
- if (enc_utf8)
- c += todo * 2;
+ c += todo * 2;
pat = xmalloc(c);
// We also need a list of all flags that can appear at the start and one
@@ -1711,7 +1707,6 @@ read_tree_node (
if (c == BY_NOFLAGS && !prefixtree) {
// No flags, all regions.
idxs[idx] = 0;
- c = 0;
} else if (c != BY_INDEX) {
if (prefixtree) {
// Read the optional pflags byte, the prefix ID and the
@@ -1787,7 +1782,7 @@ spell_reload_one (
bool didit = false;
for (slang = first_lang; slang != NULL; slang = slang->sl_next) {
- if (path_full_compare(fname, slang->sl_fname, false) == kEqualFiles) {
+ if (path_full_compare(fname, slang->sl_fname, false, true) == kEqualFiles) {
slang_clear(slang);
if (spell_load_file(fname, NULL, slang, false) == NULL)
// reloading failed, clear the language
@@ -1816,7 +1811,8 @@ spell_reload_one (
#define CONDIT_SUF 4 // add a suffix for matching flags
#define CONDIT_AFF 8 // word already has an affix
-// Tunable parameters for when the tree is compressed. See 'mkspellmem'.
+// 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
@@ -2626,19 +2622,6 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname)
spin->si_clear_chartab = false;
}
- // Don't write a word table for an ASCII file, so that we don't check
- // for conflicts with a word table that matches 'encoding'.
- // Don't write one for utf-8 either, we use utf_*() and
- // mb_get_class(), the list of chars in the file will be incomplete.
- if (!spin->si_ascii
- && !enc_utf8
- ) {
- if (fol == NULL || low == NULL || upp == NULL)
- smsg(_("Missing FOL/LOW/UPP line in %s"), fname);
- else
- (void)set_spell_chartab(fol, low, upp);
- }
-
xfree(fol);
xfree(low);
xfree(upp);
@@ -2656,8 +2639,9 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname)
}
if (compsylmax != 0) {
- if (syllable == NULL)
- smsg(_("COMPOUNDSYLMAX used without SYLLABLE"));
+ if (syllable == NULL) {
+ smsg("%s", _("COMPOUNDSYLMAX used without SYLLABLE"));
+ }
aff_check_number(spin->si_compsylmax, compsylmax, "COMPOUNDSYLMAX");
spin->si_compsylmax = compsylmax;
}
@@ -2783,6 +2767,7 @@ static unsigned affitem2flag(int flagtype, char_u *item, char_u *fname, int lnum
}
// Get one affix name from "*pp" and advance the pointer.
+// Returns ZERO_FLAG for "0".
// Returns zero for an error, still advances the pointer then.
static unsigned get_affitem(int flagtype, char_u **pp)
{
@@ -2794,6 +2779,9 @@ static unsigned get_affitem(int flagtype, char_u **pp)
return 0;
}
res = getdigits_int(pp, true, 0);
+ if (res == 0) {
+ res = ZERO_FLAG;
+ }
} else {
res = mb_ptr2char_adv((const char_u **)pp);
if (flagtype == AFT_LONG || (flagtype == AFT_CAPLONG
@@ -2915,10 +2903,15 @@ static bool flag_in_afflist(int flagtype, char_u *afflist, unsigned flag)
int digits = getdigits_int(&p, true, 0);
assert(digits >= 0);
n = (unsigned int)digits;
- if (n == flag)
+ if (n == 0) {
+ n = ZERO_FLAG;
+ }
+ if (n == flag) {
return true;
- if (*p != NUL) // skip over comma
- ++p;
+ }
+ if (*p != NUL) { // skip over comma
+ p++;
+ }
}
break;
}
@@ -3024,6 +3017,7 @@ static int spell_read_dic(spellinfo_T *spin, char_u *fname, afffile_T *affile)
char_u message[MAXLINELEN + MAXWLEN];
int flags;
int duplicate = 0;
+ Timestamp last_msg_time = 0;
// Open the file.
fd = os_fopen((char *)fname, "r");
@@ -3099,18 +3093,22 @@ static int spell_read_dic(spellinfo_T *spin, char_u *fname, afffile_T *affile)
continue;
}
- // This takes time, print a message every 10000 words.
+ // This takes time, print a message every 10000 words, but not more
+ // often than once per second.
if (spin->si_verbose && spin->si_msg_count > 10000) {
spin->si_msg_count = 0;
- vim_snprintf((char *)message, sizeof(message),
- _("line %6d, word %6ld - %s"),
- lnum, spin->si_foldwcount + spin->si_keepwcount, w);
- msg_start();
- msg_puts_long_attr(message, 0);
- msg_clr_eos();
- msg_didout = FALSE;
- msg_col = 0;
- ui_flush();
+ if (os_time() > last_msg_time) {
+ last_msg_time = os_time();
+ vim_snprintf((char *)message, sizeof(message),
+ _("line %6d, word %6ld - %s"),
+ lnum, spin->si_foldwcount + spin->si_keepwcount, w);
+ msg_start();
+ msg_puts_long_attr(message, 0);
+ msg_clr_eos();
+ msg_didout = false;
+ msg_col = 0;
+ ui_flush();
+ }
}
// Store the word in the hashtable to be able to find duplicates.
@@ -3923,9 +3921,10 @@ static int tree_add_word(spellinfo_T *spin, char_u *word, wordnode_T *root, int
++spin->si_msg_count;
if (spin->si_compress_cnt > 1) {
- if (--spin->si_compress_cnt == 1)
+ if (--spin->si_compress_cnt == 1) {
// Did enough words to lower the block count limit.
spin->si_blocks_cnt += compress_inc;
+ }
}
// When we have allocated lots of memory we need to compress the word tree
@@ -3964,9 +3963,10 @@ static int tree_add_word(spellinfo_T *spin, char_u *word, wordnode_T *root, int
// compression useful, or one of them is small, which means
// compression goes fast. But when filling the soundfold word tree
// there is no keep-case tree.
- wordtree_compress(spin, spin->si_foldroot);
- if (affixID >= 0)
- wordtree_compress(spin, spin->si_keeproot);
+ wordtree_compress(spin, spin->si_foldroot, "case-folded");
+ if (affixID >= 0) {
+ wordtree_compress(spin, spin->si_keeproot, "keep-case");
+ }
}
return OK;
@@ -3999,6 +3999,7 @@ static wordnode_T *get_wordnode(spellinfo_T *spin)
// siblings.
// Returns the number of nodes actually freed.
static int deref_wordnode(spellinfo_T *spin, wordnode_T *node)
+ FUNC_ATTR_NONNULL_ALL
{
wordnode_T *np;
int cnt = 0;
@@ -4018,6 +4019,7 @@ static int deref_wordnode(spellinfo_T *spin, wordnode_T *node)
// Free a wordnode_T for re-use later.
// Only the "wn_child" field becomes invalid.
static void free_wordnode(spellinfo_T *spin, wordnode_T *n)
+ FUNC_ATTR_NONNULL_ALL
{
n->wn_child = spin->si_first_free;
spin->si_first_free = n;
@@ -4025,18 +4027,19 @@ static void free_wordnode(spellinfo_T *spin, wordnode_T *n)
}
// Compress a tree: find tails that are identical and can be shared.
-static void wordtree_compress(spellinfo_T *spin, wordnode_T *root)
+static void wordtree_compress(spellinfo_T *spin, wordnode_T *root,
+ const char *name)
+ FUNC_ATTR_NONNULL_ALL
{
hashtab_T ht;
- int n;
- int tot = 0;
- int perc;
+ long tot = 0;
+ long perc;
// Skip the root itself, it's not actually used. The first sibling is the
// start of the tree.
if (root->wn_sibling != NULL) {
hash_init(&ht);
- n = node_compress(spin, root->wn_sibling, &ht, &tot);
+ const long n = node_compress(spin, root->wn_sibling, &ht, &tot);
#ifndef SPELL_PRINTTREE
if (spin->si_verbose || p_verbose > 2)
@@ -4049,8 +4052,8 @@ static void wordtree_compress(spellinfo_T *spin, wordnode_T *root)
else
perc = (tot - n) * 100 / tot;
vim_snprintf((char *)IObuff, IOSIZE,
- _("Compressed %d of %d nodes; %d (%d%%) remaining"),
- n, tot, tot - n, perc);
+ _("Compressed %s of %ld nodes; %ld (%ld%%) remaining"),
+ name, tot, tot - n, perc);
spell_message(spin, IObuff);
}
#ifdef SPELL_PRINTTREE
@@ -4062,23 +4065,23 @@ static void wordtree_compress(spellinfo_T *spin, wordnode_T *root)
// Compress a node, its siblings and its children, depth first.
// Returns the number of compressed nodes.
-static int
-node_compress (
+static long node_compress(
spellinfo_T *spin,
wordnode_T *node,
hashtab_T *ht,
- int *tot // total count of nodes before compressing,
+ long *tot // total count of nodes before compressing,
// incremented while going through the tree
)
+ FUNC_ATTR_NONNULL_ALL
{
wordnode_T *np;
wordnode_T *tp;
wordnode_T *child;
hash_T hash;
hashitem_T *hi;
- int len = 0;
+ long len = 0;
unsigned nr, n;
- int compressed = 0;
+ long compressed = 0;
// Go through the list of siblings. Compress each child and then try
// finding an identical child to replace it.
@@ -4151,7 +4154,7 @@ node_compress (
node->wn_u1.hashkey[5] = NUL;
// Check for CTRL-C pressed now and then.
- fast_breakcheck();
+ veryfast_breakcheck();
return compressed;
}
@@ -4719,7 +4722,8 @@ static void spell_make_sugfile(spellinfo_T *spin, char_u *wfname)
// of the code for the soundfolding stuff.
// It might have been done already by spell_reload_one().
for (slang = first_lang; slang != NULL; slang = slang->sl_next) {
- if (path_full_compare(wfname, slang->sl_fname, false) == kEqualFiles) {
+ if (path_full_compare(wfname, slang->sl_fname, false, true)
+ == kEqualFiles) {
break;
}
}
@@ -4757,7 +4761,7 @@ static void spell_make_sugfile(spellinfo_T *spin, char_u *wfname)
// Compress the soundfold trie.
spell_message(spin, (char_u *)_(msg_compressing));
- wordtree_compress(spin, spin->si_foldroot);
+ wordtree_compress(spin, spin->si_foldroot, "case-folded");
// Write the .sug file.
// Make the file name by changing ".spl" to ".sug".
@@ -5103,7 +5107,8 @@ mkspell (
spin.si_newcompID = 127; // start compound ID at first maximum
// default: fnames[0] is output file, following are input files
- innames = &fnames[1];
+ // When "fcount" is 1 there is only one file.
+ innames = &fnames[fcount == 1 ? 0 : 1];
incount = fcount - 1;
wfname = xmalloc(MAXPATHL);
@@ -5113,12 +5118,10 @@ mkspell (
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".
- innames = &fnames[0];
incount = 1;
vim_snprintf((char *)wfname, MAXPATHL, "%s.spl", fnames[0]);
} else if (fcount == 1) {
// For ":mkspell path/vim" output file is "path/vim.latin1.spl".
- innames = &fnames[0];
incount = 1;
vim_snprintf((char *)wfname, MAXPATHL, SPL_FNAME_TMPL,
fnames[0], spin.si_ascii ? (char_u *)"ascii" : spell_enc());
@@ -5228,9 +5231,9 @@ mkspell (
if (!error && !got_int) {
// Combine tails in the tree.
spell_message(&spin, (char_u *)_(msg_compressing));
- wordtree_compress(&spin, spin.si_foldroot);
- wordtree_compress(&spin, spin.si_keeproot);
- wordtree_compress(&spin, spin.si_prefroot);
+ wordtree_compress(&spin, spin.si_foldroot, "case-folded");
+ wordtree_compress(&spin, spin.si_keeproot, "keep-case");
+ wordtree_compress(&spin, spin.si_prefroot, "prefixes");
}
if (!error && !got_int) {
@@ -5282,7 +5285,8 @@ theend:
// Display a message for spell file processing when 'verbose' is set or using
// ":mkspell". "str" can be IObuff.
-static void spell_message(spellinfo_T *spin, char_u *str)
+static void spell_message(const spellinfo_T *spin, char_u *str)
+ FUNC_ATTR_NONNULL_ALL
{
if (spin->si_verbose || p_verbose > 2) {
if (!spin->si_verbose)
@@ -5524,65 +5528,6 @@ static void init_spellfile(void)
}
}
-// Set the spell character tables from strings in the affix file.
-static int set_spell_chartab(char_u *fol, char_u *low, char_u *upp)
-{
- // We build the new tables here first, so that we can compare with the
- // previous one.
- spelltab_T new_st;
- char_u *pf = fol, *pl = low, *pu = upp;
- int f, l, u;
-
- clear_spell_chartab(&new_st);
-
- while (*pf != NUL) {
- if (*pl == NUL || *pu == NUL) {
- EMSG(_(e_affform));
- return FAIL;
- }
- f = mb_ptr2char_adv((const char_u **)&pf);
- l = mb_ptr2char_adv((const char_u **)&pl);
- u = mb_ptr2char_adv((const char_u **)&pu);
- // Every character that appears is a word character.
- if (f < 256)
- new_st.st_isw[f] = true;
- if (l < 256)
- new_st.st_isw[l] = true;
- if (u < 256)
- new_st.st_isw[u] = true;
-
- // if "LOW" and "FOL" are not the same the "LOW" char needs
- // case-folding
- if (l < 256 && l != f) {
- if (f >= 256) {
- EMSG(_(e_affrange));
- return FAIL;
- }
- new_st.st_fold[l] = f;
- }
-
- // if "UPP" and "FOL" are not the same the "UPP" char needs
- // case-folding, it's upper case and the "UPP" is the upper case of
- // "FOL" .
- if (u < 256 && u != f) {
- if (f >= 256) {
- EMSG(_(e_affrange));
- return FAIL;
- }
- new_st.st_fold[u] = f;
- new_st.st_isu[u] = true;
- new_st.st_upper[f] = u;
- }
- }
-
- if (*pl != NUL || *pu != NUL) {
- EMSG(_(e_affform));
- return FAIL;
- }
-
- return set_spell_finish(&new_st);
-}
-
// Set the spell character tables from strings in the .spl file.
static void
set_spell_charflags (
diff --git a/src/nvim/state.c b/src/nvim/state.c
index 81bc078a88..b195c1d96b 100644
--- a/src/nvim/state.c
+++ b/src/nvim/state.c
@@ -156,7 +156,7 @@ char *get_mode(void)
buf[0] = 'n';
if (finish_op) {
buf[1] = 'o';
- // to be able to detect force-linewise/blockwise/characterwise operations
+ // to be able to detect force-linewise/blockwise/charwise operations
buf[2] = (char)motion_force;
} else if (restart_edit == 'I' || restart_edit == 'R'
|| restart_edit == 'V') {
diff --git a/src/nvim/strings.c b/src/nvim/strings.c
index 3ba9354c67..2f5491fda5 100644
--- a/src/nvim/strings.c
+++ b/src/nvim/strings.c
@@ -953,11 +953,17 @@ int vim_vsnprintf_typval(
- mb_string2cells((char_u *)str_arg));
}
if (precision) {
- const char *p1 = str_arg;
- for (size_t i = 0; i < precision && *p1; i++) {
- p1 += mb_ptr2len((const char_u *)p1);
+ char_u *p1;
+ size_t i = 0;
+
+ for (p1 = (char_u *)str_arg; *p1;
+ p1 += mb_ptr2len(p1)) {
+ i += (size_t)utf_ptr2cells(p1);
+ if (i > precision) {
+ break;
+ }
}
- str_arg_l = precision = (size_t)(p1 - str_arg);
+ str_arg_l = precision = (size_t)(p1 - (char_u *)str_arg);
}
}
break;
diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c
index 5a61238d8c..4aa7c21ce4 100644
--- a/src/nvim/syntax.c
+++ b/src/nvim/syntax.c
@@ -116,10 +116,12 @@ static int include_link = 0; /* when 2 include "nvim/link" and "clear" */
/// following names, separated by commas (but no spaces!).
static char *(hl_name_table[]) =
{ "bold", "standout", "underline", "undercurl",
- "italic", "reverse", "inverse", "strikethrough", "NONE" };
+ "italic", "reverse", "inverse", "strikethrough", "nocombine", "NONE" };
static int hl_attr_table[] =
{ HL_BOLD, HL_STANDOUT, HL_UNDERLINE, HL_UNDERCURL, HL_ITALIC, HL_INVERSE,
- HL_INVERSE, HL_STRIKETHROUGH, 0 };
+ HL_INVERSE, HL_STRIKETHROUGH, HL_NOCOMBINE, 0 };
+
+static char e_illegal_arg[] = N_("E390: Illegal argument: %s");
// The patterns that are being searched for are stored in a syn_pattern.
// A match item consists of one pattern.
@@ -2460,11 +2462,8 @@ update_si_end(
int force /* when TRUE overrule a previous end */
)
{
- lpos_T startpos;
- lpos_T endpos;
lpos_T hl_endpos;
lpos_T end_endpos;
- int end_idx;
/* return quickly for a keyword */
if (sip->si_idx < 0)
@@ -2480,9 +2479,12 @@ update_si_end(
* We need to find the end of the region. It may continue in the next
* line.
*/
- end_idx = 0;
- startpos.lnum = current_lnum;
- startpos.col = startcol;
+ int end_idx = 0;
+ lpos_T startpos = {
+ .lnum = current_lnum,
+ .col = startcol,
+ };
+ lpos_T endpos = { 0 };
find_endpos(sip->si_idx, &startpos, &endpos, &hl_endpos,
&(sip->si_flags), &end_endpos, &end_idx, sip->si_extmatch);
@@ -3045,7 +3047,7 @@ static void syn_cmd_conceal(exarg_T *eap, int syncing)
} else if (STRNICMP(arg, "off", 3) == 0 && next - arg == 3) {
curwin->w_s->b_syn_conceal = false;
} else {
- EMSG2(_("E390: Illegal argument: %s"), arg);
+ EMSG2(_(e_illegal_arg), arg);
}
}
@@ -3073,7 +3075,42 @@ static void syn_cmd_case(exarg_T *eap, int syncing)
} else if (STRNICMP(arg, "ignore", 6) == 0 && next - arg == 6) {
curwin->w_s->b_syn_ic = true;
} else {
- EMSG2(_("E390: Illegal argument: %s"), arg);
+ EMSG2(_(e_illegal_arg), arg);
+ }
+}
+
+/// Handle ":syntax foldlevel" command.
+static void syn_cmd_foldlevel(exarg_T *eap, int syncing)
+{
+ char_u *arg = eap->arg;
+ char_u *arg_end;
+
+ eap->nextcmd = find_nextcmd(arg);
+ if (eap->skip)
+ return;
+
+ if (*arg == NUL) {
+ switch (curwin->w_s->b_syn_foldlevel) {
+ case SYNFLD_START: MSG(_("syntax foldlevel start")); break;
+ case SYNFLD_MINIMUM: MSG(_("syntax foldlevel minimum")); break;
+ default: break;
+ }
+ return;
+ }
+
+ arg_end = skiptowhite(arg);
+ if (STRNICMP(arg, "start", 5) == 0 && arg_end - arg == 5) {
+ curwin->w_s->b_syn_foldlevel = SYNFLD_START;
+ } else if (STRNICMP(arg, "minimum", 7) == 0 && arg_end - arg == 7) {
+ curwin->w_s->b_syn_foldlevel = SYNFLD_MINIMUM;
+ } else {
+ EMSG2(_(e_illegal_arg), arg);
+ return;
+ }
+
+ arg = skipwhite(arg_end);
+ if (*arg != NUL) {
+ EMSG2(_(e_illegal_arg), arg);
}
}
@@ -3105,7 +3142,7 @@ static void syn_cmd_spell(exarg_T *eap, int syncing)
} else if (STRNICMP(arg, "default", 7) == 0 && next - arg == 7) {
curwin->w_s->b_syn_spell = SYNSPL_DEFAULT;
} else {
- EMSG2(_("E390: Illegal argument: %s"), arg);
+ EMSG2(_(e_illegal_arg), arg);
return;
}
@@ -3161,6 +3198,7 @@ void syntax_clear(synblock_T *block)
block->b_syn_error = false; // clear previous error
block->b_syn_slow = false; // clear previous timeout
block->b_syn_ic = false; // Use case, by default
+ block->b_syn_foldlevel = SYNFLD_START;
block->b_syn_spell = SYNSPL_DEFAULT; // default spell checking
block->b_syn_containedin = false;
block->b_syn_conceal = false;
@@ -3981,7 +4019,7 @@ static void add_keyword(char_u *const name,
STRLEN(kp->keyword), hash);
// even though it looks like only the kp->keyword member is
- // being used here, vim uses some pointer trickery to get the orignal
+ // being used here, vim uses some pointer trickery to get the original
// struct again later by using knowledge of the offset of the keyword
// field in the struct. See the definition of the HI2KE macro.
if (HASHITEM_EMPTY(hi)) {
@@ -5485,6 +5523,7 @@ static struct subcommand subcommands[] =
{ "cluster", syn_cmd_cluster },
{ "conceal", syn_cmd_conceal },
{ "enable", syn_cmd_enable },
+ { "foldlevel", syn_cmd_foldlevel },
{ "include", syn_cmd_include },
{ "iskeyword", syn_cmd_iskeyword },
{ "keyword", syn_cmd_keyword },
@@ -5763,6 +5802,17 @@ int syn_get_stack_item(int i)
return CUR_STATE(i).si_id;
}
+static int syn_cur_foldlevel(void)
+{
+ int level = 0;
+ for (int i = 0; i < current_state.ga_len; i++) {
+ if (CUR_STATE(i).si_flags & HL_FOLD) {
+ level++;
+ }
+ }
+ return level;
+}
+
/*
* Function called to get folding level for line "lnum" in window "wp".
*/
@@ -5776,9 +5826,22 @@ int syn_get_foldlevel(win_T *wp, long lnum)
&& !wp->w_s->b_syn_slow) {
syntax_start(wp, lnum);
- for (int i = 0; i < current_state.ga_len; ++i) {
- if (CUR_STATE(i).si_flags & HL_FOLD) {
- ++level;
+ // Start with the fold level at the start of the line.
+ level = syn_cur_foldlevel();
+
+ if (wp->w_s->b_syn_foldlevel == SYNFLD_MINIMUM) {
+ // Find the lowest fold level that is followed by a higher one.
+ int cur_level = level;
+ int low_level = cur_level;
+ while (!current_finished) {
+ (void)syn_current_attr(false, false, NULL, false);
+ cur_level = syn_cur_foldlevel();
+ if (cur_level < low_level) {
+ low_level = cur_level;
+ } else if (cur_level > low_level) {
+ level = low_level;
+ }
+ current_col++;
}
}
}
@@ -5959,6 +6022,7 @@ static const char *highlight_init_both[] = {
"IncSearch cterm=reverse gui=reverse",
"ModeMsg cterm=bold gui=bold",
"NonText ctermfg=Blue gui=bold guifg=Blue",
+ "Normal cterm=NONE gui=NONE",
"PmenuSbar ctermbg=Grey guibg=Grey",
"StatusLine cterm=reverse,bold gui=reverse,bold",
"StatusLineNC cterm=reverse gui=reverse",
@@ -6010,7 +6074,6 @@ static const char *highlight_init_light[] = {
"Title ctermfg=DarkMagenta gui=bold guifg=Magenta",
"Visual guibg=LightGrey",
"WarningMsg ctermfg=DarkRed guifg=Red",
- "Normal gui=NONE",
NULL
};
@@ -6044,7 +6107,6 @@ static const char *highlight_init_dark[] = {
"Title ctermfg=LightMagenta gui=bold guifg=Magenta",
"Visual guibg=DarkGrey",
"WarningMsg ctermfg=LightRed guifg=Red",
- "Normal gui=NONE",
NULL
};
@@ -6401,7 +6463,7 @@ static int color_numbers_88[28] = { 0, 4, 2, 6,
75, 11, 78, 15, -1 };
// for xterm with 256 colors...
static int color_numbers_256[28] = { 0, 4, 2, 6,
- 1, 5, 130, 130,
+ 1, 5, 130, 3,
248, 248, 7, 7,
242, 242,
12, 81, 10, 121,
@@ -7316,7 +7378,7 @@ static void set_hl_attr(int idx)
sgp->sg_attr = hl_get_syn_attr(idx+1, at_en);
- // a cursor style uses this syn_id, make sure its atribute is updated.
+ // a cursor style uses this syn_id, make sure its attribute is updated.
if (cursor_mode_uses_syn_id(idx+1)) {
ui_mode_info_set();
}
@@ -7445,6 +7507,8 @@ static int syn_add_group(char_u *name)
return 0;
}
+ char_u *const name_up = vim_strsave_up(name);
+
// Append another syntax_highlight entry.
struct hl_group* hlgp = GA_APPEND_VIA_PTR(struct hl_group, &highlight_ga);
memset(hlgp, 0, sizeof(*hlgp));
@@ -7453,7 +7517,7 @@ static int syn_add_group(char_u *name)
hlgp->sg_rgb_fg = -1;
hlgp->sg_rgb_sp = -1;
hlgp->sg_blend = -1;
- hlgp->sg_name_u = vim_strsave_up(name);
+ hlgp->sg_name_u = name_up;
return highlight_ga.ga_len; /* ID is index plus one */
}
@@ -7569,8 +7633,8 @@ void highlight_changed(void)
{
int id;
char_u userhl[30]; // use 30 to avoid compiler warning
- int id_SNC = -1;
int id_S = -1;
+ int id_SNC = 0;
int hlcnt;
need_highlight_changed = FALSE;
diff --git a/src/nvim/tag.c b/src/nvim/tag.c
index 91f3da1793..81d1ef4c9f 100644
--- a/src/nvim/tag.c
+++ b/src/nvim/tag.c
@@ -51,17 +51,21 @@
* Structure to hold pointers to various items in a tag line.
*/
typedef struct tag_pointers {
- /* filled in by parse_tag_line(): */
- char_u *tagname; /* start of tag name (skip "file:") */
- char_u *tagname_end; /* char after tag name */
- char_u *fname; /* first char of file name */
- char_u *fname_end; /* char after file name */
- char_u *command; /* first char of command */
- /* filled in by parse_match(): */
- char_u *command_end; /* first char after command */
- char_u *tag_fname; /* file name of the tags file */
- char_u *tagkind; /* "kind:" value */
- char_u *tagkind_end; /* end of tagkind */
+ // filled in by parse_tag_line():
+ char_u *tagname; // start of tag name (skip "file:")
+ char_u *tagname_end; // char after tag name
+ char_u *fname; // first char of file name
+ char_u *fname_end; // char after file name
+ char_u *command; // first char of command
+ // filled in by parse_match():
+ char_u *command_end; // first char after command
+ char_u *tag_fname; // file name of the tags file. This is used
+ // when 'tr' is set.
+ char_u *tagkind; // "kind:" value
+ char_u *tagkind_end; // end of tagkind
+ char_u *user_data; // user_data string
+ char_u *user_data_end; // end of user_data
+ linenr_T tagline; // "line:" value
} tagptrs_T;
/*
@@ -102,6 +106,10 @@ static char_u *nofile_fname = NULL; /* fname for NOTAGFILE error */
static char_u *bottommsg = (char_u *)N_("E555: at bottom of tag stack");
static char_u *topmsg = (char_u *)N_("E556: at top of tag stack");
+static char_u *recurmsg
+ = (char_u *)N_("E986: cannot modify the tag stack within tagfunc");
+static char_u *tfu_inv_ret_msg
+ = (char_u *)N_("E987: invalid return value from tagfunc");
static char_u *tagmatchname = NULL; /* name of last used tag */
@@ -109,7 +117,12 @@ static char_u *tagmatchname = NULL; /* name of last used tag */
* Tag for preview window is remembered separately, to avoid messing up the
* normal tagstack.
*/
-static taggy_T ptag_entry = { NULL, { { 0, 0, 0 }, 0, 0, NULL }, 0, 0 };
+static taggy_T ptag_entry = { NULL, { { 0, 0, 0 }, 0, 0, NULL }, 0, 0, NULL };
+
+static int tfu_in_use = false; // disallow recursive call of tagfunc
+
+// Used instead of NUL to separate tag fields in the growarrays.
+#define TAG_SEP 0x02
/*
* Jump to tag; handling of tag commands and tag stack
@@ -131,13 +144,13 @@ static taggy_T ptag_entry = { NULL, { { 0, 0, 0 }, 0, 0, NULL }, 0, 0 };
*
* for cscope, returns TRUE if we jumped to tag or aborted, FALSE otherwise
*/
-int
-do_tag (
- char_u *tag, /* tag (pattern) to jump to */
+int
+do_tag(
+ char_u *tag, // tag (pattern) to jump to
int type,
int count,
- int forceit, /* :ta with ! */
- int verbose /* print "tag not found" message */
+ int forceit, // :ta with !
+ int verbose // print "tag not found" message
)
{
taggy_T *tagstack = curwin->w_tagstack;
@@ -148,28 +161,20 @@ do_tag (
int oldtagstackidx = tagstackidx;
int prevtagstackidx = tagstackidx;
int prev_num_matches;
- int new_tag = FALSE;
- int other_name;
- int i, j, k;
- int idx;
+ int new_tag = false;
+ int i;
int ic;
- char_u *p;
- char_u *name;
- int no_regexp = FALSE;
+ int no_regexp = false;
int error_cur_match = 0;
- char_u *command_end;
- int save_pos = FALSE;
+ int save_pos = false;
fmark_T saved_fmark;
- int taglen;
- int jumped_to_tag = FALSE;
- tagptrs_T tagp, tagp2;
+ int jumped_to_tag = false;
int new_num_matches;
char_u **new_matches;
- int attr;
int use_tagstack;
- int skip_msg = FALSE;
- char_u *buf_ffname = curbuf->b_ffname; /* name to use for
- priority computation */
+ int skip_msg = false;
+ char_u *buf_ffname = curbuf->b_ffname; // name for priority computation
+ int use_tfu = 1;
/* remember the matches for the last used tag */
static int num_matches = 0;
@@ -177,19 +182,25 @@ do_tag (
static char_u **matches = NULL;
static int flags;
+ if (tfu_in_use) {
+ EMSG(_(recurmsg));
+ return false;
+ }
+
#ifdef EXITFREE
if (type == DT_FREE) {
/* remove the list of matches */
FreeWild(num_matches, matches);
cs_free_tags();
num_matches = 0;
- return FALSE;
+ return false;
}
#endif
if (type == DT_HELP) {
type = DT_TAG;
- no_regexp = TRUE;
+ no_regexp = true;
+ use_tfu = 0;
}
prev_num_matches = num_matches;
@@ -205,14 +216,15 @@ do_tag (
use_tagstack = false;
new_tag = true;
if (g_do_tagpreview != 0) {
- xfree(ptag_entry.tagname);
+ tagstack_clear_entry(&ptag_entry);
ptag_entry.tagname = vim_strsave(tag);
}
} else {
- if (g_do_tagpreview != 0)
- use_tagstack = FALSE;
- else
- use_tagstack = TRUE;
+ if (g_do_tagpreview != 0) {
+ use_tagstack = false;
+ } else {
+ use_tagstack = true;
+ }
/* new pattern, add to the tag stack */
if (*tag != NUL
@@ -228,7 +240,7 @@ do_tag (
cur_match = ptag_entry.cur_match;
cur_fnum = ptag_entry.cur_fnum;
} else {
- xfree(ptag_entry.tagname);
+ tagstack_clear_entry(&ptag_entry);
ptag_entry.tagname = vim_strsave(tag);
}
} else {
@@ -236,16 +248,18 @@ do_tag (
* If the last used entry is not at the top, delete all tag
* stack entries above it.
*/
- while (tagstackidx < tagstacklen)
- xfree(tagstack[--tagstacklen].tagname);
+ while (tagstackidx < tagstacklen) {
+ tagstack_clear_entry(&tagstack[--tagstacklen]);
+ }
/* if the tagstack is full: remove oldest entry */
if (++tagstacklen > TAGSTACKSIZE) {
tagstacklen = TAGSTACKSIZE;
- xfree(tagstack[0].tagname);
- for (i = 1; i < tagstacklen; ++i)
+ tagstack_clear_entry(&tagstack[0]);
+ for (i = 1; i < tagstacklen; i++) {
tagstack[i - 1] = tagstack[i];
- --tagstackidx;
+ }
+ tagstackidx--;
}
// put the tag name in the tag stack
@@ -253,15 +267,15 @@ do_tag (
curwin->w_tagstacklen = tagstacklen;
- save_pos = TRUE; /* save the cursor position below */
+ save_pos = true; // save the cursor position below
}
- new_tag = TRUE;
+ new_tag = true;
} else {
if (
- g_do_tagpreview != 0 ? ptag_entry.tagname == NULL :
- tagstacklen == 0) {
- /* empty stack */
+ g_do_tagpreview != 0 ? ptag_entry.tagname == NULL :
+ tagstacklen == 0) {
+ // empty stack
EMSG(_(e_tagstack));
goto end_do_tag;
}
@@ -271,7 +285,7 @@ do_tag (
if ((tagstackidx -= count) < 0) {
EMSG(_(bottommsg));
if (tagstackidx + count == 0) {
- /* We did [num]^T from the bottom of the stack */
+ // We did [num]^T from the bottom of the stack
tagstackidx = 0;
goto end_do_tag;
}
@@ -279,7 +293,7 @@ do_tag (
* way to the bottom now.
*/
tagstackidx = 0;
- } else if (tagstackidx >= tagstacklen) { /* count == 0? */
+ } else if (tagstackidx >= tagstacklen) { // count == 0?
EMSG(_(topmsg));
goto end_do_tag;
}
@@ -293,8 +307,8 @@ do_tag (
* file was changed) keep original position in tag stack.
*/
if (buflist_getfile(saved_fmark.fnum, saved_fmark.mark.lnum,
- GETF_SETMARK, forceit) == FAIL) {
- tagstackidx = oldtagstackidx; /* back to old posn */
+ GETF_SETMARK, forceit) == FAIL) {
+ tagstackidx = oldtagstackidx; // back to old posn
goto end_do_tag;
}
/* A BufReadPost autocommand may jump to the '" mark, but
@@ -305,12 +319,12 @@ do_tag (
curwin->w_cursor.lnum = saved_fmark.mark.lnum;
}
curwin->w_cursor.col = saved_fmark.mark.col;
- curwin->w_set_curswant = TRUE;
+ curwin->w_set_curswant = true;
check_cursor();
if ((fdo_flags & FDO_TAG) && old_KeyTyped)
foldOpenCursor();
- /* remove the old list of matches */
+ // remove the old list of matches
FreeWild(num_matches, matches);
cs_free_tags();
num_matches = 0;
@@ -325,8 +339,8 @@ do_tag (
cur_match = ptag_entry.cur_match;
cur_fnum = ptag_entry.cur_fnum;
} else {
- /* ":tag" (no argument): go to newer pattern */
- save_pos = TRUE; /* save the cursor position below */
+ // ":tag" (no argument): go to newer pattern
+ save_pos = true; // save the cursor position below
if ((tagstackidx += count - 1) >= tagstacklen) {
/*
* Beyond the last one, just give an error message and
@@ -335,8 +349,8 @@ do_tag (
*/
tagstackidx = tagstacklen - 1;
EMSG(_(topmsg));
- save_pos = FALSE;
- } else if (tagstackidx < 0) { /* must have been count == 0 */
+ save_pos = false;
+ } else if (tagstackidx < 0) { // must have been count == 0
EMSG(_(bottommsg));
tagstackidx = 0;
goto end_do_tag;
@@ -344,9 +358,9 @@ do_tag (
cur_match = tagstack[tagstackidx].cur_match;
cur_fnum = tagstack[tagstackidx].cur_fnum;
}
- new_tag = TRUE;
- } else { /* go to other matching tag */
- /* Save index for when selection is cancelled. */
+ new_tag = true;
+ } else { // go to other matching tag
+ // Save index for when selection is cancelled.
prevtagstackidx = tagstackidx;
if (g_do_tagpreview != 0) {
@@ -371,7 +385,7 @@ do_tag (
cur_match = MAXCOL - 1;
else if (cur_match < 0) {
EMSG(_("E425: Cannot go before first matching tag"));
- skip_msg = TRUE;
+ skip_msg = true;
cur_match = 0;
cur_fnum = curbuf->b_fnum;
}
@@ -418,15 +432,17 @@ do_tag (
* Repeat searching for tags, when a file has not been found.
*/
for (;; ) {
- /*
- * When desired match not found yet, try to find it (and others).
- */
- if (use_tagstack)
+ int other_name;
+ char_u *name;
+
+ // When desired match not found yet, try to find it (and others).
+ if (use_tagstack) {
name = tagstack[tagstackidx].tagname;
- else if (g_do_tagpreview != 0)
+ } else if (g_do_tagpreview != 0) {
name = ptag_entry.tagname;
- else
+ } else {
name = tag;
+ }
other_name = (tagmatchname == NULL || STRCMP(tagmatchname, name) != 0);
if (new_tag
|| (cur_match >= num_matches && max_num_matches != MAXCOL)
@@ -446,38 +462,49 @@ do_tag (
max_num_matches = cur_match + 1;
}
- /* when the argument starts with '/', use it as a regexp */
+ // when the argument starts with '/', use it as a regexp
if (!no_regexp && *name == '/') {
flags = TAG_REGEXP;
++name;
} else
flags = TAG_NOIC;
- if (type == DT_CSCOPE)
+ if (type == DT_CSCOPE) {
flags = TAG_CSCOPE;
- if (verbose)
+ }
+ if (verbose) {
flags |= TAG_VERBOSE;
+ }
+ if (!use_tfu) {
+ flags |= TAG_NO_TAGFUNC;
+ }
+
if (find_tags(name, &new_num_matches, &new_matches, flags,
- max_num_matches, buf_ffname) == OK
- && new_num_matches < max_num_matches)
- max_num_matches = MAXCOL; /* If less than max_num_matches
- found: all matches found. */
+ max_num_matches, buf_ffname) == OK
+ && new_num_matches < max_num_matches) {
+ max_num_matches = MAXCOL; // If less than max_num_matches
+ // found: all matches found.
+ }
/* If there already were some matches for the same name, move them
* to the start. Avoids that the order changes when using
* ":tnext" and jumping to another file. */
if (!new_tag && !other_name) {
- /* Find the position of each old match in the new list. Need
- * to use parse_match() to find the tag line. */
- idx = 0;
- for (j = 0; j < num_matches; ++j) {
+ 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++) {
parse_match(matches[j], &tagp);
for (i = idx; i < new_num_matches; ++i) {
parse_match(new_matches[i], &tagp2);
if (STRCMP(tagp.tagname, tagp2.tagname) == 0) {
- p = new_matches[i];
- for (k = i; k > idx; --k)
+ char_u *p = new_matches[i];
+ for (k = i; k > idx; k--) {
new_matches[k] = new_matches[k - 1];
+ }
new_matches[idx++] = p;
break;
}
@@ -504,304 +531,27 @@ do_tag (
// jump to count'th matching tag.
cur_match = count > 0 ? count - 1 : 0;
} else if (type == DT_SELECT || (type == DT_JUMP && num_matches > 1)) {
- // List all the matching tags.
- // 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);
- if (taglen < 18)
- taglen = 18;
- if (taglen > Columns - 25)
- taglen = MAXCOL;
- if (msg_col == 0)
- msg_didout = FALSE; /* overwrite previous message */
- msg_start();
- MSG_PUTS_ATTR(_(" # pri kind tag"), HL_ATTR(HLF_T));
- msg_clr_eos();
- taglen_advance(taglen);
- MSG_PUTS_ATTR(_("file\n"), HL_ATTR(HLF_T));
-
- for (i = 0; i < num_matches && !got_int; i++) {
- parse_match(matches[i], &tagp);
- if (!new_tag && ((g_do_tagpreview != 0 && i == ptag_entry.cur_match)
- || (use_tagstack
- && i == tagstack[tagstackidx].cur_match))) {
- *IObuff = '>';
- } else {
- *IObuff = ' ';
- }
- vim_snprintf((char *)IObuff + 1, IOSIZE - 1, "%2d %s ", i + 1,
- mt_names[matches[i][0] & MT_MASK]);
- msg_puts((const char *)IObuff);
- if (tagp.tagkind != NULL) {
- msg_outtrans_len(tagp.tagkind,
- (int)(tagp.tagkind_end - tagp.tagkind));
- }
- msg_advance(13);
- msg_outtrans_len_attr(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);
- msg_puts_long_attr(p, HL_ATTR(HLF_D));
- xfree(p);
-
- if (msg_col > 0)
- msg_putchar('\n');
- if (got_int)
- break;
- msg_advance(15);
-
- /* print any extra fields */
- command_end = tagp.command_end;
- if (command_end != NULL) {
- p = command_end + 3;
- while (*p && *p != '\r' && *p != '\n') {
- while (*p == TAB)
- ++p;
-
- /* skip "file:" without a value (static tag) */
- if (STRNCMP(p, "file:", 5) == 0
- && ascii_isspace(p[5])) {
- p += 5;
- continue;
- }
- /* skip "kind:<kind>" and "<kind>" */
- if (p == tagp.tagkind
- || (p + 5 == tagp.tagkind
- && STRNCMP(p, "kind:", 5) == 0)) {
- p = tagp.tagkind_end;
- continue;
- }
- // print all other extra fields
- attr = HL_ATTR(HLF_CM);
- while (*p && *p != '\r' && *p != '\n') {
- if (msg_col + ptr2cells(p) >= Columns) {
- msg_putchar('\n');
- if (got_int)
- break;
- msg_advance(15);
- }
- p = msg_outtrans_one(p, attr);
- if (*p == TAB) {
- msg_puts_attr(" ", attr);
- break;
- }
- if (*p == ':')
- attr = 0;
- }
- }
- if (msg_col > 15) {
- msg_putchar('\n');
- if (got_int)
- break;
- msg_advance(15);
- }
- } else {
- for (p = tagp.command;
- *p && *p != '\r' && *p != '\n'; ++p)
- ;
- command_end = p;
- }
-
- /*
- * Put the info (in several lines) at column 15.
- * Don't display "/^" and "?^".
- */
- p = tagp.command;
- if (*p == '/' || *p == '?') {
- ++p;
- if (*p == '^')
- ++p;
- }
- /* Remove leading whitespace from pattern */
- while (p != command_end && ascii_isspace(*p))
- ++p;
-
- while (p != command_end) {
- if (msg_col + (*p == TAB ? 1 : ptr2cells(p)) > Columns)
- msg_putchar('\n');
- if (got_int)
- break;
- msg_advance(15);
-
- // Skip backslash used for escaping a command char or a backslash.
- if (*p == '\\' && (*(p + 1) == *tagp.command
- || *(p + 1) == '\\')) {
- ++p;
- }
-
- if (*p == TAB) {
- msg_putchar(' ');
- ++p;
- } else
- p = msg_outtrans_one(p, 0);
-
- /* don't display the "$/;\"" and "$?;\"" */
- if (p == command_end - 2 && *p == '$'
- && *(p + 1) == *tagp.command)
- break;
- /* don't display matching '/' or '?' */
- if (p == command_end - 1 && *p == *tagp.command
- && (*p == '/' || *p == '?'))
- break;
- }
- if (msg_col)
- msg_putchar('\n');
- os_breakcheck();
- }
- if (got_int) {
- got_int = false; // only stop the listing
- }
+ print_tag_list(new_tag, use_tagstack, num_matches, matches);
ask_for_selection = true;
} else if (type == DT_LTAG) {
- list_T *list;
- char_u tag_name[128 + 1];
- char_u *fname;
- char_u *cmd;
-
- /*
- * Add the matching tags to the location list for the current
- * window.
- */
-
- fname = xmalloc(MAXPATHL + 1);
- cmd = xmalloc(CMDBUFFSIZE + 1);
- list = tv_list_alloc(num_matches);
-
- for (i = 0; i < num_matches; ++i) {
- int len, cmd_len;
- long lnum;
- dict_T *dict;
-
- parse_match(matches[i], &tagp);
-
- /* Save the tag name */
- len = (int)(tagp.tagname_end - tagp.tagname);
- if (len > 128)
- len = 128;
- STRLCPY(tag_name, tagp.tagname, len + 1);
-
- /* Save the tag file name */
- p = tag_full_fname(&tagp);
- STRLCPY(fname, p, MAXPATHL + 1);
- xfree(p);
-
- /*
- * Get the line number or the search pattern used to locate
- * the tag.
- */
- lnum = 0;
- if (isdigit(*tagp.command))
- /* Line number is used to locate the tag */
- lnum = atol((char *)tagp.command);
- else {
- char_u *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;
- if (cmd_end == NULL) {
- for (p = tagp.command;
- *p && *p != '\r' && *p != '\n'; ++p)
- ;
- cmd_end = p;
- }
-
- /*
- * Now, cmd_end points to the character after the
- * command. Adjust it to point to the last
- * character of the command.
- */
- cmd_end--;
-
- /*
- * Skip the '/' and '?' characters at the
- * beginning and end of the search pattern.
- */
- if (*cmd_start == '/' || *cmd_start == '?')
- cmd_start++;
-
- if (*cmd_end == '/' || *cmd_end == '?')
- cmd_end--;
-
- len = 0;
- cmd[0] = NUL;
-
- /*
- * If "^" is present in the tag search pattern, then
- * copy it first.
- */
- if (*cmd_start == '^') {
- STRCPY(cmd, "^");
- cmd_start++;
- len++;
- }
-
- /*
- * Precede the tag pattern with \V to make it very
- * nomagic.
- */
- STRCAT(cmd, "\\V");
- len += 2;
-
- cmd_len = (int)(cmd_end - cmd_start + 1);
- if (cmd_len > (CMDBUFFSIZE - 5))
- cmd_len = CMDBUFFSIZE - 5;
- STRNCAT(cmd, cmd_start, cmd_len);
- len += cmd_len;
-
- if (cmd[len - 1] == '$') {
- /*
- * Replace '$' at the end of the search pattern
- * with '\$'
- */
- cmd[len - 1] = '\\';
- cmd[len] = '$';
- len++;
- }
-
- cmd[len] = NUL;
- }
-
- dict = tv_dict_alloc();
- tv_list_append_dict(list, dict);
-
- tv_dict_add_str(dict, S_LEN("text"), (const char *)tag_name);
- tv_dict_add_str(dict, S_LEN("filename"), (const char *)fname);
- tv_dict_add_nr(dict, S_LEN("lnum"), lnum);
- if (lnum == 0) {
- tv_dict_add_str(dict, S_LEN("pattern"), (const char *)cmd);
- }
+ if (add_llist_tags(tag, num_matches, matches) == FAIL) {
+ goto end_do_tag;
}
- vim_snprintf((char *)IObuff, IOSIZE, "ltag %s", tag);
- set_errorlist(curwin, list, ' ', IObuff, NULL);
-
- tv_list_free(list);
- xfree(fname);
- xfree(cmd);
-
- cur_match = 0; /* Jump to the first tag */
+ cur_match = 0; // Jump to the first tag
}
if (ask_for_selection) {
// Ask to select a tag from the list.
i = prompt_for_number(NULL);
if (i <= 0 || i > num_matches || got_int) {
- /* no valid choice: don't change anything */
+ // no valid choice: don't change anything
if (use_tagstack) {
tagstack[tagstackidx].fmark = saved_fmark;
tagstackidx = prevtagstackidx;
}
cs_free_tags();
- jumped_to_tag = TRUE;
+ jumped_to_tag = true;
break;
}
cur_match = i - 1;
@@ -817,14 +567,25 @@ do_tag (
EMSG(_("E427: There is only one matching tag"));
else
EMSG(_("E428: Cannot go beyond last matching tag"));
- skip_msg = TRUE;
+ skip_msg = true;
}
cur_match = num_matches - 1;
}
if (use_tagstack) {
+ tagptrs_T tagp2;
+
tagstack[tagstackidx].cur_match = cur_match;
tagstack[tagstackidx].cur_fnum = cur_fnum;
- ++tagstackidx;
+
+ // store user-provided data originating from tagfunc
+ if (use_tfu && parse_match(matches[cur_match], &tagp2) == OK
+ && tagp2.user_data) {
+ XFREE_CLEAR(tagstack[tagstackidx].user_data);
+ tagstack[tagstackidx].user_data = vim_strnsave(
+ tagp2.user_data, tagp2.user_data_end - tagp2.user_data);
+ }
+
+ tagstackidx++;
} else if (g_do_tagpreview != 0) {
ptag_entry.cur_match = cur_match;
ptag_entry.cur_fnum = cur_fnum;
@@ -843,13 +604,14 @@ do_tag (
&& type != DT_CSCOPE
&& (num_matches > 1 || ic)
&& !skip_msg) {
- /* Give an indication of the number of matching tags */
- sprintf((char *)IObuff, _("tag %d of %d%s"),
- cur_match + 1,
- num_matches,
- max_num_matches != MAXCOL ? _(" or more") : "");
- if (ic)
+ // Give an indication of the number of matching tags
+ snprintf((char *)IObuff, sizeof(IObuff), _("tag %d of %d%s"),
+ cur_match + 1,
+ num_matches,
+ max_num_matches != MAXCOL ? _(" or more") : "");
+ if (ic) {
STRCAT(IObuff, _(" Using tag with different case!"));
+ }
if ((num_matches > prev_num_matches || new_tag)
&& num_matches > 1) {
if (ic) {
@@ -867,7 +629,7 @@ do_tag (
}
}
- /* Let the SwapExists event know what tag we are jumping to. */
+ // Let the SwapExists event know what tag we are jumping to.
vim_snprintf((char *)IObuff, IOSIZE, ":ta %s\r", name);
set_vim_var_string(VV_SWAPCOMMAND, (char *) IObuff, -1);
@@ -879,7 +641,7 @@ do_tag (
set_vim_var_string(VV_SWAPCOMMAND, NULL, -1);
if (i == NOTAGFILE) {
- /* File not found: try again with another matching tag */
+ // File not found: try again with another matching tag
if ((type == DT_PREV && cur_match > 0)
|| ((type == DT_TAG || type == DT_NEXT
|| type == DT_FIRST)
@@ -902,22 +664,352 @@ do_tag (
* tagstackidx is still valid. */
if (use_tagstack && tagstackidx > curwin->w_tagstacklen)
tagstackidx = curwin->w_tagstackidx;
- jumped_to_tag = TRUE;
+ jumped_to_tag = true;
}
}
break;
}
end_do_tag:
- /* Only store the new index when using the tagstack and it's valid. */
- if (use_tagstack && tagstackidx <= curwin->w_tagstacklen)
+ // Only store the new index when using the tagstack and it's valid.
+ if (use_tagstack && tagstackidx <= curwin->w_tagstacklen) {
curwin->w_tagstackidx = tagstackidx;
+ }
postponed_split = 0; // don't split next time
g_do_tagpreview = 0; // don't do tag preview next time
return jumped_to_tag;
}
+//
+// List all the matching tags.
+//
+static void
+print_tag_list(
+ int new_tag,
+ int use_tagstack,
+ int num_matches,
+ char_u **matches)
+{
+ taggy_T *tagstack = curwin->w_tagstack;
+ int tagstackidx = curwin->w_tagstackidx;
+ int i;
+ char_u *p;
+ char_u *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);
+ if (taglen < 18) {
+ taglen = 18;
+ }
+ if (taglen > Columns - 25) {
+ taglen = MAXCOL;
+ }
+ if (msg_col == 0) {
+ msg_didout = false; // overwrite previous message
+ }
+ msg_start();
+ msg_puts_attr(_(" # pri kind tag"), HL_ATTR(HLF_T));
+ msg_clr_eos();
+ taglen_advance(taglen);
+ msg_puts_attr(_("file\n"), HL_ATTR(HLF_T));
+
+ for (i = 0; i < num_matches && !got_int; i++) {
+ parse_match(matches[i], &tagp);
+ if (!new_tag && (
+ (g_do_tagpreview != 0
+ && i == ptag_entry.cur_match)
+ || (use_tagstack
+ && i == tagstack[tagstackidx].cur_match))) {
+ *IObuff = '>';
+ } else {
+ *IObuff = ' ';
+ }
+ vim_snprintf((char *)IObuff + 1, IOSIZE - 1,
+ "%2d %s ", i + 1,
+ mt_names[matches[i][0] & MT_MASK]);
+ msg_puts((char *)IObuff);
+ if (tagp.tagkind != NULL) {
+ msg_outtrans_len(tagp.tagkind,
+ (int)(tagp.tagkind_end - tagp.tagkind));
+ }
+ msg_advance(13);
+ msg_outtrans_len_attr(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);
+ if (p != NULL) {
+ msg_outtrans_attr(p, HL_ATTR(HLF_D));
+ XFREE_CLEAR(p);
+ }
+ if (msg_col > 0) {
+ msg_putchar('\n');
+ }
+ if (got_int) {
+ break;
+ }
+ msg_advance(15);
+
+ // print any extra fields
+ command_end = tagp.command_end;
+ if (command_end != NULL) {
+ p = command_end + 3;
+ while (*p && *p != '\r' && *p != '\n') {
+ while (*p == TAB) {
+ p++;
+ }
+
+ // skip "file:" without a value (static tag)
+ if (STRNCMP(p, "file:", 5) == 0 && ascii_isspace(p[5])) {
+ p += 5;
+ continue;
+ }
+ // skip "kind:<kind>" and "<kind>"
+ if (p == tagp.tagkind
+ || (p + 5 == tagp.tagkind
+ && STRNCMP(p, "kind:", 5) == 0)) {
+ p = tagp.tagkind_end;
+ continue;
+ }
+ // print all other extra fields
+ attr = HL_ATTR(HLF_CM);
+ while (*p && *p != '\r' && *p != '\n') {
+ if (msg_col + ptr2cells(p) >= Columns) {
+ msg_putchar('\n');
+ if (got_int) {
+ break;
+ }
+ msg_advance(15);
+ }
+ p = msg_outtrans_one(p, attr);
+ if (*p == TAB) {
+ msg_puts_attr(" ", attr);
+ break;
+ }
+ if (*p == ':') {
+ attr = 0;
+ }
+ }
+ }
+ if (msg_col > 15) {
+ msg_putchar('\n');
+ if (got_int) {
+ break;
+ }
+ msg_advance(15);
+ }
+ } else {
+ for (p = tagp.command;
+ *p && *p != '\r' && *p != '\n';
+ p++) {
+ }
+ command_end = p;
+ }
+
+ // Put the info (in several lines) at column 15.
+ // Don't display "/^" and "?^".
+ p = tagp.command;
+ if (*p == '/' || *p == '?') {
+ p++;
+ if (*p == '^') {
+ p++;
+ }
+ }
+ // Remove leading whitespace from pattern
+ while (p != command_end && ascii_isspace(*p)) {
+ p++;
+ }
+
+ while (p != command_end) {
+ if (msg_col + (*p == TAB ? 1 : ptr2cells(p)) > Columns) {
+ msg_putchar('\n');
+ }
+ if (got_int) {
+ break;
+ }
+ msg_advance(15);
+
+ // skip backslash used for escaping a command char or
+ // a backslash
+ if (*p == '\\' && (*(p + 1) == *tagp.command
+ || *(p + 1) == '\\')) {
+ p++;
+ }
+
+ if (*p == TAB) {
+ msg_putchar(' ');
+ p++;
+ } else {
+ p = msg_outtrans_one(p, 0);
+ }
+
+ // don't display the "$/;\"" and "$?;\""
+ if (p == command_end - 2 && *p == '$'
+ && *(p + 1) == *tagp.command) {
+ break;
+ }
+ // don't display matching '/' or '?'
+ if (p == command_end - 1 && *p == *tagp.command
+ && (*p == '/' || *p == '?')) {
+ break;
+ }
+ }
+ if (msg_col) {
+ msg_putchar('\n');
+ }
+ os_breakcheck();
+ }
+ if (got_int) {
+ got_int = false; // only stop the listing
+ }
+}
+
+//
+// Add the matching tags to the location list for the current
+// window.
+//
+static int
+add_llist_tags(
+ char_u *tag,
+ int num_matches,
+ char_u **matches)
+{
+ list_T *list;
+ char_u tag_name[128 + 1];
+ char_u *fname;
+ char_u *cmd;
+ int i;
+ char_u *p;
+ tagptrs_T tagp;
+
+ fname = xmalloc(MAXPATHL + 1);
+ cmd = xmalloc(CMDBUFFSIZE + 1);
+ list = tv_list_alloc(0);
+
+ for (i = 0; i < num_matches; i++) {
+ int len, cmd_len;
+ long lnum;
+ dict_T *dict;
+
+ parse_match(matches[i], &tagp);
+
+ // Save the tag name
+ len = (int)(tagp.tagname_end - tagp.tagname);
+ if (len > 128) {
+ len = 128;
+ }
+ xstrlcpy((char *)tag_name, (const char *)tagp.tagname, len);
+ tag_name[len] = NUL;
+
+ // Save the tag file name
+ p = tag_full_fname(&tagp);
+ if (p == NULL) {
+ continue;
+ }
+ xstrlcpy((char *)fname, (const char *)p, MAXPATHL);
+ XFREE_CLEAR(p);
+
+ // Get the line number or the search pattern used to locate
+ // the tag.
+ lnum = 0;
+ if (isdigit(*tagp.command)) {
+ // Line number is used to locate the tag
+ lnum = atol((char *)tagp.command);
+ } else {
+ char_u *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;
+ if (cmd_end == NULL) {
+ for (p = tagp.command;
+ *p && *p != '\r' && *p != '\n'; p++) {
+ }
+ cmd_end = p;
+ }
+
+ // Now, cmd_end points to the character after the
+ // command. Adjust it to point to the last
+ // character of the command.
+ cmd_end--;
+
+ // Skip the '/' and '?' characters at the
+ // beginning and end of the search pattern.
+ if (*cmd_start == '/' || *cmd_start == '?') {
+ cmd_start++;
+ }
+
+ if (*cmd_end == '/' || *cmd_end == '?') {
+ cmd_end--;
+ }
+
+ len = 0;
+ cmd[0] = NUL;
+
+ // If "^" is present in the tag search pattern, then
+ // copy it first.
+ if (*cmd_start == '^') {
+ STRCPY(cmd, "^");
+ cmd_start++;
+ len++;
+ }
+
+ // Precede the tag pattern with \V to make it very
+ // nomagic.
+ STRCAT(cmd, "\\V");
+ len += 2;
+
+ cmd_len = (int)(cmd_end - cmd_start + 1);
+ if (cmd_len > (CMDBUFFSIZE - 5)) {
+ cmd_len = CMDBUFFSIZE - 5;
+ }
+ xstrlcat((char *)cmd, (char *)cmd_start, cmd_len);
+ len += cmd_len;
+
+ if (cmd[len - 1] == '$') {
+ // Replace '$' at the end of the search pattern
+ // with '\$'
+ cmd[len - 1] = '\\';
+ cmd[len] = '$';
+ len++;
+ }
+
+ cmd[len] = NUL;
+ }
+
+ dict = tv_dict_alloc();
+ tv_list_append_dict(list, dict);
+
+ tv_dict_add_str(dict, S_LEN("text"), (const char *)tag_name);
+ tv_dict_add_str(dict, S_LEN("filename"), (const char *)fname);
+ tv_dict_add_nr(dict, S_LEN("lnum"), lnum);
+ if (lnum == 0) {
+ tv_dict_add_str(dict, S_LEN("pattern"), (const char *)cmd);
+ }
+ }
+
+ vim_snprintf((char *)IObuff, IOSIZE, "ltag %s", tag);
+ set_errorlist(curwin, list, ' ', IObuff, NULL);
+
+ tv_list_free(list);
+ XFREE_CLEAR(fname);
+ XFREE_CLEAR(cmd);
+
+ return OK;
+}
+
/*
* Free cached tags.
*/
@@ -1029,6 +1121,210 @@ static void prepare_pats(pat_T *pats, int has_re)
pats->regmatch.regprog = NULL;
}
+//
+// Call the user-defined function to generate a list of tags used by
+// find_tags().
+//
+// Return OK if at least 1 tag has been successfully found,
+// NOTDONE if the function returns v:null, and FAIL otherwise.
+//
+static int find_tagfunc_tags(
+ char_u *pat, // pattern supplied to the user-defined function
+ garray_T *ga, // the tags will be placed here
+ int *match_count, // here the number of tags found will be placed
+ int flags, // flags from find_tags (TAG_*)
+ char_u *buf_ffname) // name of buffer for priority
+{
+ pos_T save_pos;
+ list_T *taglist;
+ int ntags = 0;
+ int result = FAIL;
+ typval_T args[4];
+ typval_T rettv;
+ char_u flagString[3];
+ dict_T *d;
+ taggy_T *tag = &curwin->w_tagstack[curwin->w_tagstackidx];
+
+ if (*curbuf->b_p_tfu == NUL) {
+ return FAIL;
+ }
+
+ args[0].v_type = VAR_STRING;
+ args[0].vval.v_string = pat;
+ args[1].v_type = VAR_STRING;
+ args[1].vval.v_string = flagString;
+
+ // create 'info' dict argument
+ d = tv_dict_alloc();
+ if (tag->user_data != NULL) {
+ tv_dict_add_str(d, S_LEN("user_data"), (const char *)tag->user_data);
+ }
+ if (buf_ffname != NULL) {
+ tv_dict_add_str(d, S_LEN("buf_ffname"), (const char *)buf_ffname);
+ }
+
+ d->dv_refcount++;
+ args[2].v_type = VAR_DICT;
+ args[2].vval.v_dict = d;
+
+ args[3].v_type = VAR_UNKNOWN;
+
+ vim_snprintf((char *)flagString, sizeof(flagString),
+ "%s%s",
+ g_tag_at_cursor ? "c": "",
+ flags & TAG_INS_COMP ? "i": "");
+
+ save_pos = curwin->w_cursor;
+ result = call_vim_function(curbuf->b_p_tfu, 3, args, &rettv);
+ curwin->w_cursor = save_pos; // restore the cursor position
+ d->dv_refcount--;
+
+ if (result == FAIL) {
+ return FAIL;
+ }
+ if (rettv.v_type == VAR_SPECIAL && rettv.vval.v_special == kSpecialVarNull) {
+ tv_clear(&rettv);
+ return NOTDONE;
+ }
+ if (rettv.v_type != VAR_LIST || !rettv.vval.v_list) {
+ tv_clear(&rettv);
+ EMSG(_(tfu_inv_ret_msg));
+ return FAIL;
+ }
+ taglist = rettv.vval.v_list;
+
+ TV_LIST_ITER_CONST(taglist, li, {
+ char_u *res_name;
+ char_u *res_fname;
+ char_u *res_cmd;
+ char_u *res_kind;
+ int has_extra = 0;
+ int name_only = flags & TAG_NAMES;
+
+ if (TV_LIST_ITEM_TV(li)->v_type != VAR_DICT) {
+ EMSG(_(tfu_inv_ret_msg));
+ break;
+ }
+
+ size_t len = 2;
+ res_name = NULL;
+ res_fname = NULL;
+ res_cmd = NULL;
+ res_kind = NULL;
+
+ TV_DICT_ITER(TV_LIST_ITEM_TV(li)->vval.v_dict, di, {
+ const char_u *dict_key = di->di_key;
+ typval_T *tv = &di->di_tv;
+
+ if (tv->v_type != VAR_STRING || tv->vval.v_string == NULL) {
+ continue;
+ }
+
+ len += STRLEN(tv->vval.v_string) + 1; // Space for "\tVALUE"
+ if (!STRCMP(dict_key, "name")) {
+ res_name = tv->vval.v_string;
+ continue;
+ }
+ if (!STRCMP(dict_key, "filename")) {
+ res_fname = tv->vval.v_string;
+ continue;
+ }
+ if (!STRCMP(dict_key, "cmd")) {
+ res_cmd = tv->vval.v_string;
+ continue;
+ }
+ has_extra = 1;
+ if (!STRCMP(dict_key, "kind")) {
+ res_kind = tv->vval.v_string;
+ continue;
+ }
+ // Other elements will be stored as "\tKEY:VALUE"
+ // Allocate space for the key and the colon
+ len += STRLEN(dict_key) + 1;
+ });
+
+ if (has_extra) {
+ len += 2; // need space for ;"
+ }
+
+ if (!res_name || !res_fname || !res_cmd) {
+ EMSG(_(tfu_inv_ret_msg));
+ break;
+ }
+
+ char_u *const mfp = name_only ? vim_strsave(res_name) : xmalloc(len + 2);
+
+ if (!name_only) {
+ char_u *p = mfp;
+
+ *p++ = MT_GL_OTH + 1; // mtt
+ *p++ = TAG_SEP; // no tag file name
+
+ STRCPY(p, res_name);
+ p += STRLEN(p);
+
+ *p++ = TAB;
+ STRCPY(p, res_fname);
+ p += STRLEN(p);
+
+ *p++ = TAB;
+ STRCPY(p, res_cmd);
+ p += STRLEN(p);
+
+ if (has_extra) {
+ STRCPY(p, ";\"");
+ p += STRLEN(p);
+
+ if (res_kind) {
+ *p++ = TAB;
+ STRCPY(p, res_kind);
+ p += STRLEN(p);
+ }
+
+ TV_DICT_ITER(TV_LIST_ITEM_TV(li)->vval.v_dict, di, {
+ const char_u *dict_key = di->di_key;
+ typval_T *tv = &di->di_tv;
+ if (tv->v_type != VAR_STRING || tv->vval.v_string == NULL) {
+ continue;
+ }
+
+ if (!STRCMP(dict_key, "name")) {
+ continue;
+ }
+ if (!STRCMP(dict_key, "filename")) {
+ continue;
+ }
+ if (!STRCMP(dict_key, "cmd")) {
+ continue;
+ }
+ if (!STRCMP(dict_key, "kind")) {
+ continue;
+ }
+
+ *p++ = TAB;
+ STRCPY(p, dict_key);
+ p += STRLEN(p);
+ STRCPY(p, ":");
+ p += STRLEN(p);
+ STRCPY(p, tv->vval.v_string);
+ p += STRLEN(p);
+ });
+ }
+ }
+
+ // Add all matches because tagfunc should do filtering.
+ ga_grow(ga, 1);
+ ((char_u **)(ga->ga_data))[ga->ga_len++] = mfp;
+ ntags++;
+ result = OK;
+ });
+
+ tv_clear(&rettv);
+
+ *match_count = ntags;
+ return result;
+}
+
/*
* find_tags() - search for tags in tags files
*
@@ -1054,12 +1350,13 @@ static void prepare_pats(pat_T *pats, int has_re)
* TAG_NOIC don't always ignore case
* TAG_KEEP_LANG keep language
* TAG_CSCOPE use cscope results for tags
+ * TAG_NO_TAGFUNC do not call the 'tagfunc' function
*/
-int
-find_tags (
- char_u *pat, /* pattern to search for */
- int *num_matches, /* return: number of matches found */
- char_u ***matchesp, /* return: array of matches found */
+int
+find_tags(
+ char_u *pat, // pattern to search for
+ int *num_matches, // return: number of matches found
+ char_u ***matchesp, // return: array of matches found
int flags,
int mincount, /* MAXCOL: find all matches
other: minimal number of matches */
@@ -1094,7 +1391,6 @@ find_tags (
int low_char; // first char at low_offset
int high_char; // first char at high_offset
} search_info;
- off_T filesize;
int tagcmp;
off_T offset;
int round;
@@ -1144,6 +1440,7 @@ find_tags (
int get_it_again = FALSE;
int use_cscope = (flags & TAG_CSCOPE);
int verbose = (flags & TAG_VERBOSE);
+ int use_tfu = ((flags & TAG_NO_TAGFUNC) == 0);
int save_p_ic = p_ic;
// Change the value of 'ignorecase' according to 'tagcase' for the
@@ -1221,6 +1518,16 @@ find_tags (
// uninitialised.
memset(&search_info, 0, 1); // -V512
+ if (*curbuf->b_p_tfu != NUL && use_tfu && !tfu_in_use) {
+ tfu_in_use = true;
+ retval = find_tagfunc_tags(pat, &ga_match[0], &match_count,
+ flags, buf_ffname);
+ tfu_in_use = false;
+ if (retval != NOTDONE) {
+ goto findtag_end;
+ }
+ }
+
/*
* When finding a specified number of matches, first try with matching
* case, so binary search can be used, and try ignore-case matches in a
@@ -1352,12 +1659,9 @@ find_tags (
break; /* End the binary search without a match. */
else
search_info.curr_offset = offset;
- }
- /*
- * Skipping back (after a match during binary search).
- */
- else if (state == TS_SKIP_BACK) {
- search_info.curr_offset -= LSIZE * 2;
+ } else if (state == TS_SKIP_BACK) {
+ // Skipping back (after a match during binary search).
+ search_info.curr_offset -= lbuf_size * 2;
if (search_info.curr_offset < 0) {
search_info.curr_offset = 0;
rewind(fp);
@@ -1373,7 +1677,7 @@ find_tags (
/* Adjust the search file offset to the correct position */
search_info.curr_offset_used = search_info.curr_offset;
vim_fseek(fp, search_info.curr_offset, SEEK_SET);
- eof = vim_fgets(lbuf, LSIZE, fp);
+ eof = vim_fgets(lbuf, lbuf_size, fp);
if (!eof && search_info.curr_offset != 0) {
/* The explicit cast is to work around a bug in gcc 3.4.2
* (repeated below). */
@@ -1383,12 +1687,12 @@ find_tags (
vim_fseek(fp, search_info.low_offset, SEEK_SET);
search_info.curr_offset = search_info.low_offset;
}
- eof = vim_fgets(lbuf, LSIZE, fp);
+ eof = vim_fgets(lbuf, lbuf_size, fp);
}
/* skip empty and blank lines */
while (!eof && vim_isblankline(lbuf)) {
search_info.curr_offset = vim_ftell(fp);
- eof = vim_fgets(lbuf, LSIZE, fp);
+ eof = vim_fgets(lbuf, lbuf_size, fp);
}
if (eof) {
/* Hit end of file. Skip backwards. */
@@ -1404,10 +1708,9 @@ find_tags (
else {
/* skip empty and blank lines */
do {
- if (use_cscope)
- eof = cs_fgets(lbuf, LSIZE);
- else
- eof = vim_fgets(lbuf, LSIZE, fp);
+ eof = use_cscope
+ ? cs_fgets(lbuf, lbuf_size)
+ : vim_fgets(lbuf, lbuf_size, fp);
} while (!eof && vim_isblankline(lbuf));
if (eof) {
@@ -1503,19 +1806,21 @@ line_read_in:
state = TS_LINEAR;
}
- /*
- * When starting a binary search, get the size of the file and
- * compute the first offset.
- */
+ // When starting a binary search, get the size of the file and
+ // compute the first offset.
if (state == TS_BINARY) {
- // Get the tag file size.
- if ((filesize = vim_lseek(fileno(fp), (off_T)0L, SEEK_END)) <= 0) {
+ if (vim_fseek(fp, 0, SEEK_END) != 0) {
+ // can't seek, don't use binary search
state = TS_LINEAR;
} else {
- vim_lseek(fileno(fp), (off_T)0L, SEEK_SET);
-
- /* Calculate the first read offset in the file. Start
- * the search in the middle of the file. */
+ // Get the tag file size.
+ // Don't use lseek(), it doesn't work
+ // properly on MacOS Catalina.
+ const off_T filesize = vim_ftell(fp);
+ vim_fseek(fp, 0, SEEK_SET);
+
+ // Calculate the first read offset in the file. Start
+ // the search in the middle of the file.
search_info.low_offset = 0;
search_info.low_char = 0;
search_info.high_offset = filesize;
@@ -1530,19 +1835,14 @@ parse_line:
// When the line is too long the NUL will not be in the
// last-but-one byte (see vim_fgets()).
// Has been reported for Mozilla JS with extremely long names.
- // In that case we can't parse it and we ignore the line.
- if (lbuf[LSIZE - 2] != NUL && !use_cscope) {
- if (p_verbose >= 5) {
- verbose_enter();
- MSG(_("Ignoring long line in tags file"));
- verbose_leave();
- }
- if (state != TS_LINEAR) {
- // Avoid getting stuck.
- linear = true;
- state = TS_LINEAR;
- vim_fseek(fp, search_info.low_offset, SEEK_SET);
- }
+ // In that case we need to increase lbuf_size.
+ if (lbuf[lbuf_size - 2] != NUL && !use_cscope) {
+ lbuf_size *= 2;
+ xfree(lbuf);
+ lbuf = xmalloc(lbuf_size);
+ // this will try the same thing again, make sure the offset is
+ // different
+ search_info.curr_offset = 0;
continue;
}
@@ -1802,7 +2102,6 @@ parse_line:
}
}
} else {
-#define TAG_SEP 0x02
size_t tag_fname_len = STRLEN(tag_fname);
// Save the tag in a buffer.
// Use 0x02 to separate fields (Can't use NUL, because the
@@ -1986,9 +2285,7 @@ void free_tag_stuff(void)
do_tag(NULL, DT_FREE, 0, 0, 0);
tag_freematch();
- if (ptag_entry.tagname) {
- XFREE_CLEAR(ptag_entry.tagname);
- }
+ tagstack_clear_entry(&ptag_entry);
}
#endif
@@ -1999,11 +2296,11 @@ void free_tag_stuff(void)
*
* Return FAIL if no more tag file names, OK otherwise.
*/
-int
-get_tagfname (
- tagname_T *tnp, /* holds status info */
- int first, /* TRUE when first file name is wanted */
- char_u *buf /* pointer to buffer of MAXPATHL chars */
+int
+get_tagfname(
+ tagname_T *tnp, // holds status info
+ int first, // TRUE when first file name is wanted
+ char_u *buf // pointer to buffer of MAXPATHL chars
)
{
char_u *fname = NULL;
@@ -2128,9 +2425,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_u *lbuf, /* line to be parsed */
+static int
+parse_tag_line(
+ char_u *lbuf, // line to be parsed
tagptrs_T *tagp
)
{
@@ -2211,10 +2508,10 @@ static size_t matching_line_len(const char_u *const lbuf)
*
* Return OK or FAIL.
*/
-static int
-parse_match (
- char_u *lbuf, /* input: matching line */
- tagptrs_T *tagp /* output: pointers into the line */
+static int
+parse_match(
+ char_u *lbuf, // input: matching line
+ tagptrs_T *tagp // output: pointers into the line
)
{
int retval;
@@ -2229,6 +2526,8 @@ parse_match (
tagp);
tagp->tagkind = NULL;
+ tagp->user_data = NULL;
+ tagp->tagline = 0;
tagp->command_end = NULL;
if (retval == OK) {
@@ -2238,34 +2537,49 @@ parse_match (
tagp->command_end = p;
if (p > tagp->command && p[-1] == '|') {
tagp->command_end = p - 1; // drop trailing bar
- } else {
- tagp->command_end = p;
}
p += 2; // skip ";\""
if (*p++ == TAB) {
- while (ASCII_ISALPHA(*p)) {
+ // Accept ASCII alphabetic kind characters and any multi-byte
+ // character.
+ while (ASCII_ISALPHA(*p) || utfc_ptr2len(p) > 1) {
if (STRNCMP(p, "kind:", 5) == 0) {
tagp->tagkind = p + 5;
+ } else if (STRNCMP(p, "user_data:", 10) == 0) {
+ tagp->user_data = p + 10;
+ } else if (STRNCMP(p, "line:", 5) == 0) {
+ tagp->tagline = atoi((char *)p + 5);
+ }
+ if (tagp->tagkind != NULL && tagp->user_data != NULL) {
break;
}
+
pc = vim_strchr(p, ':');
pt = vim_strchr(p, '\t');
if (pc == NULL || (pt != NULL && pc > pt)) {
tagp->tagkind = p;
- break;
}
if (pt == NULL)
break;
- p = pt + 1;
+ p = pt;
+ MB_PTR_ADV(p);
}
}
}
if (tagp->tagkind != NULL) {
for (p = tagp->tagkind;
- *p && *p != '\t' && *p != '\r' && *p != '\n'; ++p)
- ;
+ *p && *p != '\t' && *p != '\r' && *p != '\n';
+ MB_PTR_ADV(p)) {
+ }
tagp->tagkind_end = p;
}
+ if (tagp->user_data != NULL) {
+ for (p = tagp->user_data;
+ *p && *p != '\t' && *p != '\r' && *p != '\n';
+ MB_PTR_ADV(p)) {
+ }
+ tagp->user_data_end = p;
+ }
}
return retval;
}
@@ -2334,6 +2648,9 @@ static int jumpto_tag(
str = tagp.command;
for (pbuf_end = pbuf; *str && *str != '\n' && *str != '\r'; ) {
*pbuf_end++ = *str++;
+ if (pbuf_end - pbuf + 1 >= LSIZE) {
+ break;
+ }
}
*pbuf_end = NUL;
@@ -2485,9 +2802,15 @@ static int jumpto_tag(
p_ic = FALSE; /* don't ignore case now */
p_scs = FALSE;
save_lnum = curwin->w_cursor.lnum;
- curwin->w_cursor.lnum = 0; /* start search before first line */
+ if (tagp.tagline > 0) {
+ // start search before line from "line:" field
+ curwin->w_cursor.lnum = tagp.tagline - 1;
+ } else {
+ // start search before first line
+ curwin->w_cursor.lnum = 0;
+ }
if (do_search(NULL, pbuf[0], pbuf + 1, (long)1,
- search_options, NULL, NULL)) {
+ search_options, NULL)) {
retval = OK;
} else {
int found = 1;
@@ -2498,20 +2821,18 @@ static int jumpto_tag(
*/
p_ic = TRUE;
if (!do_search(NULL, pbuf[0], pbuf + 1, (long)1,
- search_options, NULL, NULL)) {
+ search_options, NULL)) {
// Failed to find pattern, take a guess: "^func ("
found = 2;
(void)test_for_static(&tagp);
cc = *tagp.tagname_end;
*tagp.tagname_end = NUL;
snprintf((char *)pbuf, LSIZE, "^%s\\s\\*(", tagp.tagname);
- if (!do_search(NULL, '/', pbuf, (long)1,
- search_options, NULL, NULL)) {
+ if (!do_search(NULL, '/', pbuf, (long)1, search_options, NULL)) {
// Guess again: "^char * \<func ("
snprintf((char *)pbuf, LSIZE, "^\\[#a-zA-Z_]\\.\\*\\<%s\\s\\*(",
tagp.tagname);
- if (!do_search(NULL, '/', pbuf, (long)1,
- search_options, NULL, NULL)) {
+ if (!do_search(NULL, '/', pbuf, (long)1, search_options, NULL)) {
found = 0;
}
}
@@ -2666,7 +2987,8 @@ static int test_for_current(char_u *fname, char_u *fname_end, char_u *tag_fname,
*fname_end = NUL;
}
fullname = expand_tag_fname(fname, tag_fname, true);
- retval = (path_full_compare(fullname, buf_ffname, true) & kEqualFiles);
+ retval = (path_full_compare(fullname, buf_ffname, true, true)
+ & kEqualFiles);
xfree(fullname);
*fname_end = c;
}
@@ -2715,6 +3037,15 @@ static int find_extra(char_u **pp)
return FAIL;
}
+//
+// Free a single entry in a tag stack
+//
+static void tagstack_clear_entry(taggy_T *item)
+{
+ XFREE_CLEAR(item->tagname);
+ XFREE_CLEAR(item->user_data);
+}
+
int
expand_tags (
int tagnames, /* expand tag names */
@@ -2734,14 +3065,16 @@ expand_tags (
tagnmflag = TAG_NAMES;
else
tagnmflag = 0;
- if (pat[0] == '/')
+ if (pat[0] == '/') {
ret = find_tags(pat + 1, num_file, file,
- TAG_REGEXP | tagnmflag | TAG_VERBOSE,
- TAG_MANY, curbuf->b_ffname);
- else
+ TAG_REGEXP | tagnmflag | TAG_VERBOSE | TAG_NO_TAGFUNC,
+ TAG_MANY, curbuf->b_ffname);
+ } else {
ret = find_tags(pat, num_file, file,
- TAG_REGEXP | tagnmflag | TAG_VERBOSE | TAG_NOIC,
- TAG_MANY, curbuf->b_ffname);
+ TAG_REGEXP | tagnmflag | TAG_VERBOSE
+ | TAG_NO_TAGFUNC | TAG_NOIC,
+ TAG_MANY, curbuf->b_ffname);
+ }
if (ret == OK && !tagnames) {
/* Reorganize the tags for display and matching as strings of:
* "<tagname>\0<kind>\0<filename>\0"
@@ -2767,8 +3100,8 @@ expand_tags (
* Add a tag field to the dictionary "dict".
* Return OK or FAIL.
*/
-static int
-add_tag_field (
+static int
+add_tag_field(
dict_T *dict,
const char *field_name,
const char_u *start, // start of the value
@@ -2830,9 +3163,11 @@ int get_tags(list_T *list, char_u *pat, char_u *buf_fname)
is_static = test_for_static(&tp);
- /* Skip pseudo-tag lines. */
- if (STRNCMP(tp.tagname, "!_TAG_", 6) == 0)
+ // Skip pseudo-tag lines.
+ if (STRNCMP(tp.tagname, "!_TAG_", 6) == 0) {
+ xfree(matches[i]);
continue;
+ }
dict = tv_dict_alloc();
tv_list_append_dict(list, dict);
@@ -2851,7 +3186,8 @@ int get_tags(list_T *list, char_u *pat, char_u *buf_fname)
if (tp.command_end != NULL) {
for (char_u *p = tp.command_end + 3;
- *p != NUL && *p != '\n' && *p != '\r'; p++) {
+ *p != NUL && *p != '\n' && *p != '\r';
+ MB_PTR_ADV(p)) {
if (p == tp.tagkind
|| (p + 5 == tp.tagkind && STRNCMP(p, "kind:", 5) == 0)) {
// skip "kind:<kind>" and "<kind>"
@@ -2903,6 +3239,9 @@ static void get_tag_details(taggy_T *tag, dict_T *retdict)
tv_dict_add_str(retdict, S_LEN("tagname"), (const char *)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);
+ }
pos = tv_list_alloc(4);
tv_dict_add_list(retdict, S_LEN("from"), pos);
@@ -2941,7 +3280,7 @@ static void tagstack_clear(win_T *wp)
{
// Free the current tag stack
for (int i = 0; i < wp->w_tagstacklen; i++) {
- xfree(wp->w_tagstack[i].tagname);
+ tagstack_clear_entry(&wp->w_tagstack[i]);
}
wp->w_tagstacklen = 0;
wp->w_tagstackidx = 0;
@@ -2952,7 +3291,7 @@ static void tagstack_clear(win_T *wp)
static void tagstack_shift(win_T *wp)
{
taggy_T *tagstack = wp->w_tagstack;
- xfree(tagstack[0].tagname);
+ tagstack_clear_entry(&tagstack[0]);
for (int i = 1; i < wp->w_tagstacklen; i++) {
tagstack[i - 1] = tagstack[i];
}
@@ -2966,7 +3305,8 @@ static void tagstack_push_item(
int cur_fnum,
int cur_match,
pos_T mark,
- int fnum)
+ int fnum,
+ char_u *user_data)
{
taggy_T *tagstack = wp->w_tagstack;
int idx = wp->w_tagstacklen; // top of the stack
@@ -2986,6 +3326,7 @@ static void tagstack_push_item(
}
tagstack[idx].fmark.mark = mark;
tagstack[idx].fmark.fnum = fnum;
+ tagstack[idx].user_data = user_data;
}
// Add a list of items to the tag stack in the specified window
@@ -3021,10 +3362,13 @@ static void tagstack_push_items(win_T *wp, list_T *l)
if (mark.col > 0) {
mark.col--;
}
- tagstack_push_item(wp, tagname,
- (int)tv_dict_get_number(itemdict, "bufnr"),
- (int)tv_dict_get_number(itemdict, "matchnr") - 1,
- mark, fnum);
+ tagstack_push_item(
+ wp,
+ tagname,
+ (int)tv_dict_get_number(itemdict, "bufnr"),
+ (int)tv_dict_get_number(itemdict, "matchnr") - 1,
+ mark, fnum,
+ (char_u *)tv_dict_get_string(itemdict, "user_data", true));
}
}
@@ -3042,27 +3386,51 @@ static void tagstack_set_curidx(win_T *wp, int curidx)
}
// Set the tag stack entries of the specified window.
-// 'action' is set to either 'a' for append or 'r' for replace.
-int set_tagstack(win_T *wp, dict_T *d, int action)
+// 'action' is set to one of:
+// 'a' for append
+// 'r' for replace
+// 't' for truncate
+int set_tagstack(win_T *wp, const dict_T *d, int action)
+ FUNC_ATTR_NONNULL_ARG(1)
{
dictitem_T *di;
- list_T *l;
+ list_T *l = NULL;
+
+ // not allowed to alter the tag stack entries from inside tagfunc
+ if (tfu_in_use) {
+ EMSG(_(recurmsg));
+ return FAIL;
+ }
if ((di = tv_dict_find(d, "items", -1)) != NULL) {
if (di->di_tv.v_type != VAR_LIST) {
return FAIL;
}
l = di->di_tv.vval.v_list;
+ }
+
+ if ((di = tv_dict_find(d, "curidx", -1)) != NULL) {
+ tagstack_set_curidx(wp, (int)tv_get_number(&di->di_tv) - 1);
+ }
+ if (action == 't') { // truncate the stack
+ taggy_T *const tagstack = wp->w_tagstack;
+ const int tagstackidx = wp->w_tagstackidx;
+ int tagstacklen = wp->w_tagstacklen;
+ // delete all the tag stack entries above the current entry
+ while (tagstackidx < tagstacklen) {
+ tagstack_clear_entry(&tagstack[--tagstacklen]);
+ }
+ wp->w_tagstacklen = tagstacklen;
+ }
- if (action == 'r') {
+ if (l != NULL) {
+ if (action == 'r') { // replace the stack
tagstack_clear(wp);
}
tagstack_push_items(wp, l);
- }
-
- if ((di = tv_dict_find(d, "curidx", -1)) != NULL) {
- tagstack_set_curidx(wp, (int)tv_get_number(&di->di_tv) - 1);
+ // set the current index after the last entry
+ wp->w_tagstackidx = wp->w_tagstacklen;
}
return OK;
diff --git a/src/nvim/tag.h b/src/nvim/tag.h
index a8fddd05da..9f671043b3 100644
--- a/src/nvim/tag.h
+++ b/src/nvim/tag.h
@@ -20,20 +20,21 @@
#define DT_LTAG 11 /* tag using location list */
#define DT_FREE 99 /* free cached matches */
-/*
- * 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_CSCOPE 16 /* cscope tag */
-#define TAG_VERBOSE 32 /* message verbosity */
-#define TAG_INS_COMP 64 /* Currently doing insert completion */
-#define TAG_KEEP_LANG 128 /* keep current language */
+//
+// 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_CSCOPE 16 // cscope tag
+#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'
-#define TAG_MANY 300 /* When finding many tags (for completion),
- find up to this many tags */
+#define TAG_MANY 300 // When finding many tags (for completion),
+ // find up to this many tags
/*
* Structure used for get_tagfname().
diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c
index 8fcc8bf0a5..52d3eef810 100644
--- a/src/nvim/terminal.c
+++ b/src/nvim/terminal.c
@@ -138,6 +138,8 @@ struct terminal {
int pressed_button; // which mouse button is pressed
bool pending_resize; // pending width/height
+ bool color_set[16];
+
size_t refcount; // reference count
};
@@ -220,8 +222,6 @@ Terminal *terminal_open(TerminalOptions opts)
rv->sb_size = (size_t)curbuf->b_p_scbk;
rv->sb_buffer = xmalloc(sizeof(ScrollbackLine *) * rv->sb_size);
- vterm_state_set_bold_highbright(state, true);
-
// Configure the color palette. Try to get the color from:
//
// - b:terminal_color_{NUM}
@@ -243,6 +243,7 @@ Terminal *terminal_open(TerminalOptions opts)
(uint8_t)((color_val >> 8) & 0xFF),
(uint8_t)((color_val >> 0) & 0xFF));
vterm_state_set_palette_color(state, i, &color);
+ rv->color_set[i] = true;
}
}
}
@@ -342,12 +343,16 @@ void terminal_enter(void)
RedrawingDisabled = false;
// Disable these options in terminal-mode. They are nonsense because cursor is
- // placed at end of buffer to "follow" output.
+ // placed at end of buffer to "follow" output. #11072
win_T *save_curwin = curwin;
int save_w_p_cul = curwin->w_p_cul;
int save_w_p_cuc = curwin->w_p_cuc;
+ long save_w_p_so = curwin->w_p_so;
+ long save_w_p_siso = curwin->w_p_siso;
curwin->w_p_cul = false;
curwin->w_p_cuc = false;
+ curwin->w_p_so = 0;
+ curwin->w_p_siso = 0;
adjust_topline(s->term, buf, 0); // scroll to end
// erase the unfocused cursor
@@ -369,6 +374,8 @@ void terminal_enter(void)
if (save_curwin == curwin) { // save_curwin may be invalid (window closed)!
curwin->w_p_cul = save_w_p_cul;
curwin->w_p_cuc = save_w_p_cuc;
+ curwin->w_p_so = save_w_p_so;
+ curwin->w_p_siso = save_w_p_siso;
}
// draw the unfocused cursor
@@ -482,7 +489,17 @@ static int terminal_execute(VimState *state, int key)
terminal_send_key(s->term, key);
}
- return curbuf->handle == s->term->buf_handle;
+ if (curbuf->terminal == NULL) {
+ return 0;
+ }
+ if (s->term != curbuf->terminal) {
+ invalidate_terminal(s->term, s->term->cursor.row, s->term->cursor.row + 1);
+ invalidate_terminal(curbuf->terminal,
+ curbuf->terminal->cursor.row,
+ curbuf->terminal->cursor.row + 1);
+ s->term = curbuf->terminal;
+ }
+ return 1;
}
void terminal_destroy(Terminal *term)
@@ -590,6 +607,7 @@ void terminal_get_line_attributes(Terminal *term, win_T *wp, int linenr,
return;
}
+ width = MIN(TERM_ATTRS_MAX, width);
for (int col = 0; col < width; col++) {
VTermScreenCell cell;
bool color_valid = fetch_cell(term, row, col, &cell);
@@ -600,16 +618,22 @@ void terminal_get_line_attributes(Terminal *term, win_T *wp, int linenr,
int vt_fg = fg_default ? -1 : get_rgb(state, cell.fg);
int vt_bg = bg_default ? -1 : get_rgb(state, cell.bg);
- int vt_fg_idx = ((!fg_default && VTERM_COLOR_IS_INDEXED(&cell.fg))
- ? cell.fg.indexed.idx + 1 : 0);
- int vt_bg_idx = ((!bg_default && VTERM_COLOR_IS_INDEXED(&cell.bg))
- ? cell.bg.indexed.idx + 1 : 0);
+ bool fg_indexed = VTERM_COLOR_IS_INDEXED(&cell.fg);
+ bool bg_indexed = VTERM_COLOR_IS_INDEXED(&cell.bg);
+
+ int vt_fg_idx = ((!fg_default && fg_indexed) ? cell.fg.indexed.idx + 1 : 0);
+ int vt_bg_idx = ((!bg_default && bg_indexed) ? cell.bg.indexed.idx + 1 : 0);
+
+ bool fg_set = vt_fg_idx && vt_fg_idx <= 16 && term->color_set[vt_fg_idx-1];
+ bool bg_set = vt_bg_idx && vt_bg_idx <= 16 && term->color_set[vt_bg_idx-1];
int hl_attrs = (cell.attrs.bold ? HL_BOLD : 0)
| (cell.attrs.italic ? HL_ITALIC : 0)
| (cell.attrs.reverse ? HL_INVERSE : 0)
| (cell.attrs.underline ? HL_UNDERLINE : 0)
- | (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);
int attr_id = 0;
@@ -643,6 +667,11 @@ Buffer terminal_buf(const Terminal *term)
return term->buf_handle;
}
+bool terminal_running(const Terminal *term)
+{
+ return !term->closed;
+}
+
// }}}
// libvterm callbacks {{{
@@ -985,8 +1014,9 @@ static void mouse_action(Terminal *term, int button, int row, int col,
static bool send_mouse_event(Terminal *term, int c)
{
int row = mouse_row, col = mouse_col, grid = mouse_grid;
+ int offset;
win_T *mouse_win = mouse_find_win(&grid, &row, &col);
- if (mouse_win == NULL) {
+ if (mouse_win == NULL || (offset = win_col_off(mouse_win)) > col) {
goto end;
}
@@ -1008,7 +1038,7 @@ static bool send_mouse_event(Terminal *term, int c)
default: return false;
}
- mouse_action(term, button, row, col, drag, 0);
+ mouse_action(term, button, row, col - offset, drag, 0);
size_t len = vterm_output_read(term->vt, term->textbuf,
sizeof(term->textbuf));
terminal_send(term, term->textbuf, (size_t)len);
diff --git a/src/nvim/testdir/Makefile b/src/nvim/testdir/Makefile
index d1a449c7cc..e52fd888bd 100644
--- a/src/nvim/testdir/Makefile
+++ b/src/nvim/testdir/Makefile
@@ -11,12 +11,10 @@ ROOT := ../../..
export SHELL := sh
export NVIM_PRG := $(NVIM_PRG)
-export TMPDIR := $(abspath ../../../Xtest-tmpdir)
+export TMPDIR := $(abspath Xtest-tmpdir)
SCRIPTS_DEFAULT = \
test42.out \
- test48.out \
- test64.out \
ifneq ($(OS),Windows_NT)
SCRIPTS_DEFAULTS := $(SCRIPTS_DEFAULT) \
@@ -34,21 +32,23 @@ SCRIPTS ?= $(SCRIPTS_DEFAULT)
# Tests using runtest.vim.
NEW_TESTS_ALOT := test_alot_utf8 test_alot
-NEW_TESTS_IN_ALOT := $(shell sed '/^source/ s/^source //;s/\.vim$$//' test_alot*.vim)
+NEW_TESTS_IN_ALOT := $(shell sed -n '/^source/ s/^source //; s/\.vim$$//p' $(addsuffix .vim,$(NEW_TESTS_ALOT)))
+NEW_TESTS_IN_ALOT_LATIN := $(shell sed -n '/^source/ s/^source //; s/\.vim$$//p' test_alot_latin.vim)
# Ignored tests.
# test_alot_latin: Nvim does not allow setting encoding.
# test_autochdir: ported to Lua, but kept for easier merging.
# test_eval_func: used as include in old-style test (test_eval.in).
# test_listlbr: Nvim does not allow setting encoding.
# test_largefile: uses too much resources to run on CI.
-NEW_TESTS_IGNORE := $(NEW_TESTS_IN_ALOT) $(NEW_TESTS_ALOT) \
- test_alot_latin \
+NEW_TESTS_IGNORE := \
+ test_alot_latin $(NEW_TESTS_IN_ALOT_LATIN) \
test_autochdir \
test_eval_func \
test_listlbr \
test_largefile \
-NEW_TESTS ?= $(addsuffix .res,$(sort $(filter-out $(NEW_TESTS_IGNORE),$(basename $(notdir $(wildcard test_*.vim))))) $(NEW_TESTS_ALOT))
+NEW_TESTS := $(sort $(basename $(notdir $(wildcard test_*.vim))))
+NEW_TESTS_RES := $(addsuffix .res,$(filter-out $(NEW_TESTS_ALOT) $(NEW_TESTS_IN_ALOT) $(NEW_TESTS_IGNORE),$(NEW_TESTS)) $(NEW_TESTS_ALOT))
ifdef VALGRIND_GDB
@@ -86,7 +86,7 @@ nongui: nolog $(FIXFF) $(SCRIPTS) newtests report
@echo 'set $$_exitcode = -1\nrun\nif $$_exitcode != -1\n quit\nend' > .gdbinit
report:
- $(RUN_VIMTEST) $(NO_INITS) -u NONE -S summarize.vim messages
+ $(NVIM_PRG) -u NONE $(NO_INITS) -S summarize.vim messages
@echo
@echo 'Test results:'
@cat test_result.log
@@ -112,6 +112,16 @@ fixff:
-$(NVIM_PRG) $(NO_INITS) -u unix.vim "+argdo set ff=dos|upd" +q \
dotest.in
+# Execute an individual new style test, e.g.:
+# make test_largefile
+$(NEW_TESTS):
+ rm -f $@.res test.log messages
+ @MAKEFLAGS=--no-print-directory $(MAKE) -f Makefile $@.res
+ @cat messages
+ @if test -f test.log; then \
+ exit 1; \
+ fi
+
RM_ON_RUN := test.out X* viminfo
RM_ON_START := test.ok
RUN_VIM := $(TOOL) $(NVIM_PRG) -u unix.vim -U NONE -i viminfo --headless --noplugin -s dotest.in
@@ -172,7 +182,7 @@ newtests: newtestssilent
cat messages && cat test.log; \
fi"
-newtestssilent: $(NEW_TESTS)
+newtestssilent: $(NEW_TESTS_RES)
%.res: %.vim .gdbinit
@echo "[OLDTEST] Running" $*
diff --git a/src/nvim/testdir/check.vim b/src/nvim/testdir/check.vim
new file mode 100644
index 0000000000..57a8eb57b8
--- /dev/null
+++ b/src/nvim/testdir/check.vim
@@ -0,0 +1,11 @@
+source shared.vim
+source term_util.vim
+
+" Command to check that making screendumps is supported.
+" Caller must source screendump.vim
+command CheckScreendump call CheckScreendump()
+func CheckScreendump()
+ if !CanRunVimInTerminal()
+ throw 'Skipped: cannot make screendumps'
+ endif
+endfunc
diff --git a/src/nvim/testdir/runnvim.sh b/src/nvim/testdir/runnvim.sh
index 2dcd9150be..72f9254635 100755
--- a/src/nvim/testdir/runnvim.sh
+++ b/src/nvim/testdir/runnvim.sh
@@ -82,6 +82,11 @@ main() {(
fi
if test "$FAILED" = 1 ; then
echo "Test $test_name failed, see output above and summary for more details" >> test.log
+ # When Neovim crashed/aborted it might not have created messages.
+ # test.log itself is used as an indicator to exit non-zero in the Makefile.
+ if ! test -f message; then
+ cp -a test.log messages
+ fi
fi
)}
diff --git a/src/nvim/testdir/runtest.vim b/src/nvim/testdir/runtest.vim
index 593ce6fcdc..2bf61b0719 100644
--- a/src/nvim/testdir/runtest.vim
+++ b/src/nvim/testdir/runtest.vim
@@ -44,6 +44,10 @@ if &lines < 24 || &columns < 80
qa!
endif
+if has('reltime')
+ let s:start_time = reltime()
+endif
+
" Common with all tests on all systems.
source setup.vim
@@ -80,6 +84,11 @@ let &runtimepath .= ','.expand($BUILD_DIR).'/runtime/'
" Always use forward slashes.
set shellslash
+if has('win32')
+ " avoid prompt that is long or contains a line break
+ let $PROMPT = '$P$G'
+endif
+
" Prepare for calling test_garbagecollect_now().
let v:testing = 1
@@ -98,13 +107,11 @@ func GetAllocId(name)
return lnum - top - 1
endfunc
-func CanRunVimInTerminal()
- " Nvim: always false, we use Lua screen-tests instead.
- return 0
-endfunc
-
func RunTheTest(test)
echo 'Executing ' . a:test
+ if has('reltime')
+ let func_start = reltime()
+ endif
" Avoid stopping at the "hit enter" prompt
set nomore
@@ -129,7 +136,11 @@ func RunTheTest(test)
endtry
endif
- call add(s:messages, 'Executing ' . a:test)
+ let message = 'Executed ' . a:test
+ if has('reltime')
+ let message ..= ' in ' .. reltimestr(reltime(func_start)) .. ' seconds'
+ endif
+ call add(s:messages, message)
let s:done += 1
if a:test =~ 'Test_nocatch_'
@@ -235,6 +246,9 @@ func FinishTesting()
else
let message = 'Executed ' . s:done . (s:done > 1 ? ' tests' : ' test')
endif
+ if has('reltime')
+ let message ..= ' in ' .. reltimestr(reltime(s:start_time)) .. ' seconds'
+ endif
echo message
call add(s:messages, message)
if s:fail > 0
@@ -282,18 +296,23 @@ 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_oneshot()',
\ 'Test_out_cb()',
\ 'Test_paused()',
+ \ 'Test_popup_and_window_resize()',
\ 'Test_quoteplus()',
\ 'Test_quotestar()',
\ 'Test_reltime()',
\ 'Test_repeat_many()',
\ 'Test_repeat_three()',
+ \ 'Test_state()',
\ 'Test_stop_all_in_callback()',
+ \ '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()',
@@ -314,6 +333,12 @@ if argc() > 1
let s:tests = filter(s:tests, 'v:val =~ argv(1)')
endif
+" If the environment variable $TEST_FILTER is set then filter the function
+" names against it.
+if $TEST_FILTER != ''
+ let s:tests = filter(s:tests, 'v:val =~ $TEST_FILTER')
+endif
+
" Execute the tests in alphabetical order.
for s:test in sort(s:tests)
" Silence, please!
diff --git a/src/nvim/testdir/screendump.vim b/src/nvim/testdir/screendump.vim
index e69de29bb2..8afff1da91 100644
--- a/src/nvim/testdir/screendump.vim
+++ b/src/nvim/testdir/screendump.vim
@@ -0,0 +1,2 @@
+source shared.vim
+source term_util.vim
diff --git a/src/nvim/testdir/setup.vim b/src/nvim/testdir/setup.vim
index ea28f328ae..d032c9a739 100644
--- a/src/nvim/testdir/setup.vim
+++ b/src/nvim/testdir/setup.vim
@@ -19,6 +19,7 @@ set sidescroll=0
set tags=./tags,tags
set undodir^=.
set wildoptions=
+set startofline
" Prevent Nvim log from writing to stderr.
let $NVIM_LOG_FILE = exists($NVIM_LOG_FILE) ? $NVIM_LOG_FILE : 'Xnvim.log'
diff --git a/src/nvim/testdir/shared.vim b/src/nvim/testdir/shared.vim
index df512e2e3f..41ff9b2bd6 100644
--- a/src/nvim/testdir/shared.vim
+++ b/src/nvim/testdir/shared.vim
@@ -1,10 +1,12 @@
" Functions shared by several tests.
" Only load this script once.
-if exists('*WaitFor')
+if exists('*PythonProg')
finish
endif
+source view_util.vim
+
" {Nvim}
" Filepath captured from output may be truncated, like this:
" /home/va...estdir/Xtest-tmpdir/nvimxbXN4i/10
@@ -69,7 +71,8 @@ endfunc
" Read the port number from the Xportnr file.
func GetPort()
let l = []
- for i in range(200)
+ " with 200 it sometimes failed
+ for i in range(400)
try
let l = readfile("Xportnr")
catch
@@ -135,39 +138,80 @@ endfunc
" Wait for up to five seconds for "expr" to become true. "expr" can be a
" stringified expression to evaluate, or a funcref without arguments.
+" Using a lambda works best. Example:
+" call WaitFor({-> status == "ok"})
"
" A second argument can be used to specify a different timeout in msec.
"
-" Return time slept in milliseconds. With the +reltime feature this can be
-" more than the actual waiting time. Without +reltime it can also be less.
+" When successful the time slept is returned.
+" When running into the timeout an exception is thrown, thus the function does
+" not return.
func WaitFor(expr, ...)
let timeout = get(a:000, 0, 5000)
+ let slept = s:WaitForCommon(a:expr, v:null, timeout)
+ if slept < 0
+ throw 'WaitFor() timed out after ' . timeout . ' msec'
+ endif
+ return slept
+endfunc
+
+" Wait for up to five seconds for "assert" to return zero. "assert" must be a
+" (lambda) function containing one assert function. Example:
+" call WaitForAssert({-> assert_equal("dead", job_status(job)})
+"
+" A second argument can be used to specify a different timeout in msec.
+"
+" Return zero for success, one for failure (like the assert function).
+func WaitForAssert(assert, ...)
+ let timeout = get(a:000, 0, 5000)
+ if s:WaitForCommon(v:null, a:assert, timeout) < 0
+ return 1
+ endif
+ return 0
+endfunc
+
+" Common implementation of WaitFor() and WaitForAssert().
+" Either "expr" or "assert" is not v:null
+" Return the waiting time for success, -1 for failure.
+func s:WaitForCommon(expr, assert, timeout)
" using reltime() is more accurate, but not always available
+ let slept = 0
if has('reltime')
let start = reltime()
- else
- let slept = 0
endif
- if type(a:expr) == v:t_func
- let Test = a:expr
- else
- let Test = {-> eval(a:expr) }
- endif
- for i in range(timeout / 10)
- if Test()
- if has('reltime')
- return float2nr(reltimefloat(reltime(start)) * 1000)
- endif
+
+ while 1
+ if type(a:expr) == v:t_func
+ let success = a:expr()
+ elseif type(a:assert) == v:t_func
+ let success = a:assert() == 0
+ else
+ let success = eval(a:expr)
+ endif
+ if success
return slept
endif
- if !has('reltime')
- let slept += 10
+
+ if slept >= a:timeout
+ break
endif
+ if type(a:assert) == v:t_func
+ " Remove the error added by the assert function.
+ call remove(v:errors, -1)
+ endif
+
sleep 10m
- endfor
- throw 'WaitFor() timed out after ' . timeout . ' msec'
+ if has('reltime')
+ let slept = float2nr(reltimefloat(reltime(start)) * 1000)
+ else
+ let slept += 10
+ endif
+ endwhile
+
+ return -1 " timed out
endfunc
+
" Wait for up to a given milliseconds.
" With the +timers feature this waits for key-input by getchar(), Resume()
" feeds key-input and resumes process. Return time waited in milliseconds.
@@ -211,6 +255,8 @@ func GetVimProg()
endif
endfunc
+let g:valgrind_cnt = 1
+
" Get the command to run Vim, with -u NONE and --headless arguments.
" If there is an argument use it instead of "NONE".
func GetVimCommand(...)
@@ -226,14 +272,25 @@ func GetVimCommand(...)
endif
let cmd .= ' --headless -i NONE'
let cmd = substitute(cmd, 'VIMRUNTIME=.*VIMRUNTIME;', '', '')
+
+ " If using valgrind, make sure every run uses a different log file.
+ if cmd =~ 'valgrind.*--log-file='
+ let cmd = substitute(cmd, '--log-file=\(\S*\)', '--log-file=\1.' . g:valgrind_cnt, '')
+ let g:valgrind_cnt += 1
+ endif
+
return cmd
endfunc
-" Get the command to run Vim, with --clean.
+" Get the command to run Vim, with --clean instead of "-u NONE".
func GetVimCommandClean()
let cmd = GetVimCommand()
let cmd = substitute(cmd, '-u NONE', '--clean', '')
let cmd = substitute(cmd, '--headless', '', '')
+
+ " Optionally run Vim under valgrind
+ " let cmd = 'valgrind --tool=memcheck --leak-check=yes --num-callers=25 --log-file=valgrind ' . cmd
+
return cmd
endfunc
@@ -249,9 +306,6 @@ endfunc
func RunVimPiped(before, after, arguments, pipecmd)
let $NVIM_LOG_FILE = exists($NVIM_LOG_FILE) ? $NVIM_LOG_FILE : 'Xnvim.log'
let cmd = GetVimCommand()
- if cmd == ''
- return 0
- endif
let args = ''
if len(a:before) > 0
call writefile(a:before, 'Xbefore.vim')
@@ -262,6 +316,9 @@ func RunVimPiped(before, after, arguments, pipecmd)
let args .= ' -S Xafter.vim'
endif
+ " Optionally run Vim under valgrind
+ " let cmd = 'valgrind --tool=memcheck --leak-check=yes --num-callers=25 --log-file=valgrind ' . cmd
+
exe "silent !" . a:pipecmd . cmd . args . ' ' . a:arguments
if len(a:before) > 0
@@ -273,17 +330,6 @@ func RunVimPiped(before, after, arguments, pipecmd)
return 1
endfunc
-" Get line "lnum" as displayed on the screen.
-" Trailing white space is trimmed.
-func! Screenline(lnum)
- let chars = []
- for c in range(1, winwidth(0))
- call add(chars, nr2char(screenchar(a:lnum, c)))
- endfor
- let line = join(chars, '')
- return matchstr(line, '^.\{-}\ze\s*$')
-endfunc
-
func CanRunGui()
return has('gui') && ($DISPLAY != "" || has('gui_running'))
endfunc
diff --git a/src/nvim/testdir/summarize.vim b/src/nvim/testdir/summarize.vim
index 5bbf0b338a..7f8f758a71 100644
--- a/src/nvim/testdir/summarize.vim
+++ b/src/nvim/testdir/summarize.vim
@@ -1,6 +1,7 @@
if 1
" This is executed only with the eval feature
set nocompatible
+ set viminfo=
func Count(match, type)
if a:type ==# 'executed'
let g:executed += (a:match+0)
@@ -8,7 +9,7 @@ if 1
let g:failed += a:match+0
elseif a:type ==# 'skipped'
let g:skipped += 1
- call extend(g:skipped_output, ["\t".a:match])
+ call extend(g:skipped_output, ["\t" .. a:match])
endif
endfunc
@@ -19,11 +20,15 @@ if 1
let g:failed_output = []
let output = [""]
+ if $TEST_FILTER != ''
+ call extend(g:skipped_output, ["\tAll tests not matching $TEST_FILTER: '" .. $TEST_FILTER .. "'"])
+ endif
+
try
" This uses the :s command to just fetch and process the output of the
" tests, it doesn't actually replace anything.
" And it uses "silent" to avoid reporting the number of matches.
- silent %s/^Executed\s\+\zs\d\+\ze\s\+tests\?/\=Count(submatch(0),'executed')/egn
+ silent %s/Executed\s\+\zs\d\+\ze\s\+tests\?/\=Count(submatch(0),'executed')/egn
silent %s/^SKIPPED \zs.*/\=Count(submatch(0), 'skipped')/egn
silent %s/^\(\d\+\)\s\+FAILED:/\=Count(submatch(1), 'failed')/egn
diff --git a/src/nvim/testdir/term_util.vim b/src/nvim/testdir/term_util.vim
new file mode 100644
index 0000000000..3a838a3a1f
--- /dev/null
+++ b/src/nvim/testdir/term_util.vim
@@ -0,0 +1,11 @@
+" Functions about terminal shared by several tests
+
+" Only load this script once.
+if exists('*CanRunVimInTerminal')
+ finish
+endif
+
+func CanRunVimInTerminal()
+ " Nvim: always false, we use Lua screen-tests instead.
+ return 0
+endfunc
diff --git a/src/nvim/testdir/test42.in b/src/nvim/testdir/test42.in
index baa6e67d26..d9057e72fb 100644
--- a/src/nvim/testdir/test42.in
+++ b/src/nvim/testdir/test42.in
Binary files differ
diff --git a/src/nvim/testdir/test48.in b/src/nvim/testdir/test48.in
deleted file mode 100644
index 1df5a3c46a..0000000000
--- a/src/nvim/testdir/test48.in
+++ /dev/null
@@ -1,82 +0,0 @@
-This is a test of 'virtualedit'.
-
-STARTTEST
-:set noswf
-:set ve=all
-j-dgg
-:"
-:" Insert "keyword keyw", ESC, C CTRL-N, shows "keyword ykeyword".
-:" Repeating CTRL-N fixes it. (Mary Ellen Foster)
-2/w
-C
-:"
-:" Using "C" then then <CR> moves the last remaining character to the next
-:" line. (Mary Ellen Foster)
-j^/are
-C are belong to vim
-:"
-:" When past the end of a line that ends in a single character "b" skips
-:" that word.
-^$15lbC7
-:"
-:" Make sure 'i' works
-$4li<-- should be 3 ' '
-:"
-:" Make sure 'C' works
-$4lC<-- should be 3 ' '
-:"
-:" Make sure 'a' works
-$4la<-- should be 4 ' '
-:"
-:" Make sure 'A' works
-$4lA<-- should be 0 ' '
-:"
-:" Make sure 'D' works
-$4lDi<-- 'D' should be intact
-:"
-:" Test for yank bug reported by Mark Waggoner.
-:set ve=block
-^2w3jyGp
-:"
-:" Test "r" beyond the end of the line
-:set ve=all
-/^"r"
-$5lrxa<-- should be 'x'
-:"
-:" Test "r" on a tab
-:" Note that for this test, 'ts' must be 8 (the default).
-^5lrxA<-- should be ' x '
-:"
-:" Test to make sure 'x' can delete control characters
-:set display=uhex
-^xxxxxxi[This line should contain only the text between the brackets.]
-:set display=
-:"
-:" Test for ^Y/^E due to bad w_virtcol value, reported by
-:" Roy <royl@netropolis.net>.
-^O3li4li4li <-- should show the name of a noted text editor
-^o4li4li4li <-- and its version number-dd
-:"
-:" Test for yanking and pasting using the small delete register
-gg/^foo
-dewve"-p
-:wq! test.out
-ENDTEST
-foo, bar
-keyword keyw
-all your base are belong to us
-1 2 3 4 5 6
-'i'
-'C'
-'a'
-'A'
-'D'
-this is a test
-this is a test
-this is a test
-"r"
-"r"
-ab sd
-abcv6efi.him0kl
-
-
diff --git a/src/nvim/testdir/test48.ok b/src/nvim/testdir/test48.ok
deleted file mode 100644
index 14cd9b12ec..0000000000
--- a/src/nvim/testdir/test48.ok
+++ /dev/null
@@ -1,23 +0,0 @@
-, foo
-keyword keyword
-all your base
-are belong to vim
-1 2 3 4 5 7
-'i' <-- should be 3 ' '
-'C' <-- should be 3 ' '
-'a' <-- should be 4 ' '
-'A'<-- should be 0 ' '
-'D' <-- 'D' should be intact
-this is a test
-this is a test
-this is a test
-"r" x<-- should be 'x'
-"r" x <-- should be ' x '
-[This line should contain only the text between the brackets.]
- v i m <-- should show the name of a noted text editor
- 6 . 0 <-- and its version number
-
-a
-a
-a
-
diff --git a/src/nvim/testdir/test49.vim b/src/nvim/testdir/test49.vim
index 837e55ebca..c86fdf25ab 100644
--- a/src/nvim/testdir/test49.vim
+++ b/src/nvim/testdir/test49.vim
@@ -1,6 +1,6 @@
" Vim script language tests
" Author: Servatius Brandt <Servatius.Brandt@fujitsu-siemens.com>
-" Last Change: 2019 May 24
+" Last Change: 2019 Oct 08
"-------------------------------------------------------------------------------
" Test environment {{{1
@@ -178,7 +178,7 @@ endif
" next Xpath value. No new Xnext value is prepared. The argument
" should be 2^(n-1) for the nth Xloop command inside the loop.
" If the loop has only one Xloop command, the argument can be
-" ommitted (default: 1).
+" omitted (default: 1).
"
" - Use XloopNEXT before ":continue" and ":endwhile". This computes a new
" Xnext value for the next execution of the loop by multiplying the old
@@ -456,7 +456,7 @@ function! ExtraVim(...)
" messing up the user's viminfo file.
let redirect = a:0 ?
\ " -c 'au VimLeave * redir END' -c 'redir\\! >" . a:1 . "'" : ""
- exec "!echo '" . debug_quits . "q' | $NVIM_PRG -u NONE -N -es" . redirect .
+ exec "!echo '" . debug_quits . "q' | " .. v:progpath .. " -u NONE -N -es" . redirect .
\ " -c 'debuggreedy|set viminfo+=nviminfo'" .
\ " -c 'let ExtraVimBegin = " . extra_begin . "'" .
\ " -c 'let ExtraVimResult = \"" . resultfile . "\"'" . breakpoints .
diff --git a/src/nvim/testdir/test64.in b/src/nvim/testdir/test64.in
deleted file mode 100644
index ec11e15e35..0000000000
--- a/src/nvim/testdir/test64.in
+++ /dev/null
@@ -1,654 +0,0 @@
-Test for regexp patterns without multi-byte support.
-See test95 for multi-byte tests.
-
-A pattern that gives the expected result produces OK, so that we know it was
-actually tried.
-
-STARTTEST
-:" tl is a List of Lists with:
-:" regexp engine
-:" regexp pattern
-:" text to test the pattern on
-:" expected match (optional)
-:" expected submatch 1 (optional)
-:" expected submatch 2 (optional)
-:" etc.
-:" When there is no match use only the first two items.
-:let tl = []
-:"
-:""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
-:"""" Previously written tests """"""""""""""""""""""""""""""""
-:""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
-:"
-:set noautoindent
-:call add(tl, [2, 'ab', 'aab', 'ab'])
-:call add(tl, [2, 'b', 'abcdef', 'b'])
-:call add(tl, [2, 'bc*', 'abccccdef', 'bcccc'])
-:call add(tl, [2, 'bc\{-}', 'abccccdef', 'b'])
-:call add(tl, [2, 'bc\{-}\(d\)', 'abccccdef', 'bccccd', 'd'])
-:call add(tl, [2, 'bc*', 'abbdef', 'b'])
-:call add(tl, [2, 'c*', 'ccc', 'ccc'])
-:call add(tl, [2, 'bc*', 'abdef', 'b'])
-:call add(tl, [2, 'c*', 'abdef', ''])
-:call add(tl, [2, 'bc\+', 'abccccdef', 'bcccc'])
-:call add(tl, [2, 'bc\+', 'abdef']) "no match
-:"
-:"operator \|
-:call add(tl, [2, 'a\|ab', 'cabd', 'a']) "alternation is ordered
-:"
-:call add(tl, [2, 'c\?', 'ccb', 'c'])
-:call add(tl, [2, 'bc\?', 'abd', 'b'])
-:call add(tl, [2, 'bc\?', 'abccd', 'bc'])
-:"
-:call add(tl, [2, '\va{1}', 'ab', 'a'])
-:"
-:call add(tl, [2, '\va{2}', 'aa', 'aa'])
-:call add(tl, [2, '\va{2}', 'caad', 'aa'])
-:call add(tl, [2, '\va{2}', 'aba'])
-:call add(tl, [2, '\va{2}', 'ab'])
-:call add(tl, [2, '\va{2}', 'abaa', 'aa'])
-:call add(tl, [2, '\va{2}', 'aaa', 'aa'])
-:"
-:call add(tl, [2, '\vb{1}', 'abca', 'b'])
-:call add(tl, [2, '\vba{2}', 'abaa', 'baa'])
-:call add(tl, [2, '\vba{3}', 'aabaac'])
-:"
-:call add(tl, [2, '\v(ab){1}', 'ab', 'ab', 'ab'])
-:call add(tl, [2, '\v(ab){1}', 'dabc', 'ab', 'ab'])
-:call add(tl, [2, '\v(ab){1}', 'acb'])
-:"
-:call add(tl, [2, '\v(ab){0,2}', 'acb', "", ""])
-:call add(tl, [2, '\v(ab){0,2}', 'ab', 'ab', 'ab'])
-:call add(tl, [2, '\v(ab){1,2}', 'ab', 'ab', 'ab'])
-:call add(tl, [2, '\v(ab){1,2}', 'ababc', 'abab', 'ab'])
-:call add(tl, [2, '\v(ab){2,4}', 'ababcab', 'abab', 'ab'])
-:call add(tl, [2, '\v(ab){2,4}', 'abcababa', 'abab', 'ab'])
-:"
-:call add(tl, [2, '\v(ab){2}', 'abab', 'abab', 'ab'])
-:call add(tl, [2, '\v(ab){2}', 'cdababe', 'abab', 'ab'])
-:call add(tl, [2, '\v(ab){2}', 'abac'])
-:call add(tl, [2, '\v(ab){2}', 'abacabab', 'abab', 'ab'])
-:call add(tl, [2, '\v((ab){2}){2}', 'abababab', 'abababab', 'abab', 'ab'])
-:call add(tl, [2, '\v((ab){2}){2}', 'abacabababab', 'abababab', 'abab', 'ab'])
-:"
-:call add(tl, [2, '\v(a{1}){1}', 'a', 'a', 'a'])
-:call add(tl, [2, '\v(a{2}){1}', 'aa', 'aa', 'aa'])
-:call add(tl, [2, '\v(a{2}){1}', 'aaac', 'aa', 'aa'])
-:call add(tl, [2, '\v(a{2}){1}', 'daaac', 'aa', 'aa'])
-:call add(tl, [2, '\v(a{1}){2}', 'daaac', 'aa', 'a'])
-:call add(tl, [2, '\v(a{1}){2}', 'aaa', 'aa', 'a'])
-:call add(tl, [2, '\v(a{2})+', 'adaac', 'aa', 'aa'])
-:call add(tl, [2, '\v(a{2})+', 'aa', 'aa', 'aa'])
-:call add(tl, [2, '\v(a{2}){1}', 'aa', 'aa', 'aa'])
-:call add(tl, [2, '\v(a{1}){2}', 'aa', 'aa', 'a'])
-:call add(tl, [2, '\v(a{1}){1}', 'a', 'a', 'a'])
-:call add(tl, [2, '\v(a{2}){2}', 'aaaa', 'aaaa', 'aa'])
-:call add(tl, [2, '\v(a{2}){2}', 'aaabaaaa', 'aaaa', 'aa'])
-:"
-:call add(tl, [2, '\v(a+){2}', 'dadaac', 'aa', 'a'])
-:call add(tl, [2, '\v(a{3}){2}', 'aaaaaaa', 'aaaaaa', 'aaa'])
-:"
-:call add(tl, [2, '\v(a{1,2}){2}', 'daaac', 'aaa', 'a'])
-:call add(tl, [2, '\v(a{1,3}){2}', 'daaaac', 'aaaa', 'a'])
-:call add(tl, [2, '\v(a{1,3}){2}', 'daaaaac', 'aaaaa', 'aa'])
-:call add(tl, [2, '\v(a{1,3}){3}', 'daac'])
-:call add(tl, [2, '\v(a{1,2}){2}', 'dac'])
-:call add(tl, [2, '\v(a+)+', 'daac', 'aa', 'aa'])
-:call add(tl, [2, '\v(a+)+', 'aaa', 'aaa', 'aaa'])
-:call add(tl, [2, '\v(a+){1,2}', 'aaa', 'aaa', 'aaa'])
-:call add(tl, [2, '\v(a+)(a+)', 'aaa', 'aaa', 'aa', 'a'])
-:call add(tl, [2, '\v(a{3})+', 'daaaac', 'aaa', 'aaa'])
-:call add(tl, [2, '\v(a|b|c)+', 'aacb', 'aacb', 'b'])
-:call add(tl, [2, '\v(a|b|c){2}', 'abcb', 'ab', 'b'])
-:call add(tl, [2, '\v(abc){2}', 'abcabd', ])
-:call add(tl, [2, '\v(abc){2}', 'abdabcabc','abcabc', 'abc'])
-:"
-:call add(tl, [2, 'a*', 'cc', ''])
-:call add(tl, [2, '\v(a*)+', 'cc', ''])
-:call add(tl, [2, '\v((ab)+)+', 'ab', 'ab', 'ab', 'ab'])
-:call add(tl, [2, '\v(((ab)+)+)+', 'ab', 'ab', 'ab', 'ab', 'ab'])
-:call add(tl, [2, '\v(((ab)+)+)+', 'dababc', 'abab', 'abab', 'abab', 'ab'])
-:call add(tl, [2, '\v(a{0,2})+', 'cc', ''])
-:call add(tl, [2, '\v(a*)+', '', ''])
-:call add(tl, [2, '\v((a*)+)+', '', ''])
-:call add(tl, [2, '\v((ab)*)+', '', ''])
-:call add(tl, [2, '\va{1,3}', 'aab', 'aa'])
-:call add(tl, [2, '\va{2,3}', 'abaa', 'aa'])
-:"
-:call add(tl, [2, '\v((ab)+|c*)+', 'abcccaba', 'abcccab', '', 'ab'])
-:call add(tl, [2, '\v(a{2})|(b{3})', 'bbabbbb', 'bbb', '', 'bbb'])
-:call add(tl, [2, '\va{2}|b{2}', 'abab'])
-:call add(tl, [2, '\v(a)+|(c)+', 'bbacbaacbbb', 'a', 'a'])
-:call add(tl, [2, '\vab{2,3}c', 'aabbccccccccccccc', 'abbc'])
-:call add(tl, [2, '\vab{2,3}c', 'aabbbccccccccccccc', 'abbbc'])
-:call add(tl, [2, '\vab{2,3}cd{2,3}e', 'aabbbcddee', 'abbbcdde'])
-:call add(tl, [2, '\va(bc){2}d', 'aabcbfbc' ])
-:call add(tl, [2, '\va*a{2}', 'a', ])
-:call add(tl, [2, '\va*a{2}', 'aa', 'aa' ])
-:call add(tl, [2, '\va*a{2}', 'aaa', 'aaa' ])
-:call add(tl, [2, '\va*a{2}', 'bbbabcc', ])
-:call add(tl, [2, '\va*b*|a*c*', 'a', 'a'])
-:call add(tl, [2, '\va{1}b{1}|a{1}b{1}', ''])
-:"
-:"submatches
-:call add(tl, [2, '\v(a)', 'ab', 'a', 'a'])
-:call add(tl, [2, '\v(a)(b)', 'ab', 'ab', 'a', 'b'])
-:call add(tl, [2, '\v(ab)(b)(c)', 'abbc', 'abbc', 'ab', 'b', 'c'])
-:call add(tl, [2, '\v((a)(b))', 'ab', 'ab', 'ab', 'a', 'b'])
-:call add(tl, [2, '\v(a)|(b)', 'ab', 'a', 'a'])
-:"
-:call add(tl, [2, '\v(a*)+', 'aaaa', 'aaaa', ''])
-:call add(tl, [2, 'x', 'abcdef'])
-:"
-:""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
-:""""" Simple tests """""""""""""""""""""""""""""""""""""""""""
-:""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
-:"
-:" Search single groups
-:call add(tl, [2, 'ab', 'aab', 'ab'])
-:call add(tl, [2, 'ab', 'baced'])
-:call add(tl, [2, 'ab', ' ab ', 'ab'])
-:"
-:" Search multi-modifiers
-:call add(tl, [2, 'x*', 'xcd', 'x'])
-:call add(tl, [2, 'x*', 'xxxxxxxxxxxxxxxxsofijiojgf', 'xxxxxxxxxxxxxxxx'])
-:" empty match is good
-:call add(tl, [2, 'x*', 'abcdoij', ''])
-:" no match here
-:call add(tl, [2, 'x\+', 'abcdoin'])
-:call add(tl, [2, 'x\+', 'abcdeoijdfxxiuhfij', 'xx'])
-:call add(tl, [2, 'x\+', 'xxxxx', 'xxxxx'])
-:call add(tl, [2, 'x\+', 'abc x siufhiush xxxxxxxxx', 'x'])
-:call add(tl, [2, 'x\=', 'x sdfoij', 'x'])
-:call add(tl, [2, 'x\=', 'abc sfoij', '']) " empty match is good
-:call add(tl, [2, 'x\=', 'xxxxxxxxx c', 'x'])
-:call add(tl, [2, 'x\?', 'x sdfoij', 'x'])
-:" empty match is good
-:call add(tl, [2, 'x\?', 'abc sfoij', ''])
-:call add(tl, [2, 'x\?', 'xxxxxxxxxx c', 'x'])
-:"
-:call add(tl, [2, 'a\{0,0}', 'abcdfdoij', ''])
-:" same thing as 'a?'
-:call add(tl, [2, 'a\{0,1}', 'asiubid axxxaaa', 'a'])
-:" same thing as 'a\{0,1}'
-:call add(tl, [2, 'a\{1,0}', 'asiubid axxxaaa', 'a'])
-:call add(tl, [2, 'a\{3,6}', 'aa siofuh'])
-:call add(tl, [2, 'a\{3,6}', 'aaaaa asfoij afaa', 'aaaaa'])
-:call add(tl, [2, 'a\{3,6}', 'aaaaaaaa', 'aaaaaa'])
-:call add(tl, [2, 'a\{0}', 'asoiuj', ''])
-:call add(tl, [2, 'a\{2}', 'aaaa', 'aa'])
-:call add(tl, [2, 'a\{2}', 'iuash fiusahfliusah fiushfilushfi uhsaifuh askfj nasfvius afg aaaa sfiuhuhiushf', 'aa'])
-:call add(tl, [2, 'a\{2}', 'abcdefghijklmnopqrestuvwxyz1234567890'])
-:" same thing as 'a*'
-:call add(tl, [2, 'a\{0,}', 'oij sdigfusnf', ''])
-:call add(tl, [2, 'a\{0,}', 'aaaaa aa', 'aaaaa'])
-:call add(tl, [2, 'a\{2,}', 'sdfiougjdsafg'])
-:call add(tl, [2, 'a\{2,}', 'aaaaasfoij ', 'aaaaa'])
-:call add(tl, [2, 'a\{5,}', 'xxaaaaxxx '])
-:call add(tl, [2, 'a\{5,}', 'xxaaaaaxxx ', 'aaaaa'])
-:call add(tl, [2, 'a\{,0}', 'oidfguih iuhi hiu aaaa', ''])
-:call add(tl, [2, 'a\{,5}', 'abcd', 'a'])
-:call add(tl, [2, 'a\{,5}', 'aaaaaaaaaa', 'aaaaa'])
-:" leading star as normal char when \{} follows
-:call add(tl, [2, '^*\{4,}$', '***'])
-:call add(tl, [2, '^*\{4,}$', '****', '****'])
-:call add(tl, [2, '^*\{4,}$', '*****', '*****'])
-:" same thing as 'a*'
-:call add(tl, [2, 'a\{}', 'bbbcddiuhfcd', ''])
-:call add(tl, [2, 'a\{}', 'aaaaioudfh coisf jda', 'aaaa'])
-:"
-:call add(tl, [2, 'a\{-0,0}', 'abcdfdoij', ''])
-:" anti-greedy version of 'a?'
-:call add(tl, [2, 'a\{-0,1}', 'asiubid axxxaaa', ''])
-:call add(tl, [2, 'a\{-3,6}', 'aa siofuh'])
-:call add(tl, [2, 'a\{-3,6}', 'aaaaa asfoij afaa', 'aaa'])
-:call add(tl, [2, 'a\{-3,6}', 'aaaaaaaa', 'aaa'])
-:call add(tl, [2, 'a\{-0}', 'asoiuj', ''])
-:call add(tl, [2, 'a\{-2}', 'aaaa', 'aa'])
-:call add(tl, [2, 'a\{-2}', 'abcdefghijklmnopqrestuvwxyz1234567890'])
-:call add(tl, [2, 'a\{-0,}', 'oij sdigfusnf', ''])
-:call add(tl, [2, 'a\{-0,}', 'aaaaa aa', ''])
-:call add(tl, [2, 'a\{-2,}', 'sdfiougjdsafg'])
-:call add(tl, [2, 'a\{-2,}', 'aaaaasfoij ', 'aa'])
-:call add(tl, [2, 'a\{-,0}', 'oidfguih iuhi hiu aaaa', ''])
-:call add(tl, [2, 'a\{-,5}', 'abcd', ''])
-:call add(tl, [2, 'a\{-,5}', 'aaaaaaaaaa', ''])
-:" anti-greedy version of 'a*'
-:call add(tl, [2, 'a\{-}', 'bbbcddiuhfcd', ''])
-:call add(tl, [2, 'a\{-}', 'aaaaioudfh coisf jda', ''])
-:"
-:" Test groups of characters and submatches
-:call add(tl, [2, '\(abc\)*', 'abcabcabc', 'abcabcabc', 'abc'])
-:call add(tl, [2, '\(ab\)\+', 'abababaaaaa', 'ababab', 'ab'])
-:call add(tl, [2, '\(abaaaaa\)*cd', 'cd', 'cd', ''])
-:call add(tl, [2, '\(test1\)\? \(test2\)\?', 'test1 test3', 'test1 ', 'test1', ''])
-:call add(tl, [2, '\(test1\)\= \(test2\) \(test4443\)\=', ' test2 test4443 yupiiiiiiiiiii', ' test2 test4443', '', 'test2', 'test4443'])
-:call add(tl, [2, '\(\(sub1\) hello \(sub 2\)\)', 'asterix sub1 hello sub 2 obelix', 'sub1 hello sub 2', 'sub1 hello sub 2', 'sub1', 'sub 2'])
-:call add(tl, [2, '\(\(\(yyxxzz\)\)\)', 'abcdddsfiusfyyzzxxyyxxzz', 'yyxxzz', 'yyxxzz', 'yyxxzz', 'yyxxzz'])
-:call add(tl, [2, '\v((ab)+|c+)+', 'abcccaba', 'abcccab', 'ab', 'ab'])
-:call add(tl, [2, '\v((ab)|c*)+', 'abcccaba', 'abcccab', '', 'ab'])
-:call add(tl, [2, '\v(a(c*)+b)+', 'acbababaaa', 'acbabab', 'ab', ''])
-:call add(tl, [2, '\v(a|b*)+', 'aaaa', 'aaaa', ''])
-:call add(tl, [2, '\p*', 'aá ', 'aá '])
-:"
-:" Test greedy-ness and lazy-ness
-:call add(tl, [2, 'a\{-2,7}','aaaaaaaaaaaaa', 'aa'])
-:call add(tl, [2, 'a\{-2,7}x','aaaaaaaaax', 'aaaaaaax'])
-:call add(tl, [2, 'a\{2,7}','aaaaaaaaaaaaaaaaaaaa', 'aaaaaaa'])
-:call add(tl, [2, 'a\{2,7}x','aaaaaaaaax', 'aaaaaaax'])
-:call add(tl, [2, '\vx(.{-,8})yz(.*)','xayxayzxayzxayz','xayxayzxayzxayz','ayxa','xayzxayz'])
-:call add(tl, [2, '\vx(.*)yz(.*)','xayxayzxayzxayz','xayxayzxayzxayz', 'ayxayzxayzxa',''])
-:call add(tl, [2, '\v(a{1,2}){-2,3}','aaaaaaa','aaaa','aa'])
-:call add(tl, [2, '\v(a{-1,3})+', 'aa', 'aa', 'a'])
-:call add(tl, [2, '^\s\{-}\zs\( x\|x$\)', ' x', ' x', ' x'])
-:call add(tl, [2, '^\s\{-}\zs\(x\| x$\)', ' x', ' x', ' x'])
-:call add(tl, [2, '^\s\{-}\ze\(x\| x$\)', ' x', '', ' x'])
-:call add(tl, [2, '^\(\s\{-}\)\(x\| x$\)', ' x', ' x', '', ' x'])
-:"
-:" Test Character classes
-:call add(tl, [2, '\d\+e\d\d','test 10e23 fd','10e23'])
-:"
-:" Test collections and character range []
-:call add(tl, [2, '\v[a]', 'abcd', 'a'])
-:call add(tl, [2, 'a[bcd]', 'abcd', 'ab'])
-:call add(tl, [2, 'a[b-d]', 'acbd', 'ac'])
-:call add(tl, [2, '[a-d][e-f][x-x]d', 'cexdxx', 'cexd'])
-:call add(tl, [2, '\v[[:alpha:]]+', 'abcdefghijklmnopqrstuvwxyz6','abcdefghijklmnopqrstuvwxyz'])
-:call add(tl, [2, '[[:alpha:]\+]', '6x8','x'])
-:call add(tl, [2, '[^abc]\+','abcabcabc'])
-:call add(tl, [2, '[^abc]','defghiasijvoinasoiunbvb','d'])
-:call add(tl, [2, '[^abc]\+','ddddddda','ddddddd'])
-:call add(tl, [2, '[^a-d]\+','aaaAAAZIHFNCddd','AAAZIHFNC'])
-:call add(tl, [2, '[a-f]*','iiiiiiii',''])
-:call add(tl, [2, '[a-f]*','abcdefgh','abcdef'])
-:call add(tl, [2, '[^a-f]\+','abcdefgh','gh'])
-:call add(tl, [2, '[a-c]\{-3,6}','abcabc','abc'])
-:call add(tl, [2, '[^[:alpha:]]\+','abcccadfoij7787ysf287yrnccdu','7787'])
-:call add(tl, [2, '[-a]', '-', '-'])
-:call add(tl, [2, '[a-]', '-', '-'])
-:call add(tl, [2, '[a-f]*\c','ABCDEFGH','ABCDEF'])
-:call add(tl, [2, '[abc][xyz]\c','-af-AF-BY--','BY'])
-:" filename regexp
-:call add(tl, [2, '[-./[:alnum:]_~]\+', 'log13.file', 'log13.file'])
-:" special chars
-:call add(tl, [2, '[\]\^\-\\]\+', '\^\\\-\---^', '\^\\\-\---^'])
-:" collation elem
-:call add(tl, [2, '[[.a.]]\+', 'aa', 'aa'])
-:" middle of regexp
-:call add(tl, [2, 'abc[0-9]*ddd', 'siuhabc ii'])
-:call add(tl, [2, 'abc[0-9]*ddd', 'adf abc44482ddd oijs', 'abc44482ddd'])
-:call add(tl, [2, '\_[0-9]\+', 'asfi9888u', '9888'])
-:call add(tl, [2, '[0-9\n]\+', 'asfi9888u', '9888'])
-:call add(tl, [2, '\_[0-9]\+', "asfi\n9888u", "\n9888"])
-:call add(tl, [2, '\_f', " \na ", "\n"])
-:call add(tl, [2, '\_f\+', " \na ", "\na"])
-:call add(tl, [2, '[0-9A-Za-z-_.]\+', " @0_a.A-{ ", "0_a.A-"])
-:"
-:"""" Test start/end of line, start/end of file
-:call add(tl, [2, '^a.', "a_\nb ", "a_"])
-:call add(tl, [2, '^a.', "b a \na_"])
-:call add(tl, [2, '.a$', " a\n "])
-:call add(tl, [2, '.a$', " a b\n_a", "_a"])
-:call add(tl, [2, '\%^a.', "a a\na", "a "])
-:call add(tl, [2, '\%^a', " a \na "])
-:call add(tl, [2, '.a\%$', " a\n "])
-:call add(tl, [2, '.a\%$', " a\n_a", "_a"])
-:"
-:"""" Test recognition of character classes
-:call add(tl, [2, '[0-7]\+', 'x0123456789x', '01234567'])
-:call add(tl, [2, '[^0-7]\+', '0a;X+% 897', 'a;X+% 89'])
-:call add(tl, [2, '[0-9]\+', 'x0123456789x', '0123456789'])
-:call add(tl, [2, '[^0-9]\+', '0a;X+% 9', 'a;X+% '])
-:call add(tl, [2, '[0-9a-fA-F]\+', 'x0189abcdefg', '0189abcdef'])
-:call add(tl, [2, '[^0-9A-Fa-f]\+', '0189g;X+% ab', 'g;X+% '])
-:call add(tl, [2, '[a-z_A-Z0-9]\+', ';+aso_SfOij ', 'aso_SfOij'])
-:call add(tl, [2, '[^a-z_A-Z0-9]\+', 'aSo_;+% sfOij', ';+% '])
-:call add(tl, [2, '[a-z_A-Z]\+', '0abyz_ABYZ;', 'abyz_ABYZ'])
-:call add(tl, [2, '[^a-z_A-Z]\+', 'abAB_09;+% yzYZ', '09;+% '])
-:call add(tl, [2, '[a-z]\+', '0abcxyz1', 'abcxyz'])
-:call add(tl, [2, '[a-z]\+', 'AabxyzZ', 'abxyz'])
-:call add(tl, [2, '[^a-z]\+', 'a;X09+% x', ';X09+% '])
-:call add(tl, [2, '[^a-z]\+', 'abX0;%yz', 'X0;%'])
-:call add(tl, [2, '[a-zA-Z]\+', '0abABxzXZ9', 'abABxzXZ'])
-:call add(tl, [2, '[^a-zA-Z]\+', 'ab09_;+ XZ', '09_;+ '])
-:call add(tl, [2, '[A-Z]\+', 'aABXYZz', 'ABXYZ'])
-:call add(tl, [2, '[^A-Z]\+', 'ABx0;%YZ', 'x0;%'])
-:call add(tl, [2, '[a-z]\+\c', '0abxyzABXYZ;', 'abxyzABXYZ'])
-:call add(tl, [2, '[A-Z]\+\c', '0abABxzXZ9', 'abABxzXZ'])
-:call add(tl, [2, '\c[^a-z]\+', 'ab09_;+ XZ', '09_;+ '])
-:call add(tl, [2, '\c[^A-Z]\+', 'ab09_;+ XZ', '09_;+ '])
-:call add(tl, [2, '\C[^A-Z]\+', 'ABCOIJDEOIFNSD jsfoij sa', ' jsfoij sa'])
-:"
-:"""" Tests for \z features
-:" match ends at \ze
-:call add(tl, [2, 'xx \ze test', 'xx '])
-:call add(tl, [2, 'abc\zeend', 'oij abcend', 'abc'])
-:call add(tl, [2, 'aa\zebb\|aaxx', ' aabb ', 'aa'])
-:call add(tl, [2, 'aa\zebb\|aaxx', ' aaxx ', 'aaxx'])
-:call add(tl, [2, 'aabb\|aa\zebb', ' aabb ', 'aabb'])
-:call add(tl, [2, 'aa\zebb\|aaebb', ' aabb ', 'aa'])
-:" match starts at \zs
-:call add(tl, [2, 'abc\zsdd', 'ddabcddxyzt', 'dd'])
-:call add(tl, [2, 'aa \zsax', ' ax'])
-:call add(tl, [2, 'abc \zsmatch\ze abc', 'abc abc abc match abc abc', 'match'])
-:call add(tl, [2, '\v(a \zsif .*){2}', 'a if then a if last', 'if last', 'a if last'])
-:call add(tl, [2, '\>\zs.', 'aword. ', '.'])
-:call add(tl, [2, '\s\+\ze\[/\|\s\zs\s\+', 'is [a t', ' '])
-:"
-:"""" Tests for \@= and \& features
-:call add(tl, [2, 'abc\@=', 'abc', 'ab'])
-:call add(tl, [2, 'abc\@=cd', 'abcd', 'abcd'])
-:call add(tl, [2, 'abc\@=', 'ababc', 'ab'])
-:" will never match, no matter the input text
-:call add(tl, [2, 'abcd\@=e', 'abcd'])
-:" will never match
-:call add(tl, [2, 'abcd\@=e', 'any text in here ... '])
-:call add(tl, [2, '\v(abc)@=..', 'xabcd', 'ab', 'abc'])
-:call add(tl, [2, '\(.*John\)\@=.*Bob', 'here is John, and here is B'])
-:call add(tl, [2, '\(John.*\)\@=.*Bob', 'John is Bobs friend', 'John is Bob', 'John is Bobs friend'])
-:call add(tl, [2, '\<\S\+\())\)\@=', '$((i=i+1))', 'i=i+1', '))'])
-:call add(tl, [2, '.*John\&.*Bob', 'here is John, and here is B'])
-:call add(tl, [2, '.*John\&.*Bob', 'John is Bobs friend', 'John is Bob'])
-:call add(tl, [2, '\v(test1)@=.*yep', 'this is a test1, yep it is', 'test1, yep', 'test1'])
-:call add(tl, [2, 'foo\(bar\)\@!', 'foobar'])
-:call add(tl, [2, 'foo\(bar\)\@!', 'foo bar', 'foo'])
-:call add(tl, [2, 'if \(\(then\)\@!.\)*$', ' if then else'])
-:call add(tl, [2, 'if \(\(then\)\@!.\)*$', ' if else ', 'if else ', ' '])
-:call add(tl, [2, '\(foo\)\@!bar', 'foobar', 'bar'])
-:call add(tl, [2, '\(foo\)\@!...bar', 'foobar'])
-:call add(tl, [2, '^\%(.*bar\)\@!.*\zsfoo', ' bar foo '])
-:call add(tl, [2, '^\%(.*bar\)\@!.*\zsfoo', ' foo bar '])
-:call add(tl, [2, '^\%(.*bar\)\@!.*\zsfoo', ' foo xxx ', 'foo'])
-:call add(tl, [2, '[ ]\@!\p\%([ ]\@!\p\)*:', 'implicit mappings:', 'mappings:'])
-:call add(tl, [2, '[ ]\@!\p\([ ]\@!\p\)*:', 'implicit mappings:', 'mappings:', 's'])
-:call add(tl, [2, 'm\k\+_\@=\%(_\@!\k\)\@<=\k\+e', 'mx__xe', 'mx__xe'])
-:call add(tl, [2, '\%(\U\@<=S\k*\|S\l\)R', 'SuR', 'SuR'])
-:"
-:"""" Combining different tests and features
-:call add(tl, [2, '[[:alpha:]]\{-2,6}', '787abcdiuhsasiuhb4', 'ab'])
-:call add(tl, [2, '', 'abcd', ''])
-:call add(tl, [2, '\v(())', 'any possible text', ''])
-:call add(tl, [2, '\v%(ab(xyz)c)', ' abxyzc ', 'abxyzc', 'xyz'])
-:call add(tl, [2, '\v(test|)empty', 'tesempty', 'empty', ''])
-:call add(tl, [2, '\v(a|aa)(a|aa)', 'aaa', 'aa', 'a', 'a'])
-:"
-:"""" \%u and friends
-:call add(tl, [2, '\%d32', 'yes no', ' '])
-:call add(tl, [2, '\%o40', 'yes no', ' '])
-:call add(tl, [2, '\%x20', 'yes no', ' '])
-:call add(tl, [2, '\%u0020', 'yes no', ' '])
-:call add(tl, [2, '\%U00000020', 'yes no', ' '])
-:call add(tl, [2, '\%d0', "yes\x0ano", "\x0a"])
-:"
-:""""" \%[abc]
-:call add(tl, [2, 'foo\%[bar]', 'fobar'])
-:call add(tl, [2, 'foo\%[bar]', 'foobar', 'foobar'])
-:call add(tl, [2, 'foo\%[bar]', 'fooxx', 'foo'])
-:call add(tl, [2, 'foo\%[bar]', 'foobxx', 'foob'])
-:call add(tl, [2, 'foo\%[bar]', 'foobaxx', 'fooba'])
-:call add(tl, [2, 'foo\%[bar]', 'foobarxx', 'foobar'])
-:call add(tl, [2, 'foo\%[bar]x', 'foobxx', 'foobx'])
-:call add(tl, [2, 'foo\%[bar]x', 'foobarxx', 'foobarx'])
-:call add(tl, [2, '\%[bar]x', 'barxx', 'barx'])
-:call add(tl, [2, '\%[bar]x', 'bxx', 'bx'])
-:call add(tl, [2, '\%[bar]x', 'xxx', 'x'])
-:call add(tl, [2, 'b\%[[ao]r]', 'bar bor', 'bar'])
-:call add(tl, [2, 'b\%[[]]r]', 'b]r bor', 'b]r'])
-:call add(tl, [2, '@\%[\w\-]*', '<http://john.net/pandoc/>[@pandoc]', '@pandoc'])
-:"
-:"""" Alternatives, must use first longest match
-:call add(tl, [2, 'goo\|go', 'google', 'goo'])
-:call add(tl, [2, '\<goo\|\<go', 'google', 'goo'])
-:call add(tl, [2, '\<goo\|go', 'google', 'goo'])
-:"
-:"""" Back references
-:call add(tl, [2, '\(\i\+\) \1', ' abc abc', 'abc abc', 'abc'])
-:call add(tl, [2, '\(\i\+\) \1', 'xgoo goox', 'goo goo', 'goo'])
-:call add(tl, [2, '\(a\)\(b\)\(c\)\(dd\)\(e\)\(f\)\(g\)\(h\)\(i\)\1\2\3\4\5\6\7\8\9', 'xabcddefghiabcddefghix', 'abcddefghiabcddefghi', 'a', 'b', 'c', 'dd', 'e', 'f', 'g', 'h', 'i'])
-:call add(tl, [2, '\(\d*\)a \1b', ' a b ', 'a b', ''])
-:call add(tl, [2, '^.\(.\).\_..\1.', "aaa\naaa\nb", "aaa\naaa", 'a'])
-:call add(tl, [2, '^.*\.\(.*\)/.\+\(\1\)\@<!$', 'foo.bat/foo.com', 'foo.bat/foo.com', 'bat'])
-:call add(tl, [2, '^.*\.\(.*\)/.\+\(\1\)\@<!$', 'foo.bat/foo.bat'])
-:call add(tl, [2, '^.*\.\(.*\)/.\+\(\1\)\@<=$', 'foo.bat/foo.bat', 'foo.bat/foo.bat', 'bat', 'bat'])
-:call add(tl, [2, '\\\@<!\${\(\d\+\%(:.\{-}\)\?\\\@<!\)}', '2013-06-27${0}', '${0}', '0'])
-:call add(tl, [2, '^\(a*\)\1$', 'aaaaaaaa', 'aaaaaaaa', 'aaaa'])
-:call add(tl, [2, '^\(a\{-2,}\)\1\+$', 'aaaaaaaaa', 'aaaaaaaaa', 'aaa'])
-:"
-:"""" Look-behind with limit
-:call add(tl, [2, '<\@<=span.', 'xxspanxx<spanyyy', 'spany'])
-:call add(tl, [2, '<\@1<=span.', 'xxspanxx<spanyyy', 'spany'])
-:call add(tl, [2, '<\@2<=span.', 'xxspanxx<spanyyy', 'spany'])
-:call add(tl, [2, '\(<<\)\@<=span.', 'xxspanxxxx<spanxx<<spanyyy', 'spany', '<<'])
-:call add(tl, [2, '\(<<\)\@1<=span.', 'xxspanxxxx<spanxx<<spanyyy'])
-:call add(tl, [2, '\(<<\)\@2<=span.', 'xxspanxxxx<spanxx<<spanyyy', 'spany', '<<'])
-:call add(tl, [2, '\(foo\)\@<!bar.', 'xx foobar1 xbar2 xx', 'bar2'])
-:"
-:" look-behind match in front of a zero-width item
-:call add(tl, [2, '\v\C%(<Last Changed:\s+)@<=.*$', '" test header'])
-:call add(tl, [2, '\v\C%(<Last Changed:\s+)@<=.*$', '" Last Changed: 1970', '1970'])
-:call add(tl, [2, '\(foo\)\@<=\>', 'foobar'])
-:call add(tl, [2, '\(foo\)\@<=\>', 'barfoo', '', 'foo'])
-:call add(tl, [2, '\(foo\)\@<=.*', 'foobar', 'bar', 'foo'])
-:"
-:" complicated look-behind match
-:call add(tl, [2, '\(r\@<=\|\w\@<!\)\/', 'x = /word/;', '/'])
-:call add(tl, [2, '^[a-z]\+\ze \&\(asdf\)\@<!', 'foo bar', 'foo'])
-:"
-:""""" \@>
-:call add(tl, [2, '\(a*\)\@>a', 'aaaa'])
-:call add(tl, [2, '\(a*\)\@>b', 'aaab', 'aaab', 'aaa'])
-:call add(tl, [2, '^\(.\{-}b\)\@>.', ' abcbd', ' abc', ' ab'])
-:call add(tl, [2, '\(.\{-}\)\(\)\@>$', 'abc', 'abc', 'abc', ''])
-:" TODO: BT engine does not restore submatch after failure
-:call add(tl, [1, '\(a*\)\@>a\|a\+', 'aaaa', 'aaaa'])
-:"
-:"""" "\_" prepended negated collection matches EOL
-:call add(tl, [2, '\_[^8-9]\+', "asfi\n9888", "asfi\n"])
-:call add(tl, [2, '\_[^a]\+', "asfi\n9888", "sfi\n9888"])
-:"
-:"""" Requiring lots of states.
-:call add(tl, [2, '[0-9a-zA-Z]\{8}-\([0-9a-zA-Z]\{4}-\)\{3}[0-9a-zA-Z]\{12}', " 12345678-1234-1234-1234-123456789012 ", "12345678-1234-1234-1234-123456789012", "1234-"])
-:"
-:"""" Skip adding state twice
-:call add(tl, [2, '^\%(\%(^\s*#\s*if\>\|#\s*if\)\)\(\%>1c.*$\)\@=', "#if FOO", "#if", ' FOO'])
-:"
-:""" Test \%V atom
-:call add(tl, [2, '\%>70vGesamt', 'Jean-Michel Charlier & Victor Hubinon\Gesamtausgabe [Salleck] Buck Danny {Jean-Michel Charlier & Victor Hubinon}\Gesamtausgabe', 'Gesamt'])
-:"
-:"""" Run the tests
-:"
-:for t in tl
-: let re = t[0]
-: let pat = t[1]
-: let text = t[2]
-: let matchidx = 3
-: for engine in [0, 1, 2]
-: if engine == 2 && re == 0 || engine == 1 && re == 1
-: continue
-: endif
-: let &regexpengine = engine
-: try
-: let l = matchlist(text, pat)
-: catch
-: $put ='ERROR ' . engine . ': pat: \"' . pat . '\", text: \"' . text . '\", caused an exception: \"' . v:exception . '\"'
-: endtry
-:" check the match itself
-: if len(l) == 0 && len(t) > matchidx
-: $put ='ERROR ' . engine . ': pat: \"' . pat . '\", text: \"' . text . '\", did not match, expected: \"' . t[matchidx] . '\"'
-: elseif len(l) > 0 && len(t) == matchidx
-: $put ='ERROR ' . engine . ': pat: \"' . pat . '\", text: \"' . text . '\", match: \"' . l[0] . '\", expected no match'
-: elseif len(t) > matchidx && l[0] != t[matchidx]
-: $put ='ERROR ' . engine . ': pat: \"' . pat . '\", text: \"' . text . '\", match: \"' . l[0] . '\", expected: \"' . t[matchidx] . '\"'
-: else
-: $put ='OK ' . engine . ' - ' . pat
-: endif
-: if len(l) > 0
-:" check all the nine submatches
-: for i in range(1, 9)
-: if len(t) <= matchidx + i
-: let e = ''
-: else
-: let e = t[matchidx + i]
-: endif
-: if l[i] != e
-: $put ='ERROR ' . engine . ': pat: \"' . pat . '\", text: \"' . text . '\", submatch ' . i . ': \"' . l[i] . '\", expected: \"' . e . '\"'
-: endif
-: endfor
-: unlet i
-: endif
-: endfor
-:endfor
-:unlet t tl e l
-:"
-:"""""" multi-line tests """"""""""""""""""""
-:let tl = []
-:"
-:"""" back references
-:call add(tl, [2, '^.\(.\).\_..\1.', ['aaa', 'aaa', 'b'], ['XX', 'b']])
-:call add(tl, [2, '\v.*\/(.*)\n.*\/\1$', ['./Dir1/Dir2/zyxwvuts.txt', './Dir1/Dir2/abcdefgh.bat', '', './Dir1/Dir2/file1.txt', './OtherDir1/OtherDir2/file1.txt'], ['./Dir1/Dir2/zyxwvuts.txt', './Dir1/Dir2/abcdefgh.bat', '', 'XX']])
-:"
-:"""" line breaks
-:call add(tl, [2, '\S.*\nx', ['abc', 'def', 'ghi', 'xjk', 'lmn'], ['abc', 'def', 'XXjk', 'lmn']])
-:"
-:" Check that \_[0-9] matching EOL does not break a following \>
-:call add(tl, [2, '\<\(\(25\_[0-5]\|2\_[0-4]\_[0-9]\|\_[01]\?\_[0-9]\_[0-9]\?\)\.\)\{3\}\(25\_[0-5]\|2\_[0-4]\_[0-9]\|\_[01]\?\_[0-9]\_[0-9]\?\)\>', ['', 'localnet/192.168.0.1', ''], ['', 'localnet/XX', '']])
-:"
-:" Check a pattern with a line break and ^ and $
-:call add(tl, [2, 'a\n^b$\n^c', ['a', 'b', 'c'], ['XX']])
-:"
-:call add(tl, [2, '\(^.\+\n\)\1', [' dog', ' dog', 'asdf'], ['XXasdf']])
-:"
-:"""" Run the multi-line tests
-:"
-:$put ='multi-line tests'
-:for t in tl
-: let re = t[0]
-: let pat = t[1]
-: let before = t[2]
-: let after = t[3]
-: for engine in [0, 1, 2]
-: if engine == 2 && re == 0 || engine == 1 && re ==1
-: continue
-: endif
-: let &regexpengine = engine
-: new
-: call setline(1, before)
-: exe '%s/' . pat . '/XX/'
-: let result = getline(1, '$')
-: q!
-: if result != after
-: $put ='ERROR: pat: \"' . pat . '\", text: \"' . string(before) . '\", expected: \"' . string(after) . '\", got: \"' . string(result) . '\"'
-: else
-: $put ='OK ' . engine . ' - ' . pat
-: endif
-: endfor
-:endfor
-:unlet t tl
-:"
-:" Check that using a pattern on two lines doesn't get messed up by using
-:" matchstr() with \ze in between.
-:set re=0
-/^Substitute here
-:.+1,.+2s/""/\='"'.matchstr(getline("."), '\d\+\ze<').'"'
-/^Substitute here
-:.+1,.+2yank
-Gop:"
-:"
-:" Check a pattern with a look beind crossing a line boundary
-/^Behind:
-/\(<\_[xy]\+\)\@3<=start
-:.yank
-Gop:"
-:"
-:" Check matching Visual area
-/^Visual:
-jfxvfx:s/\%Ve/E/g
-jV:s/\%Va/A/g
-jfxfxj:s/\%Vo/O/g
-:/^Visual/+1,/^Visual/+4yank
-Gop:"
-:"
-:" Check matching marks
-/^Marks:
-jfSmsfEme:.-4,.+6s/.\%>'s.*\%<'e../here/
-jfSmsj0fEme:.-4,.+6s/.\%>'s\_.*\%<'e../again/
-:/^Marks:/+1,/^Marks:/+3yank
-Gop:"
-:"
-:" Check patterns matching cursor position.
-:func! Postest()
- new
- call setline(1, ['ffooooo', 'boboooo', 'zoooooo', 'koooooo', 'moooooo', "\t\t\tfoo", 'abababababababfoo', 'bababababababafoo', '********_', ' xxxxxxxxxxxx xxxx xxxxxx xxxxxxx x xxxxxxxxx xx xxxxxx xxxxxx xxxxx xxxxxxx xx xxxx xxxxxxxx xxxx xxxxxxxxxxx xxx xxxxxxx xxxxxxxxx xx xxxxxx xx xxxxxxx xxxxxxxxxxxxxxxx xxxxxxxxx xxx xxxxxxxx xxxxxxxxx xxxx xxx xxxx xxx xxx xxxxx xxxxxxxxxxxx xxxx xxxxxxxxx xxxxxxxxxxx xx xxxxx xxx xxxxxxxx xxxxxx xxx xxx xxxxxxxxx xxxxxxx x xxxxxxxxx xx xxxxxx xxxxxxx xxxxxxxxxxxxxxxxxx xxxxxxx xxxxxxx xxx xxx xxxxxxxx xxxxxxx xxxx xxx xxxxxx xxxxx xxxxx xx xxxxxx xxxxxxx xxx xxxxxxxxxxxx xxxx xxxxxxxxx xxxxxx xxxxxx xxxxx xxx xxxxxxx xxxxxxxxxxxxxxxx xxxxxxxxx xxxxxxxxxx xxxx xx xxxxxxxx xxx xxxxxxxxxxx xxxxx'])
- call setpos('.', [0, 1, 0, 0])
- s/\%>3c.//g
- call setpos('.', [0, 2, 4, 0])
- s/\%#.*$//g
- call setpos('.', [0, 3, 0, 0])
- s/\%<3c./_/g
- %s/\%4l\%>5c./_/g
- %s/\%6l\%>25v./_/g
- %s/\%>6l\%3c./!/g
- %s/\%>7l\%12c./?/g
- %s/\%>7l\%<9l\%>5v\%<8v./#/g
- $s/\%(|\u.*\)\@<=[^|\t]\+$//ge
- 1,$yank
- quit!
-endfunc
-Go-0-:set re=0
-:call Postest()
-:put
-o-1-:set re=1
-:call Postest()
-:put
-o-2-:set re=2
-:call Postest()
-:put
-:"
-:" start and end of buffer
-/\%^
-yeGop:"
-50%/\%^..
-yeGopA END:"
-50%/\%$
-"ayb20gg/..\%$
-"bybGo"apo"bp:"
-:"
-:" Check for detecting error
-:set regexpengine=2
-:for pat in [' \ze*', ' \zs*']
-: try
-: let l = matchlist('x x', pat)
-: $put ='E888 NOT detected for ' . pat
-: catch
-: $put ='E888 detected for ' . pat
-: endtry
-:endfor
-:"
-:""""" Write the results """""""""""""
-:/\%#=1^Results/,$wq! test.out
-ENDTEST
-
-Substitute here:
-<T="">Ta 5</Title>
-<T="">Ac 7</Title>
-
-Behind:
-asdfasd<yyy
-xxstart1
-asdfasd<yy
-xxxstart2
-asdfasd<yy
-xxstart3
-
-Visual:
-thexe the thexethe
-andaxand andaxand
-oooxofor foroxooo
-oooxofor foroxooo
-
-Marks:
-asdfSasdfsadfEasdf
-asdfSas
-dfsadfEasdf
-
-Results of test64:
diff --git a/src/nvim/testdir/test64.ok b/src/nvim/testdir/test64.ok
deleted file mode 100644
index c218f8ea17..0000000000
--- a/src/nvim/testdir/test64.ok
+++ /dev/null
@@ -1,1107 +0,0 @@
-Results of test64:
-OK 0 - ab
-OK 1 - ab
-OK 2 - ab
-OK 0 - b
-OK 1 - b
-OK 2 - b
-OK 0 - bc*
-OK 1 - bc*
-OK 2 - bc*
-OK 0 - bc\{-}
-OK 1 - bc\{-}
-OK 2 - bc\{-}
-OK 0 - bc\{-}\(d\)
-OK 1 - bc\{-}\(d\)
-OK 2 - bc\{-}\(d\)
-OK 0 - bc*
-OK 1 - bc*
-OK 2 - bc*
-OK 0 - c*
-OK 1 - c*
-OK 2 - c*
-OK 0 - bc*
-OK 1 - bc*
-OK 2 - bc*
-OK 0 - c*
-OK 1 - c*
-OK 2 - c*
-OK 0 - bc\+
-OK 1 - bc\+
-OK 2 - bc\+
-OK 0 - bc\+
-OK 1 - bc\+
-OK 2 - bc\+
-OK 0 - a\|ab
-OK 1 - a\|ab
-OK 2 - a\|ab
-OK 0 - c\?
-OK 1 - c\?
-OK 2 - c\?
-OK 0 - bc\?
-OK 1 - bc\?
-OK 2 - bc\?
-OK 0 - bc\?
-OK 1 - bc\?
-OK 2 - bc\?
-OK 0 - \va{1}
-OK 1 - \va{1}
-OK 2 - \va{1}
-OK 0 - \va{2}
-OK 1 - \va{2}
-OK 2 - \va{2}
-OK 0 - \va{2}
-OK 1 - \va{2}
-OK 2 - \va{2}
-OK 0 - \va{2}
-OK 1 - \va{2}
-OK 2 - \va{2}
-OK 0 - \va{2}
-OK 1 - \va{2}
-OK 2 - \va{2}
-OK 0 - \va{2}
-OK 1 - \va{2}
-OK 2 - \va{2}
-OK 0 - \va{2}
-OK 1 - \va{2}
-OK 2 - \va{2}
-OK 0 - \vb{1}
-OK 1 - \vb{1}
-OK 2 - \vb{1}
-OK 0 - \vba{2}
-OK 1 - \vba{2}
-OK 2 - \vba{2}
-OK 0 - \vba{3}
-OK 1 - \vba{3}
-OK 2 - \vba{3}
-OK 0 - \v(ab){1}
-OK 1 - \v(ab){1}
-OK 2 - \v(ab){1}
-OK 0 - \v(ab){1}
-OK 1 - \v(ab){1}
-OK 2 - \v(ab){1}
-OK 0 - \v(ab){1}
-OK 1 - \v(ab){1}
-OK 2 - \v(ab){1}
-OK 0 - \v(ab){0,2}
-OK 1 - \v(ab){0,2}
-OK 2 - \v(ab){0,2}
-OK 0 - \v(ab){0,2}
-OK 1 - \v(ab){0,2}
-OK 2 - \v(ab){0,2}
-OK 0 - \v(ab){1,2}
-OK 1 - \v(ab){1,2}
-OK 2 - \v(ab){1,2}
-OK 0 - \v(ab){1,2}
-OK 1 - \v(ab){1,2}
-OK 2 - \v(ab){1,2}
-OK 0 - \v(ab){2,4}
-OK 1 - \v(ab){2,4}
-OK 2 - \v(ab){2,4}
-OK 0 - \v(ab){2,4}
-OK 1 - \v(ab){2,4}
-OK 2 - \v(ab){2,4}
-OK 0 - \v(ab){2}
-OK 1 - \v(ab){2}
-OK 2 - \v(ab){2}
-OK 0 - \v(ab){2}
-OK 1 - \v(ab){2}
-OK 2 - \v(ab){2}
-OK 0 - \v(ab){2}
-OK 1 - \v(ab){2}
-OK 2 - \v(ab){2}
-OK 0 - \v(ab){2}
-OK 1 - \v(ab){2}
-OK 2 - \v(ab){2}
-OK 0 - \v((ab){2}){2}
-OK 1 - \v((ab){2}){2}
-OK 2 - \v((ab){2}){2}
-OK 0 - \v((ab){2}){2}
-OK 1 - \v((ab){2}){2}
-OK 2 - \v((ab){2}){2}
-OK 0 - \v(a{1}){1}
-OK 1 - \v(a{1}){1}
-OK 2 - \v(a{1}){1}
-OK 0 - \v(a{2}){1}
-OK 1 - \v(a{2}){1}
-OK 2 - \v(a{2}){1}
-OK 0 - \v(a{2}){1}
-OK 1 - \v(a{2}){1}
-OK 2 - \v(a{2}){1}
-OK 0 - \v(a{2}){1}
-OK 1 - \v(a{2}){1}
-OK 2 - \v(a{2}){1}
-OK 0 - \v(a{1}){2}
-OK 1 - \v(a{1}){2}
-OK 2 - \v(a{1}){2}
-OK 0 - \v(a{1}){2}
-OK 1 - \v(a{1}){2}
-OK 2 - \v(a{1}){2}
-OK 0 - \v(a{2})+
-OK 1 - \v(a{2})+
-OK 2 - \v(a{2})+
-OK 0 - \v(a{2})+
-OK 1 - \v(a{2})+
-OK 2 - \v(a{2})+
-OK 0 - \v(a{2}){1}
-OK 1 - \v(a{2}){1}
-OK 2 - \v(a{2}){1}
-OK 0 - \v(a{1}){2}
-OK 1 - \v(a{1}){2}
-OK 2 - \v(a{1}){2}
-OK 0 - \v(a{1}){1}
-OK 1 - \v(a{1}){1}
-OK 2 - \v(a{1}){1}
-OK 0 - \v(a{2}){2}
-OK 1 - \v(a{2}){2}
-OK 2 - \v(a{2}){2}
-OK 0 - \v(a{2}){2}
-OK 1 - \v(a{2}){2}
-OK 2 - \v(a{2}){2}
-OK 0 - \v(a+){2}
-OK 1 - \v(a+){2}
-OK 2 - \v(a+){2}
-OK 0 - \v(a{3}){2}
-OK 1 - \v(a{3}){2}
-OK 2 - \v(a{3}){2}
-OK 0 - \v(a{1,2}){2}
-OK 1 - \v(a{1,2}){2}
-OK 2 - \v(a{1,2}){2}
-OK 0 - \v(a{1,3}){2}
-OK 1 - \v(a{1,3}){2}
-OK 2 - \v(a{1,3}){2}
-OK 0 - \v(a{1,3}){2}
-OK 1 - \v(a{1,3}){2}
-OK 2 - \v(a{1,3}){2}
-OK 0 - \v(a{1,3}){3}
-OK 1 - \v(a{1,3}){3}
-OK 2 - \v(a{1,3}){3}
-OK 0 - \v(a{1,2}){2}
-OK 1 - \v(a{1,2}){2}
-OK 2 - \v(a{1,2}){2}
-OK 0 - \v(a+)+
-OK 1 - \v(a+)+
-OK 2 - \v(a+)+
-OK 0 - \v(a+)+
-OK 1 - \v(a+)+
-OK 2 - \v(a+)+
-OK 0 - \v(a+){1,2}
-OK 1 - \v(a+){1,2}
-OK 2 - \v(a+){1,2}
-OK 0 - \v(a+)(a+)
-OK 1 - \v(a+)(a+)
-OK 2 - \v(a+)(a+)
-OK 0 - \v(a{3})+
-OK 1 - \v(a{3})+
-OK 2 - \v(a{3})+
-OK 0 - \v(a|b|c)+
-OK 1 - \v(a|b|c)+
-OK 2 - \v(a|b|c)+
-OK 0 - \v(a|b|c){2}
-OK 1 - \v(a|b|c){2}
-OK 2 - \v(a|b|c){2}
-OK 0 - \v(abc){2}
-OK 1 - \v(abc){2}
-OK 2 - \v(abc){2}
-OK 0 - \v(abc){2}
-OK 1 - \v(abc){2}
-OK 2 - \v(abc){2}
-OK 0 - a*
-OK 1 - a*
-OK 2 - a*
-OK 0 - \v(a*)+
-OK 1 - \v(a*)+
-OK 2 - \v(a*)+
-OK 0 - \v((ab)+)+
-OK 1 - \v((ab)+)+
-OK 2 - \v((ab)+)+
-OK 0 - \v(((ab)+)+)+
-OK 1 - \v(((ab)+)+)+
-OK 2 - \v(((ab)+)+)+
-OK 0 - \v(((ab)+)+)+
-OK 1 - \v(((ab)+)+)+
-OK 2 - \v(((ab)+)+)+
-OK 0 - \v(a{0,2})+
-OK 1 - \v(a{0,2})+
-OK 2 - \v(a{0,2})+
-OK 0 - \v(a*)+
-OK 1 - \v(a*)+
-OK 2 - \v(a*)+
-OK 0 - \v((a*)+)+
-OK 1 - \v((a*)+)+
-OK 2 - \v((a*)+)+
-OK 0 - \v((ab)*)+
-OK 1 - \v((ab)*)+
-OK 2 - \v((ab)*)+
-OK 0 - \va{1,3}
-OK 1 - \va{1,3}
-OK 2 - \va{1,3}
-OK 0 - \va{2,3}
-OK 1 - \va{2,3}
-OK 2 - \va{2,3}
-OK 0 - \v((ab)+|c*)+
-OK 1 - \v((ab)+|c*)+
-OK 2 - \v((ab)+|c*)+
-OK 0 - \v(a{2})|(b{3})
-OK 1 - \v(a{2})|(b{3})
-OK 2 - \v(a{2})|(b{3})
-OK 0 - \va{2}|b{2}
-OK 1 - \va{2}|b{2}
-OK 2 - \va{2}|b{2}
-OK 0 - \v(a)+|(c)+
-OK 1 - \v(a)+|(c)+
-OK 2 - \v(a)+|(c)+
-OK 0 - \vab{2,3}c
-OK 1 - \vab{2,3}c
-OK 2 - \vab{2,3}c
-OK 0 - \vab{2,3}c
-OK 1 - \vab{2,3}c
-OK 2 - \vab{2,3}c
-OK 0 - \vab{2,3}cd{2,3}e
-OK 1 - \vab{2,3}cd{2,3}e
-OK 2 - \vab{2,3}cd{2,3}e
-OK 0 - \va(bc){2}d
-OK 1 - \va(bc){2}d
-OK 2 - \va(bc){2}d
-OK 0 - \va*a{2}
-OK 1 - \va*a{2}
-OK 2 - \va*a{2}
-OK 0 - \va*a{2}
-OK 1 - \va*a{2}
-OK 2 - \va*a{2}
-OK 0 - \va*a{2}
-OK 1 - \va*a{2}
-OK 2 - \va*a{2}
-OK 0 - \va*a{2}
-OK 1 - \va*a{2}
-OK 2 - \va*a{2}
-OK 0 - \va*b*|a*c*
-OK 1 - \va*b*|a*c*
-OK 2 - \va*b*|a*c*
-OK 0 - \va{1}b{1}|a{1}b{1}
-OK 1 - \va{1}b{1}|a{1}b{1}
-OK 2 - \va{1}b{1}|a{1}b{1}
-OK 0 - \v(a)
-OK 1 - \v(a)
-OK 2 - \v(a)
-OK 0 - \v(a)(b)
-OK 1 - \v(a)(b)
-OK 2 - \v(a)(b)
-OK 0 - \v(ab)(b)(c)
-OK 1 - \v(ab)(b)(c)
-OK 2 - \v(ab)(b)(c)
-OK 0 - \v((a)(b))
-OK 1 - \v((a)(b))
-OK 2 - \v((a)(b))
-OK 0 - \v(a)|(b)
-OK 1 - \v(a)|(b)
-OK 2 - \v(a)|(b)
-OK 0 - \v(a*)+
-OK 1 - \v(a*)+
-OK 2 - \v(a*)+
-OK 0 - x
-OK 1 - x
-OK 2 - x
-OK 0 - ab
-OK 1 - ab
-OK 2 - ab
-OK 0 - ab
-OK 1 - ab
-OK 2 - ab
-OK 0 - ab
-OK 1 - ab
-OK 2 - ab
-OK 0 - x*
-OK 1 - x*
-OK 2 - x*
-OK 0 - x*
-OK 1 - x*
-OK 2 - x*
-OK 0 - x*
-OK 1 - x*
-OK 2 - x*
-OK 0 - x\+
-OK 1 - x\+
-OK 2 - x\+
-OK 0 - x\+
-OK 1 - x\+
-OK 2 - x\+
-OK 0 - x\+
-OK 1 - x\+
-OK 2 - x\+
-OK 0 - x\+
-OK 1 - x\+
-OK 2 - x\+
-OK 0 - x\=
-OK 1 - x\=
-OK 2 - x\=
-OK 0 - x\=
-OK 1 - x\=
-OK 2 - x\=
-OK 0 - x\=
-OK 1 - x\=
-OK 2 - x\=
-OK 0 - x\?
-OK 1 - x\?
-OK 2 - x\?
-OK 0 - x\?
-OK 1 - x\?
-OK 2 - x\?
-OK 0 - x\?
-OK 1 - x\?
-OK 2 - x\?
-OK 0 - a\{0,0}
-OK 1 - a\{0,0}
-OK 2 - a\{0,0}
-OK 0 - a\{0,1}
-OK 1 - a\{0,1}
-OK 2 - a\{0,1}
-OK 0 - a\{1,0}
-OK 1 - a\{1,0}
-OK 2 - a\{1,0}
-OK 0 - a\{3,6}
-OK 1 - a\{3,6}
-OK 2 - a\{3,6}
-OK 0 - a\{3,6}
-OK 1 - a\{3,6}
-OK 2 - a\{3,6}
-OK 0 - a\{3,6}
-OK 1 - a\{3,6}
-OK 2 - a\{3,6}
-OK 0 - a\{0}
-OK 1 - a\{0}
-OK 2 - a\{0}
-OK 0 - a\{2}
-OK 1 - a\{2}
-OK 2 - a\{2}
-OK 0 - a\{2}
-OK 1 - a\{2}
-OK 2 - a\{2}
-OK 0 - a\{2}
-OK 1 - a\{2}
-OK 2 - a\{2}
-OK 0 - a\{0,}
-OK 1 - a\{0,}
-OK 2 - a\{0,}
-OK 0 - a\{0,}
-OK 1 - a\{0,}
-OK 2 - a\{0,}
-OK 0 - a\{2,}
-OK 1 - a\{2,}
-OK 2 - a\{2,}
-OK 0 - a\{2,}
-OK 1 - a\{2,}
-OK 2 - a\{2,}
-OK 0 - a\{5,}
-OK 1 - a\{5,}
-OK 2 - a\{5,}
-OK 0 - a\{5,}
-OK 1 - a\{5,}
-OK 2 - a\{5,}
-OK 0 - a\{,0}
-OK 1 - a\{,0}
-OK 2 - a\{,0}
-OK 0 - a\{,5}
-OK 1 - a\{,5}
-OK 2 - a\{,5}
-OK 0 - a\{,5}
-OK 1 - a\{,5}
-OK 2 - a\{,5}
-OK 0 - ^*\{4,}$
-OK 1 - ^*\{4,}$
-OK 2 - ^*\{4,}$
-OK 0 - ^*\{4,}$
-OK 1 - ^*\{4,}$
-OK 2 - ^*\{4,}$
-OK 0 - ^*\{4,}$
-OK 1 - ^*\{4,}$
-OK 2 - ^*\{4,}$
-OK 0 - a\{}
-OK 1 - a\{}
-OK 2 - a\{}
-OK 0 - a\{}
-OK 1 - a\{}
-OK 2 - a\{}
-OK 0 - a\{-0,0}
-OK 1 - a\{-0,0}
-OK 2 - a\{-0,0}
-OK 0 - a\{-0,1}
-OK 1 - a\{-0,1}
-OK 2 - a\{-0,1}
-OK 0 - a\{-3,6}
-OK 1 - a\{-3,6}
-OK 2 - a\{-3,6}
-OK 0 - a\{-3,6}
-OK 1 - a\{-3,6}
-OK 2 - a\{-3,6}
-OK 0 - a\{-3,6}
-OK 1 - a\{-3,6}
-OK 2 - a\{-3,6}
-OK 0 - a\{-0}
-OK 1 - a\{-0}
-OK 2 - a\{-0}
-OK 0 - a\{-2}
-OK 1 - a\{-2}
-OK 2 - a\{-2}
-OK 0 - a\{-2}
-OK 1 - a\{-2}
-OK 2 - a\{-2}
-OK 0 - a\{-0,}
-OK 1 - a\{-0,}
-OK 2 - a\{-0,}
-OK 0 - a\{-0,}
-OK 1 - a\{-0,}
-OK 2 - a\{-0,}
-OK 0 - a\{-2,}
-OK 1 - a\{-2,}
-OK 2 - a\{-2,}
-OK 0 - a\{-2,}
-OK 1 - a\{-2,}
-OK 2 - a\{-2,}
-OK 0 - a\{-,0}
-OK 1 - a\{-,0}
-OK 2 - a\{-,0}
-OK 0 - a\{-,5}
-OK 1 - a\{-,5}
-OK 2 - a\{-,5}
-OK 0 - a\{-,5}
-OK 1 - a\{-,5}
-OK 2 - a\{-,5}
-OK 0 - a\{-}
-OK 1 - a\{-}
-OK 2 - a\{-}
-OK 0 - a\{-}
-OK 1 - a\{-}
-OK 2 - a\{-}
-OK 0 - \(abc\)*
-OK 1 - \(abc\)*
-OK 2 - \(abc\)*
-OK 0 - \(ab\)\+
-OK 1 - \(ab\)\+
-OK 2 - \(ab\)\+
-OK 0 - \(abaaaaa\)*cd
-OK 1 - \(abaaaaa\)*cd
-OK 2 - \(abaaaaa\)*cd
-OK 0 - \(test1\)\? \(test2\)\?
-OK 1 - \(test1\)\? \(test2\)\?
-OK 2 - \(test1\)\? \(test2\)\?
-OK 0 - \(test1\)\= \(test2\) \(test4443\)\=
-OK 1 - \(test1\)\= \(test2\) \(test4443\)\=
-OK 2 - \(test1\)\= \(test2\) \(test4443\)\=
-OK 0 - \(\(sub1\) hello \(sub 2\)\)
-OK 1 - \(\(sub1\) hello \(sub 2\)\)
-OK 2 - \(\(sub1\) hello \(sub 2\)\)
-OK 0 - \(\(\(yyxxzz\)\)\)
-OK 1 - \(\(\(yyxxzz\)\)\)
-OK 2 - \(\(\(yyxxzz\)\)\)
-OK 0 - \v((ab)+|c+)+
-OK 1 - \v((ab)+|c+)+
-OK 2 - \v((ab)+|c+)+
-OK 0 - \v((ab)|c*)+
-OK 1 - \v((ab)|c*)+
-OK 2 - \v((ab)|c*)+
-OK 0 - \v(a(c*)+b)+
-OK 1 - \v(a(c*)+b)+
-OK 2 - \v(a(c*)+b)+
-OK 0 - \v(a|b*)+
-OK 1 - \v(a|b*)+
-OK 2 - \v(a|b*)+
-OK 0 - \p*
-OK 1 - \p*
-OK 2 - \p*
-OK 0 - a\{-2,7}
-OK 1 - a\{-2,7}
-OK 2 - a\{-2,7}
-OK 0 - a\{-2,7}x
-OK 1 - a\{-2,7}x
-OK 2 - a\{-2,7}x
-OK 0 - a\{2,7}
-OK 1 - a\{2,7}
-OK 2 - a\{2,7}
-OK 0 - a\{2,7}x
-OK 1 - a\{2,7}x
-OK 2 - a\{2,7}x
-OK 0 - \vx(.{-,8})yz(.*)
-OK 1 - \vx(.{-,8})yz(.*)
-OK 2 - \vx(.{-,8})yz(.*)
-OK 0 - \vx(.*)yz(.*)
-OK 1 - \vx(.*)yz(.*)
-OK 2 - \vx(.*)yz(.*)
-OK 0 - \v(a{1,2}){-2,3}
-OK 1 - \v(a{1,2}){-2,3}
-OK 2 - \v(a{1,2}){-2,3}
-OK 0 - \v(a{-1,3})+
-OK 1 - \v(a{-1,3})+
-OK 2 - \v(a{-1,3})+
-OK 0 - ^\s\{-}\zs\( x\|x$\)
-OK 1 - ^\s\{-}\zs\( x\|x$\)
-OK 2 - ^\s\{-}\zs\( x\|x$\)
-OK 0 - ^\s\{-}\zs\(x\| x$\)
-OK 1 - ^\s\{-}\zs\(x\| x$\)
-OK 2 - ^\s\{-}\zs\(x\| x$\)
-OK 0 - ^\s\{-}\ze\(x\| x$\)
-OK 1 - ^\s\{-}\ze\(x\| x$\)
-OK 2 - ^\s\{-}\ze\(x\| x$\)
-OK 0 - ^\(\s\{-}\)\(x\| x$\)
-OK 1 - ^\(\s\{-}\)\(x\| x$\)
-OK 2 - ^\(\s\{-}\)\(x\| x$\)
-OK 0 - \d\+e\d\d
-OK 1 - \d\+e\d\d
-OK 2 - \d\+e\d\d
-OK 0 - \v[a]
-OK 1 - \v[a]
-OK 2 - \v[a]
-OK 0 - a[bcd]
-OK 1 - a[bcd]
-OK 2 - a[bcd]
-OK 0 - a[b-d]
-OK 1 - a[b-d]
-OK 2 - a[b-d]
-OK 0 - [a-d][e-f][x-x]d
-OK 1 - [a-d][e-f][x-x]d
-OK 2 - [a-d][e-f][x-x]d
-OK 0 - \v[[:alpha:]]+
-OK 1 - \v[[:alpha:]]+
-OK 2 - \v[[:alpha:]]+
-OK 0 - [[:alpha:]\+]
-OK 1 - [[:alpha:]\+]
-OK 2 - [[:alpha:]\+]
-OK 0 - [^abc]\+
-OK 1 - [^abc]\+
-OK 2 - [^abc]\+
-OK 0 - [^abc]
-OK 1 - [^abc]
-OK 2 - [^abc]
-OK 0 - [^abc]\+
-OK 1 - [^abc]\+
-OK 2 - [^abc]\+
-OK 0 - [^a-d]\+
-OK 1 - [^a-d]\+
-OK 2 - [^a-d]\+
-OK 0 - [a-f]*
-OK 1 - [a-f]*
-OK 2 - [a-f]*
-OK 0 - [a-f]*
-OK 1 - [a-f]*
-OK 2 - [a-f]*
-OK 0 - [^a-f]\+
-OK 1 - [^a-f]\+
-OK 2 - [^a-f]\+
-OK 0 - [a-c]\{-3,6}
-OK 1 - [a-c]\{-3,6}
-OK 2 - [a-c]\{-3,6}
-OK 0 - [^[:alpha:]]\+
-OK 1 - [^[:alpha:]]\+
-OK 2 - [^[:alpha:]]\+
-OK 0 - [-a]
-OK 1 - [-a]
-OK 2 - [-a]
-OK 0 - [a-]
-OK 1 - [a-]
-OK 2 - [a-]
-OK 0 - [a-f]*\c
-OK 1 - [a-f]*\c
-OK 2 - [a-f]*\c
-OK 0 - [abc][xyz]\c
-OK 1 - [abc][xyz]\c
-OK 2 - [abc][xyz]\c
-OK 0 - [-./[:alnum:]_~]\+
-OK 1 - [-./[:alnum:]_~]\+
-OK 2 - [-./[:alnum:]_~]\+
-OK 0 - [\]\^\-\\]\+
-OK 1 - [\]\^\-\\]\+
-OK 2 - [\]\^\-\\]\+
-OK 0 - [[.a.]]\+
-OK 1 - [[.a.]]\+
-OK 2 - [[.a.]]\+
-OK 0 - abc[0-9]*ddd
-OK 1 - abc[0-9]*ddd
-OK 2 - abc[0-9]*ddd
-OK 0 - abc[0-9]*ddd
-OK 1 - abc[0-9]*ddd
-OK 2 - abc[0-9]*ddd
-OK 0 - \_[0-9]\+
-OK 1 - \_[0-9]\+
-OK 2 - \_[0-9]\+
-OK 0 - [0-9\n]\+
-OK 1 - [0-9\n]\+
-OK 2 - [0-9\n]\+
-OK 0 - \_[0-9]\+
-OK 1 - \_[0-9]\+
-OK 2 - \_[0-9]\+
-OK 0 - \_f
-OK 1 - \_f
-OK 2 - \_f
-OK 0 - \_f\+
-OK 1 - \_f\+
-OK 2 - \_f\+
-OK 0 - [0-9A-Za-z-_.]\+
-OK 1 - [0-9A-Za-z-_.]\+
-OK 2 - [0-9A-Za-z-_.]\+
-OK 0 - ^a.
-OK 1 - ^a.
-OK 2 - ^a.
-OK 0 - ^a.
-OK 1 - ^a.
-OK 2 - ^a.
-OK 0 - .a$
-OK 1 - .a$
-OK 2 - .a$
-OK 0 - .a$
-OK 1 - .a$
-OK 2 - .a$
-OK 0 - \%^a.
-OK 1 - \%^a.
-OK 2 - \%^a.
-OK 0 - \%^a
-OK 1 - \%^a
-OK 2 - \%^a
-OK 0 - .a\%$
-OK 1 - .a\%$
-OK 2 - .a\%$
-OK 0 - .a\%$
-OK 1 - .a\%$
-OK 2 - .a\%$
-OK 0 - [0-7]\+
-OK 1 - [0-7]\+
-OK 2 - [0-7]\+
-OK 0 - [^0-7]\+
-OK 1 - [^0-7]\+
-OK 2 - [^0-7]\+
-OK 0 - [0-9]\+
-OK 1 - [0-9]\+
-OK 2 - [0-9]\+
-OK 0 - [^0-9]\+
-OK 1 - [^0-9]\+
-OK 2 - [^0-9]\+
-OK 0 - [0-9a-fA-F]\+
-OK 1 - [0-9a-fA-F]\+
-OK 2 - [0-9a-fA-F]\+
-OK 0 - [^0-9A-Fa-f]\+
-OK 1 - [^0-9A-Fa-f]\+
-OK 2 - [^0-9A-Fa-f]\+
-OK 0 - [a-z_A-Z0-9]\+
-OK 1 - [a-z_A-Z0-9]\+
-OK 2 - [a-z_A-Z0-9]\+
-OK 0 - [^a-z_A-Z0-9]\+
-OK 1 - [^a-z_A-Z0-9]\+
-OK 2 - [^a-z_A-Z0-9]\+
-OK 0 - [a-z_A-Z]\+
-OK 1 - [a-z_A-Z]\+
-OK 2 - [a-z_A-Z]\+
-OK 0 - [^a-z_A-Z]\+
-OK 1 - [^a-z_A-Z]\+
-OK 2 - [^a-z_A-Z]\+
-OK 0 - [a-z]\+
-OK 1 - [a-z]\+
-OK 2 - [a-z]\+
-OK 0 - [a-z]\+
-OK 1 - [a-z]\+
-OK 2 - [a-z]\+
-OK 0 - [^a-z]\+
-OK 1 - [^a-z]\+
-OK 2 - [^a-z]\+
-OK 0 - [^a-z]\+
-OK 1 - [^a-z]\+
-OK 2 - [^a-z]\+
-OK 0 - [a-zA-Z]\+
-OK 1 - [a-zA-Z]\+
-OK 2 - [a-zA-Z]\+
-OK 0 - [^a-zA-Z]\+
-OK 1 - [^a-zA-Z]\+
-OK 2 - [^a-zA-Z]\+
-OK 0 - [A-Z]\+
-OK 1 - [A-Z]\+
-OK 2 - [A-Z]\+
-OK 0 - [^A-Z]\+
-OK 1 - [^A-Z]\+
-OK 2 - [^A-Z]\+
-OK 0 - [a-z]\+\c
-OK 1 - [a-z]\+\c
-OK 2 - [a-z]\+\c
-OK 0 - [A-Z]\+\c
-OK 1 - [A-Z]\+\c
-OK 2 - [A-Z]\+\c
-OK 0 - \c[^a-z]\+
-OK 1 - \c[^a-z]\+
-OK 2 - \c[^a-z]\+
-OK 0 - \c[^A-Z]\+
-OK 1 - \c[^A-Z]\+
-OK 2 - \c[^A-Z]\+
-OK 0 - \C[^A-Z]\+
-OK 1 - \C[^A-Z]\+
-OK 2 - \C[^A-Z]\+
-OK 0 - xx \ze test
-OK 1 - xx \ze test
-OK 2 - xx \ze test
-OK 0 - abc\zeend
-OK 1 - abc\zeend
-OK 2 - abc\zeend
-OK 0 - aa\zebb\|aaxx
-OK 1 - aa\zebb\|aaxx
-OK 2 - aa\zebb\|aaxx
-OK 0 - aa\zebb\|aaxx
-OK 1 - aa\zebb\|aaxx
-OK 2 - aa\zebb\|aaxx
-OK 0 - aabb\|aa\zebb
-OK 1 - aabb\|aa\zebb
-OK 2 - aabb\|aa\zebb
-OK 0 - aa\zebb\|aaebb
-OK 1 - aa\zebb\|aaebb
-OK 2 - aa\zebb\|aaebb
-OK 0 - abc\zsdd
-OK 1 - abc\zsdd
-OK 2 - abc\zsdd
-OK 0 - aa \zsax
-OK 1 - aa \zsax
-OK 2 - aa \zsax
-OK 0 - abc \zsmatch\ze abc
-OK 1 - abc \zsmatch\ze abc
-OK 2 - abc \zsmatch\ze abc
-OK 0 - \v(a \zsif .*){2}
-OK 1 - \v(a \zsif .*){2}
-OK 2 - \v(a \zsif .*){2}
-OK 0 - \>\zs.
-OK 1 - \>\zs.
-OK 2 - \>\zs.
-OK 0 - \s\+\ze\[/\|\s\zs\s\+
-OK 1 - \s\+\ze\[/\|\s\zs\s\+
-OK 2 - \s\+\ze\[/\|\s\zs\s\+
-OK 0 - abc\@=
-OK 1 - abc\@=
-OK 2 - abc\@=
-OK 0 - abc\@=cd
-OK 1 - abc\@=cd
-OK 2 - abc\@=cd
-OK 0 - abc\@=
-OK 1 - abc\@=
-OK 2 - abc\@=
-OK 0 - abcd\@=e
-OK 1 - abcd\@=e
-OK 2 - abcd\@=e
-OK 0 - abcd\@=e
-OK 1 - abcd\@=e
-OK 2 - abcd\@=e
-OK 0 - \v(abc)@=..
-OK 1 - \v(abc)@=..
-OK 2 - \v(abc)@=..
-OK 0 - \(.*John\)\@=.*Bob
-OK 1 - \(.*John\)\@=.*Bob
-OK 2 - \(.*John\)\@=.*Bob
-OK 0 - \(John.*\)\@=.*Bob
-OK 1 - \(John.*\)\@=.*Bob
-OK 2 - \(John.*\)\@=.*Bob
-OK 0 - \<\S\+\())\)\@=
-OK 1 - \<\S\+\())\)\@=
-OK 2 - \<\S\+\())\)\@=
-OK 0 - .*John\&.*Bob
-OK 1 - .*John\&.*Bob
-OK 2 - .*John\&.*Bob
-OK 0 - .*John\&.*Bob
-OK 1 - .*John\&.*Bob
-OK 2 - .*John\&.*Bob
-OK 0 - \v(test1)@=.*yep
-OK 1 - \v(test1)@=.*yep
-OK 2 - \v(test1)@=.*yep
-OK 0 - foo\(bar\)\@!
-OK 1 - foo\(bar\)\@!
-OK 2 - foo\(bar\)\@!
-OK 0 - foo\(bar\)\@!
-OK 1 - foo\(bar\)\@!
-OK 2 - foo\(bar\)\@!
-OK 0 - if \(\(then\)\@!.\)*$
-OK 1 - if \(\(then\)\@!.\)*$
-OK 2 - if \(\(then\)\@!.\)*$
-OK 0 - if \(\(then\)\@!.\)*$
-OK 1 - if \(\(then\)\@!.\)*$
-OK 2 - if \(\(then\)\@!.\)*$
-OK 0 - \(foo\)\@!bar
-OK 1 - \(foo\)\@!bar
-OK 2 - \(foo\)\@!bar
-OK 0 - \(foo\)\@!...bar
-OK 1 - \(foo\)\@!...bar
-OK 2 - \(foo\)\@!...bar
-OK 0 - ^\%(.*bar\)\@!.*\zsfoo
-OK 1 - ^\%(.*bar\)\@!.*\zsfoo
-OK 2 - ^\%(.*bar\)\@!.*\zsfoo
-OK 0 - ^\%(.*bar\)\@!.*\zsfoo
-OK 1 - ^\%(.*bar\)\@!.*\zsfoo
-OK 2 - ^\%(.*bar\)\@!.*\zsfoo
-OK 0 - ^\%(.*bar\)\@!.*\zsfoo
-OK 1 - ^\%(.*bar\)\@!.*\zsfoo
-OK 2 - ^\%(.*bar\)\@!.*\zsfoo
-OK 0 - [ ]\@!\p\%([ ]\@!\p\)*:
-OK 1 - [ ]\@!\p\%([ ]\@!\p\)*:
-OK 2 - [ ]\@!\p\%([ ]\@!\p\)*:
-OK 0 - [ ]\@!\p\([ ]\@!\p\)*:
-OK 1 - [ ]\@!\p\([ ]\@!\p\)*:
-OK 2 - [ ]\@!\p\([ ]\@!\p\)*:
-OK 0 - m\k\+_\@=\%(_\@!\k\)\@<=\k\+e
-OK 1 - m\k\+_\@=\%(_\@!\k\)\@<=\k\+e
-OK 2 - m\k\+_\@=\%(_\@!\k\)\@<=\k\+e
-OK 0 - \%(\U\@<=S\k*\|S\l\)R
-OK 1 - \%(\U\@<=S\k*\|S\l\)R
-OK 2 - \%(\U\@<=S\k*\|S\l\)R
-OK 0 - [[:alpha:]]\{-2,6}
-OK 1 - [[:alpha:]]\{-2,6}
-OK 2 - [[:alpha:]]\{-2,6}
-OK 0 -
-OK 1 -
-OK 2 -
-OK 0 - \v(())
-OK 1 - \v(())
-OK 2 - \v(())
-OK 0 - \v%(ab(xyz)c)
-OK 1 - \v%(ab(xyz)c)
-OK 2 - \v%(ab(xyz)c)
-OK 0 - \v(test|)empty
-OK 1 - \v(test|)empty
-OK 2 - \v(test|)empty
-OK 0 - \v(a|aa)(a|aa)
-OK 1 - \v(a|aa)(a|aa)
-OK 2 - \v(a|aa)(a|aa)
-OK 0 - \%d32
-OK 1 - \%d32
-OK 2 - \%d32
-OK 0 - \%o40
-OK 1 - \%o40
-OK 2 - \%o40
-OK 0 - \%x20
-OK 1 - \%x20
-OK 2 - \%x20
-OK 0 - \%u0020
-OK 1 - \%u0020
-OK 2 - \%u0020
-OK 0 - \%U00000020
-OK 1 - \%U00000020
-OK 2 - \%U00000020
-OK 0 - \%d0
-OK 1 - \%d0
-OK 2 - \%d0
-OK 0 - foo\%[bar]
-OK 1 - foo\%[bar]
-OK 2 - foo\%[bar]
-OK 0 - foo\%[bar]
-OK 1 - foo\%[bar]
-OK 2 - foo\%[bar]
-OK 0 - foo\%[bar]
-OK 1 - foo\%[bar]
-OK 2 - foo\%[bar]
-OK 0 - foo\%[bar]
-OK 1 - foo\%[bar]
-OK 2 - foo\%[bar]
-OK 0 - foo\%[bar]
-OK 1 - foo\%[bar]
-OK 2 - foo\%[bar]
-OK 0 - foo\%[bar]
-OK 1 - foo\%[bar]
-OK 2 - foo\%[bar]
-OK 0 - foo\%[bar]x
-OK 1 - foo\%[bar]x
-OK 2 - foo\%[bar]x
-OK 0 - foo\%[bar]x
-OK 1 - foo\%[bar]x
-OK 2 - foo\%[bar]x
-OK 0 - \%[bar]x
-OK 1 - \%[bar]x
-OK 2 - \%[bar]x
-OK 0 - \%[bar]x
-OK 1 - \%[bar]x
-OK 2 - \%[bar]x
-OK 0 - \%[bar]x
-OK 1 - \%[bar]x
-OK 2 - \%[bar]x
-OK 0 - b\%[[ao]r]
-OK 1 - b\%[[ao]r]
-OK 2 - b\%[[ao]r]
-OK 0 - b\%[[]]r]
-OK 1 - b\%[[]]r]
-OK 2 - b\%[[]]r]
-OK 0 - @\%[\w\-]*
-OK 1 - @\%[\w\-]*
-OK 2 - @\%[\w\-]*
-OK 0 - goo\|go
-OK 1 - goo\|go
-OK 2 - goo\|go
-OK 0 - \<goo\|\<go
-OK 1 - \<goo\|\<go
-OK 2 - \<goo\|\<go
-OK 0 - \<goo\|go
-OK 1 - \<goo\|go
-OK 2 - \<goo\|go
-OK 0 - \(\i\+\) \1
-OK 1 - \(\i\+\) \1
-OK 2 - \(\i\+\) \1
-OK 0 - \(\i\+\) \1
-OK 1 - \(\i\+\) \1
-OK 2 - \(\i\+\) \1
-OK 0 - \(a\)\(b\)\(c\)\(dd\)\(e\)\(f\)\(g\)\(h\)\(i\)\1\2\3\4\5\6\7\8\9
-OK 1 - \(a\)\(b\)\(c\)\(dd\)\(e\)\(f\)\(g\)\(h\)\(i\)\1\2\3\4\5\6\7\8\9
-OK 2 - \(a\)\(b\)\(c\)\(dd\)\(e\)\(f\)\(g\)\(h\)\(i\)\1\2\3\4\5\6\7\8\9
-OK 0 - \(\d*\)a \1b
-OK 1 - \(\d*\)a \1b
-OK 2 - \(\d*\)a \1b
-OK 0 - ^.\(.\).\_..\1.
-OK 1 - ^.\(.\).\_..\1.
-OK 2 - ^.\(.\).\_..\1.
-OK 0 - ^.*\.\(.*\)/.\+\(\1\)\@<!$
-OK 1 - ^.*\.\(.*\)/.\+\(\1\)\@<!$
-OK 2 - ^.*\.\(.*\)/.\+\(\1\)\@<!$
-OK 0 - ^.*\.\(.*\)/.\+\(\1\)\@<!$
-OK 1 - ^.*\.\(.*\)/.\+\(\1\)\@<!$
-OK 2 - ^.*\.\(.*\)/.\+\(\1\)\@<!$
-OK 0 - ^.*\.\(.*\)/.\+\(\1\)\@<=$
-OK 1 - ^.*\.\(.*\)/.\+\(\1\)\@<=$
-OK 2 - ^.*\.\(.*\)/.\+\(\1\)\@<=$
-OK 0 - \\\@<!\${\(\d\+\%(:.\{-}\)\?\\\@<!\)}
-OK 1 - \\\@<!\${\(\d\+\%(:.\{-}\)\?\\\@<!\)}
-OK 2 - \\\@<!\${\(\d\+\%(:.\{-}\)\?\\\@<!\)}
-OK 0 - ^\(a*\)\1$
-OK 1 - ^\(a*\)\1$
-OK 2 - ^\(a*\)\1$
-OK 0 - ^\(a\{-2,}\)\1\+$
-OK 1 - ^\(a\{-2,}\)\1\+$
-OK 2 - ^\(a\{-2,}\)\1\+$
-OK 0 - <\@<=span.
-OK 1 - <\@<=span.
-OK 2 - <\@<=span.
-OK 0 - <\@1<=span.
-OK 1 - <\@1<=span.
-OK 2 - <\@1<=span.
-OK 0 - <\@2<=span.
-OK 1 - <\@2<=span.
-OK 2 - <\@2<=span.
-OK 0 - \(<<\)\@<=span.
-OK 1 - \(<<\)\@<=span.
-OK 2 - \(<<\)\@<=span.
-OK 0 - \(<<\)\@1<=span.
-OK 1 - \(<<\)\@1<=span.
-OK 2 - \(<<\)\@1<=span.
-OK 0 - \(<<\)\@2<=span.
-OK 1 - \(<<\)\@2<=span.
-OK 2 - \(<<\)\@2<=span.
-OK 0 - \(foo\)\@<!bar.
-OK 1 - \(foo\)\@<!bar.
-OK 2 - \(foo\)\@<!bar.
-OK 0 - \v\C%(<Last Changed:\s+)@<=.*$
-OK 1 - \v\C%(<Last Changed:\s+)@<=.*$
-OK 2 - \v\C%(<Last Changed:\s+)@<=.*$
-OK 0 - \v\C%(<Last Changed:\s+)@<=.*$
-OK 1 - \v\C%(<Last Changed:\s+)@<=.*$
-OK 2 - \v\C%(<Last Changed:\s+)@<=.*$
-OK 0 - \(foo\)\@<=\>
-OK 1 - \(foo\)\@<=\>
-OK 2 - \(foo\)\@<=\>
-OK 0 - \(foo\)\@<=\>
-OK 1 - \(foo\)\@<=\>
-OK 2 - \(foo\)\@<=\>
-OK 0 - \(foo\)\@<=.*
-OK 1 - \(foo\)\@<=.*
-OK 2 - \(foo\)\@<=.*
-OK 0 - \(r\@<=\|\w\@<!\)\/
-OK 1 - \(r\@<=\|\w\@<!\)\/
-OK 2 - \(r\@<=\|\w\@<!\)\/
-OK 0 - ^[a-z]\+\ze \&\(asdf\)\@<!
-OK 1 - ^[a-z]\+\ze \&\(asdf\)\@<!
-OK 2 - ^[a-z]\+\ze \&\(asdf\)\@<!
-OK 0 - \(a*\)\@>a
-OK 1 - \(a*\)\@>a
-OK 2 - \(a*\)\@>a
-OK 0 - \(a*\)\@>b
-OK 1 - \(a*\)\@>b
-OK 2 - \(a*\)\@>b
-OK 0 - ^\(.\{-}b\)\@>.
-OK 1 - ^\(.\{-}b\)\@>.
-OK 2 - ^\(.\{-}b\)\@>.
-OK 0 - \(.\{-}\)\(\)\@>$
-OK 1 - \(.\{-}\)\(\)\@>$
-OK 2 - \(.\{-}\)\(\)\@>$
-OK 0 - \(a*\)\@>a\|a\+
-OK 2 - \(a*\)\@>a\|a\+
-OK 0 - \_[^8-9]\+
-OK 1 - \_[^8-9]\+
-OK 2 - \_[^8-9]\+
-OK 0 - \_[^a]\+
-OK 1 - \_[^a]\+
-OK 2 - \_[^a]\+
-OK 0 - [0-9a-zA-Z]\{8}-\([0-9a-zA-Z]\{4}-\)\{3}[0-9a-zA-Z]\{12}
-OK 1 - [0-9a-zA-Z]\{8}-\([0-9a-zA-Z]\{4}-\)\{3}[0-9a-zA-Z]\{12}
-OK 2 - [0-9a-zA-Z]\{8}-\([0-9a-zA-Z]\{4}-\)\{3}[0-9a-zA-Z]\{12}
-OK 0 - ^\%(\%(^\s*#\s*if\>\|#\s*if\)\)\(\%>1c.*$\)\@=
-OK 1 - ^\%(\%(^\s*#\s*if\>\|#\s*if\)\)\(\%>1c.*$\)\@=
-OK 2 - ^\%(\%(^\s*#\s*if\>\|#\s*if\)\)\(\%>1c.*$\)\@=
-OK 0 - \%>70vGesamt
-OK 1 - \%>70vGesamt
-OK 2 - \%>70vGesamt
-multi-line tests
-OK 0 - ^.\(.\).\_..\1.
-OK 1 - ^.\(.\).\_..\1.
-OK 2 - ^.\(.\).\_..\1.
-OK 0 - \v.*\/(.*)\n.*\/\1$
-OK 1 - \v.*\/(.*)\n.*\/\1$
-OK 2 - \v.*\/(.*)\n.*\/\1$
-OK 0 - \S.*\nx
-OK 1 - \S.*\nx
-OK 2 - \S.*\nx
-OK 0 - \<\(\(25\_[0-5]\|2\_[0-4]\_[0-9]\|\_[01]\?\_[0-9]\_[0-9]\?\)\.\)\{3\}\(25\_[0-5]\|2\_[0-4]\_[0-9]\|\_[01]\?\_[0-9]\_[0-9]\?\)\>
-OK 1 - \<\(\(25\_[0-5]\|2\_[0-4]\_[0-9]\|\_[01]\?\_[0-9]\_[0-9]\?\)\.\)\{3\}\(25\_[0-5]\|2\_[0-4]\_[0-9]\|\_[01]\?\_[0-9]\_[0-9]\?\)\>
-OK 2 - \<\(\(25\_[0-5]\|2\_[0-4]\_[0-9]\|\_[01]\?\_[0-9]\_[0-9]\?\)\.\)\{3\}\(25\_[0-5]\|2\_[0-4]\_[0-9]\|\_[01]\?\_[0-9]\_[0-9]\?\)\>
-OK 0 - a\n^b$\n^c
-OK 1 - a\n^b$\n^c
-OK 2 - a\n^b$\n^c
-OK 0 - \(^.\+\n\)\1
-OK 1 - \(^.\+\n\)\1
-OK 2 - \(^.\+\n\)\1
-
-<T="5">Ta 5</Title>
-<T="7">Ac 7</Title>
-
-xxstart3
-
-thexE thE thExethe
-AndAxAnd AndAxAnd
-oooxOfOr fOrOxooo
-oooxOfOr fOrOxooo
-
-asdfhereasdf
-asdfagainasdf
-
--0-
-ffo
-bob
-__ooooo
-koooo__
-moooooo
- f__
-ab!babababababfoo
-ba!ab##abab?bafoo
-**!*****_
- ! xxx?xxxxxxxx xxxx xxxxxx xxxxxxx x xxxxxxxxx xx xxxxxx xxxxxx xxxxx xxxxxxx xx xxxx xxxxxxxx xxxx xxxxxxxxxxx xxx xxxxxxx xxxxxxxxx xx xxxxxx xx xxxxxxx xxxxxxxxxxxxxxxx xxxxxxxxx xxx xxxxxxxx xxxxxxxxx xxxx xxx xxxx xxx xxx xxxxx xxxxxxxxxxxx xxxx xxxxxxxxx xxxxxxxxxxx xx xxxxx xxx xxxxxxxx xxxxxx xxx xxx xxxxxxxxx xxxxxxx x xxxxxxxxx xx xxxxxx xxxxxxx xxxxxxxxxxxxxxxxxx xxxxxxx xxxxxxx xxx xxx xxxxxxxx xxxxxxx xxxx xxx xxxxxx xxxxx xxxxx xx xxxxxx xxxxxxx xxx xxxxxxxxxxxx xxxx xxxxxxxxx xxxxxx xxxxxx xxxxx xxx xxxxxxx xxxxxxxxxxxxxxxx xxxxxxxxx xxxxxxxxxx xxxx xx xxxxxxxx xxx xxxxxxxxxxx xxxxx
--1-
-ffo
-bob
-__ooooo
-koooo__
-moooooo
- f__
-ab!babababababfoo
-ba!ab##abab?bafoo
-**!*****_
- ! xxx?xxxxxxxx xxxx xxxxxx xxxxxxx x xxxxxxxxx xx xxxxxx xxxxxx xxxxx xxxxxxx xx xxxx xxxxxxxx xxxx xxxxxxxxxxx xxx xxxxxxx xxxxxxxxx xx xxxxxx xx xxxxxxx xxxxxxxxxxxxxxxx xxxxxxxxx xxx xxxxxxxx xxxxxxxxx xxxx xxx xxxx xxx xxx xxxxx xxxxxxxxxxxx xxxx xxxxxxxxx xxxxxxxxxxx xx xxxxx xxx xxxxxxxx xxxxxx xxx xxx xxxxxxxxx xxxxxxx x xxxxxxxxx xx xxxxxx xxxxxxx xxxxxxxxxxxxxxxxxx xxxxxxx xxxxxxx xxx xxx xxxxxxxx xxxxxxx xxxx xxx xxxxxx xxxxx xxxxx xx xxxxxx xxxxxxx xxx xxxxxxxxxxxx xxxx xxxxxxxxx xxxxxx xxxxxx xxxxx xxx xxxxxxx xxxxxxxxxxxxxxxx xxxxxxxxx xxxxxxxxxx xxxx xx xxxxxxxx xxx xxxxxxxxxxx xxxxx
--2-
-ffo
-bob
-__ooooo
-koooo__
-moooooo
- f__
-ab!babababababfoo
-ba!ab##abab?bafoo
-**!*****_
- ! xxx?xxxxxxxx xxxx xxxxxx xxxxxxx x xxxxxxxxx xx xxxxxx xxxxxx xxxxx xxxxxxx xx xxxx xxxxxxxx xxxx xxxxxxxxxxx xxx xxxxxxx xxxxxxxxx xx xxxxxx xx xxxxxxx xxxxxxxxxxxxxxxx xxxxxxxxx xxx xxxxxxxx xxxxxxxxx xxxx xxx xxxx xxx xxx xxxxx xxxxxxxxxxxx xxxx xxxxxxxxx xxxxxxxxxxx xx xxxxx xxx xxxxxxxx xxxxxx xxx xxx xxxxxxxxx xxxxxxx x xxxxxxxxx xx xxxxxx xxxxxxx xxxxxxxxxxxxxxxxxx xxxxxxx xxxxxxx xxx xxx xxxxxxxx xxxxxxx xxxx xxx xxxxxx xxxxx xxxxx xx xxxxxx xxxxxxx xxx xxxxxxxxxxxx xxxx xxxxxxxxx xxxxxx xxxxxx xxxxx xxx xxxxxxx xxxxxxxxxxxxxxxx xxxxxxxxx xxxxxxxxxx xxxx xx xxxxxxxx xxx xxxxxxxxxxx xxxxx
-Test
-Test END
-EN
-E
-E888 detected for \ze*
-E888 detected for \zs*
diff --git a/src/nvim/testdir/test_alot.vim b/src/nvim/testdir/test_alot.vim
index 6bf2e8329c..5668f45dea 100644
--- a/src/nvim/testdir/test_alot.vim
+++ b/src/nvim/testdir/test_alot.vim
@@ -2,6 +2,7 @@
" This makes testing go faster, since Vim doesn't need to restart.
source test_assign.vim
+source test_backup.vim
source test_behave.vim
source test_cd.vim
source test_changedtick.vim
@@ -27,7 +28,6 @@ source test_jumps.vim
source test_fileformat.vim
source test_filetype.vim
source test_lambda.vim
-source test_mapping.vim
source test_menu.vim
source test_messages.vim
source test_modeline.vim
@@ -45,6 +45,7 @@ source test_syn_attr.vim
source test_tabline.vim
source test_tabpage.vim
source test_tagcase.vim
+source test_tagfunc.vim
source test_tagjump.vim
source test_taglist.vim
source test_true_false.vim
diff --git a/src/nvim/testdir/test_assert.vim b/src/nvim/testdir/test_assert.vim
index a4c8ce7e43..b4f7478807 100644
--- a/src/nvim/testdir/test_assert.vim
+++ b/src/nvim/testdir/test_assert.vim
@@ -1,5 +1,51 @@
" Test that the methods used for testing work.
+func Test_assert_equalfile()
+ call assert_equal(1, assert_equalfile('abcabc', 'xyzxyz'))
+ call assert_match("E485: Can't read file abcabc", v:errors[0])
+ call remove(v:errors, 0)
+
+ let goodtext = ["one", "two", "three"]
+ call writefile(goodtext, 'Xone')
+ call assert_equal(1, assert_equalfile('Xone', 'xyzxyz'))
+ call assert_match("E485: Can't read file xyzxyz", v:errors[0])
+ call remove(v:errors, 0)
+
+ call writefile(goodtext, 'Xtwo')
+ call assert_equal(0, assert_equalfile('Xone', 'Xtwo'))
+
+ call writefile([goodtext[0]], 'Xone')
+ call assert_equal(1, assert_equalfile('Xone', 'Xtwo'))
+ call assert_match("first file is shorter", v:errors[0])
+ call remove(v:errors, 0)
+
+ call writefile(goodtext, 'Xone')
+ call writefile([goodtext[0]], 'Xtwo')
+ call assert_equal(1, assert_equalfile('Xone', 'Xtwo'))
+ call assert_match("second file is shorter", v:errors[0])
+ call remove(v:errors, 0)
+
+ call writefile(['1234X89'], 'Xone')
+ call writefile(['1234Y89'], 'Xtwo')
+ call assert_equal(1, assert_equalfile('Xone', 'Xtwo'))
+ call assert_match('difference at byte 4, line 1 after "1234X" vs "1234Y"', v:errors[0])
+ call remove(v:errors, 0)
+
+ call writefile([repeat('x', 234) .. 'X'], 'Xone')
+ call writefile([repeat('x', 234) .. 'Y'], 'Xtwo')
+ call assert_equal(1, assert_equalfile('Xone', 'Xtwo'))
+ let xes = repeat('x', 134)
+ call assert_match('difference at byte 234, line 1 after "' .. xes .. 'X" vs "' .. xes .. 'Y"', v:errors[0])
+ call remove(v:errors, 0)
+
+ 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_fails_in_try_block()
try
call assert_equal(0, assert_fails('throw "error"'))
diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim
index 5848940a2b..d116246ef3 100644
--- a/src/nvim/testdir/test_autocmd.vim
+++ b/src/nvim/testdir/test_autocmd.vim
@@ -52,7 +52,7 @@ if has('timers')
au CursorHoldI * let g:triggered += 1
set updatetime=500
call job_start(has('win32') ? 'cmd /c echo:' : 'echo',
- \ {'exit_cb': {j, s -> timer_start(1000, 'ExitInsertMode')}})
+ \ {'exit_cb': {-> timer_start(1000, 'ExitInsertMode')}})
call feedkeys('a', 'x!')
call assert_equal(1, g:triggered)
unlet g:triggered
@@ -425,18 +425,20 @@ func Test_autocmd_bufwipe_in_SessLoadPost()
set noswapfile
mksession!
- let content = ['set nocp noswapfile',
- \ 'let v:swapchoice="e"',
- \ 'augroup test_autocmd_sessionload',
- \ 'autocmd!',
- \ 'autocmd SessionLoadPost * exe bufnr("Xsomething") . "bw!"',
- \ 'augroup END',
- \ '',
- \ 'func WriteErrors()',
- \ ' call writefile([execute("messages")], "Xerrors")',
- \ 'endfunc',
- \ 'au VimLeave * call WriteErrors()',
- \ ]
+ let content =<< trim [CODE]
+ set nocp noswapfile
+ let v:swapchoice="e"
+ augroup test_autocmd_sessionload
+ autocmd!
+ autocmd SessionLoadPost * exe bufnr("Xsomething") . "bw!"
+ augroup END
+
+ func WriteErrors()
+ call writefile([execute("messages")], "Xerrors")
+ endfunc
+ au VimLeave * call WriteErrors()
+ [CODE]
+
call writefile(content, 'Xvimrc')
call system(v:progpath. ' --headless -i NONE -u Xvimrc --noplugins -S Session.vim -c cq')
let errors = join(readfile('Xerrors'))
@@ -454,27 +456,29 @@ func Test_autocmd_bufwipe_in_SessLoadPost2()
set noswapfile
mksession!
- let content = ['set nocp noswapfile',
- \ 'function! DeleteInactiveBufs()',
- \ ' tabfirst',
- \ ' let tabblist = []',
- \ ' for i in range(1, tabpagenr(''$''))',
- \ ' call extend(tabblist, tabpagebuflist(i))',
- \ ' endfor',
- \ ' for b in range(1, bufnr(''$''))',
- \ ' if bufexists(b) && buflisted(b) && (index(tabblist, b) == -1 || bufname(b) =~# ''^$'')',
- \ ' exec ''bwipeout '' . b',
- \ ' endif',
- \ ' endfor',
- \ ' echomsg "SessionLoadPost DONE"',
- \ 'endfunction',
- \ 'au SessionLoadPost * call DeleteInactiveBufs()',
- \ '',
- \ 'func WriteErrors()',
- \ ' call writefile([execute("messages")], "Xerrors")',
- \ 'endfunc',
- \ 'au VimLeave * call WriteErrors()',
- \ ]
+ let content =<< trim [CODE]
+ set nocp noswapfile
+ function! DeleteInactiveBufs()
+ tabfirst
+ let tabblist = []
+ for i in range(1, tabpagenr(''$''))
+ call extend(tabblist, tabpagebuflist(i))
+ endfor
+ for b in range(1, bufnr(''$''))
+ if bufexists(b) && buflisted(b) && (index(tabblist, b) == -1 || bufname(b) =~# ''^$'')
+ exec ''bwipeout '' . b
+ endif
+ endfor
+ echomsg "SessionLoadPost DONE"
+ endfunction
+ au SessionLoadPost * call DeleteInactiveBufs()
+
+ func WriteErrors()
+ call writefile([execute("messages")], "Xerrors")
+ endfunc
+ au VimLeave * call WriteErrors()
+ [CODE]
+
call writefile(content, 'Xvimrc')
call system(v:progpath. ' --headless -i NONE -u Xvimrc --noplugins -S Session.vim -c cq')
let errors = join(readfile('Xerrors'))
@@ -936,21 +940,23 @@ func Test_bufunload_all()
call writefile(['Test file Xxx1'], 'Xxx1')"
call writefile(['Test file Xxx2'], 'Xxx2')"
- let content = [
- \ "func UnloadAllBufs()",
- \ " let i = 1",
- \ " while i <= bufnr('$')",
- \ " if i != bufnr('%') && bufloaded(i)",
- \ " exe i . 'bunload'",
- \ " endif",
- \ " let i += 1",
- \ " endwhile",
- \ "endfunc",
- \ "au BufUnload * call UnloadAllBufs()",
- \ "au VimLeave * call writefile(['Test Finished'], 'Xout')",
- \ "edit Xxx1",
- \ "split Xxx2",
- \ "q"]
+ let content =<< trim [CODE]
+ func UnloadAllBufs()
+ let i = 1
+ while i <= bufnr('$')
+ if i != bufnr('%') && bufloaded(i)
+ exe i . 'bunload'
+ endif
+ let i += 1
+ endwhile
+ endfunc
+ au BufUnload * call UnloadAllBufs()
+ au VimLeave * call writefile(['Test Finished'], 'Xout')
+ edit Xxx1
+ split Xxx2
+ q
+ [CODE]
+
call writefile(content, 'Xtest')
call delete('Xout')
@@ -1066,6 +1072,40 @@ func Test_Cmd_Autocmds()
enew!
endfunc
+func s:ReadFile()
+ setl noswapfile nomodified
+ let filename = resolve(expand("<afile>:p"))
+ execute 'read' fnameescape(filename)
+ 1d_
+ exe 'file' fnameescape(filename)
+ setl buftype=acwrite
+endfunc
+
+func s:WriteFile()
+ let filename = resolve(expand("<afile>:p"))
+ setl buftype=
+ noautocmd execute 'write' fnameescape(filename)
+ setl buftype=acwrite
+ setl nomodified
+endfunc
+
+func Test_BufReadCmd()
+ autocmd BufReadCmd *.test call s:ReadFile()
+ autocmd BufWriteCmd *.test call s:WriteFile()
+
+ call writefile(['one', 'two', 'three'], 'Xcmd.test')
+ edit Xcmd.test
+ call assert_match('Xcmd.test" line 1 of 3', execute('file'))
+ normal! Gofour
+ write
+ call assert_equal(['one', 'two', 'three', 'four'], readfile('Xcmd.test'))
+
+ bwipe!
+ call delete('Xcmd.test')
+ au! BufReadCmd
+ au! BufWriteCmd
+endfunc
+
func SetChangeMarks(start, end)
exe a:start. 'mark ['
exe a:end. 'mark ]'
@@ -1206,23 +1246,27 @@ func Test_TextYankPost()
norm "ayiw
call assert_equal(
- \{'regcontents': ['foo'], 'inclusive': v:true, 'regname': 'a', 'operator': 'y', 'regtype': 'v'},
+ \{'regcontents': ['foo'], 'inclusive': v:true, 'regname': 'a', 'operator': 'y', 'visual': v:false, 'regtype': 'v'},
\g:event)
norm y_
call assert_equal(
- \{'regcontents': ['foo'], 'inclusive': v:false, 'regname': '', 'operator': 'y', 'regtype': 'V'},
+ \{'regcontents': ['foo'], 'inclusive': v:false, 'regname': '', 'operator': 'y', 'visual': v:false, 'regtype': 'V'},
+ \g:event)
+ norm Vy
+ call assert_equal(
+ \{'regcontents': ['foo'], 'inclusive': v:true, 'regname': '', 'operator': 'y', 'visual': v:true, 'regtype': 'V'},
\g:event)
call feedkeys("\<C-V>y", 'x')
call assert_equal(
- \{'regcontents': ['f'], 'inclusive': v:true, 'regname': '', 'operator': 'y', 'regtype': "\x161"},
+ \{'regcontents': ['f'], 'inclusive': v:true, 'regname': '', 'operator': 'y', 'visual': v:true, 'regtype': "\x161"},
\g:event)
norm "xciwbar
call assert_equal(
- \{'regcontents': ['foo'], 'inclusive': v:true, 'regname': 'x', 'operator': 'c', 'regtype': 'v'},
+ \{'regcontents': ['foo'], 'inclusive': v:true, 'regname': 'x', 'operator': 'c', 'visual': v:false, 'regtype': 'v'},
\g:event)
norm "bdiw
call assert_equal(
- \{'regcontents': ['bar'], 'inclusive': v:true, 'regname': 'b', 'operator': 'd', 'regtype': 'v'},
+ \{'regcontents': ['bar'], 'inclusive': v:true, 'regname': 'b', 'operator': 'd', 'visual': v:false, 'regtype': 'v'},
\g:event)
call assert_equal({}, v:event)
@@ -1344,11 +1388,11 @@ func Test_Changed_FirstTime()
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 WaitFor({-> term_getline(buf, 3) =~# '\<All$'})
+ call WaitForAssert({-> assert_match('\<All$', term_getline(buf, 3))})
" 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>")
- call WaitFor({-> term_getstatus(buf) == 'finished'})
+ call WaitForAssert({-> assert_equal('finished', term_getstatus(buf))})
call assert_equal([''], readfile('Xchanged.txt'))
" clean up
@@ -1780,3 +1824,46 @@ func Test_FileChangedShell_reload()
bwipe!
call delete('Xchanged')
endfunc
+
+" Test for FileReadCmd autocmd
+func Test_autocmd_FileReadCmd()
+ func ReadFileCmd()
+ call append(line('$'), "v:cmdarg = " .. v:cmdarg)
+ endfunc
+ augroup FileReadCmdTest
+ au!
+ au FileReadCmd Xtest call ReadFileCmd()
+ augroup END
+
+ new
+ read ++bin Xtest
+ read ++nobin Xtest
+ read ++edit Xtest
+ read ++bad=keep Xtest
+ read ++bad=drop Xtest
+ read ++bad=- Xtest
+ read ++ff=unix Xtest
+ read ++ff=dos Xtest
+ read ++ff=mac Xtest
+ read ++enc=utf-8 Xtest
+
+ call assert_equal(['',
+ \ 'v:cmdarg = ++bin',
+ \ 'v:cmdarg = ++nobin',
+ \ 'v:cmdarg = ++edit',
+ \ 'v:cmdarg = ++bad=keep',
+ \ 'v:cmdarg = ++bad=drop',
+ \ 'v:cmdarg = ++bad=-',
+ \ 'v:cmdarg = ++ff=unix',
+ \ 'v:cmdarg = ++ff=dos',
+ \ 'v:cmdarg = ++ff=mac',
+ \ 'v:cmdarg = ++enc=utf-8'], getline(1, '$'))
+
+ close!
+ augroup FileReadCmdTest
+ au!
+ augroup END
+ delfunc ReadFileCmd
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_backup.vim b/src/nvim/testdir/test_backup.vim
new file mode 100644
index 0000000000..ce2bfe72bc
--- /dev/null
+++ b/src/nvim/testdir/test_backup.vim
@@ -0,0 +1,58 @@
+" Tests for the backup function
+
+func Test_backup()
+ set backup backupdir=. backupskip=
+ new
+ call setline(1, ['line1', 'line2'])
+ :f Xbackup.txt
+ :w! Xbackup.txt
+ " backup file is only created after
+ " writing a second time (before overwriting)
+ :w! Xbackup.txt
+ let l = readfile('Xbackup.txt~')
+ call assert_equal(['line1', 'line2'], l)
+ bw!
+ set backup&vim backupdir&vim backupskip&vim
+ call delete('Xbackup.txt')
+ call delete('Xbackup.txt~')
+endfunc
+
+func Test_backup2()
+ set backup backupdir=.// backupskip=
+ new
+ call setline(1, ['line1', 'line2', 'line3'])
+ :f Xbackup.txt
+ :w! Xbackup.txt
+ " backup file is only created after
+ " writing a second time (before overwriting)
+ :w! Xbackup.txt
+ sp *Xbackup.txt~
+ call assert_equal(['line1', 'line2', 'line3'], getline(1,'$'))
+ let f=expand('%')
+ call assert_match('%testdir%Xbackup.txt\~', f)
+ bw!
+ bw!
+ call delete('Xbackup.txt')
+ call delete(f)
+ set backup&vim backupdir&vim backupskip&vim
+endfunc
+
+func Test_backup2_backupcopy()
+ set backup backupdir=.// backupcopy=yes backupskip=
+ new
+ call setline(1, ['line1', 'line2', 'line3'])
+ :f Xbackup.txt
+ :w! Xbackup.txt
+ " backup file is only created after
+ " writing a second time (before overwriting)
+ :w! Xbackup.txt
+ sp *Xbackup.txt~
+ call assert_equal(['line1', 'line2', 'line3'], getline(1,'$'))
+ let f=expand('%')
+ call assert_match('%testdir%Xbackup.txt\~', f)
+ bw!
+ bw!
+ call delete('Xbackup.txt')
+ call delete(f)
+ set backup&vim backupdir&vim backupcopy&vim backupskip&vim
+endfunc
diff --git a/src/nvim/testdir/test_breakindent.vim b/src/nvim/testdir/test_breakindent.vim
index 4b34420cab..a4c1f62a43 100644
--- a/src/nvim/testdir/test_breakindent.vim
+++ b/src/nvim/testdir/test_breakindent.vim
@@ -296,3 +296,129 @@ function Test_breakindent16()
call s:compare_lines(expect, lines)
call s:close_windows()
endfunction
+
+func Test_breakindent17_vartabs()
+ if !has("vartabs")
+ return
+ endif
+ let s:input = ""
+ call s:test_windows('setl breakindent list listchars=tab:<-> showbreak=+++')
+ call setline(1, "\t" . repeat('a', 63))
+ vert resize 30
+ norm! 1gg$
+ redraw!
+ let lines = s:screen_lines(1, 30)
+ let expect = [
+ \ "<-->aaaaaaaaaaaaaaaaaaaaaaaaaa",
+ \ " +++aaaaaaaaaaaaaaaaaaaaaaa",
+ \ " +++aaaaaaaaaaaaaa ",
+ \ ]
+ call s:compare_lines(expect, lines)
+ call s:close_windows('set breakindent& list& listchars& showbreak&')
+endfunc
+
+func Test_breakindent18_vartabs()
+ if !has("vartabs")
+ return
+ endif
+ let s:input = ""
+ call s:test_windows('setl breakindent list listchars=tab:<->')
+ call setline(1, "\t" . repeat('a', 63))
+ vert resize 30
+ norm! 1gg$
+ redraw!
+ let lines = s:screen_lines(1, 30)
+ let expect = [
+ \ "<-->aaaaaaaaaaaaaaaaaaaaaaaaaa",
+ \ " aaaaaaaaaaaaaaaaaaaaaaaaaa",
+ \ " aaaaaaaaaaa ",
+ \ ]
+ call s:compare_lines(expect, lines)
+ call s:close_windows('set breakindent& list& listchars&')
+endfunc
+
+func Test_breakindent19_sbr_nextpage()
+ let s:input = ""
+ call s:test_windows('setl breakindent briopt=shift:2,sbr,min:18 sbr=>')
+ call setline(1, repeat('a', 200))
+ norm! 1gg
+ redraw!
+ let lines = s:screen_lines(1, 20)
+ let expect = [
+ \ "aaaaaaaaaaaaaaaaaaaa",
+ \ "> aaaaaaaaaaaaaaaaaa",
+ \ "> aaaaaaaaaaaaaaaaaa",
+ \ ]
+ call s:compare_lines(expect, lines)
+ " Scroll down one screen line
+ setl scrolloff=5
+ norm! 5gj
+ redraw!
+ let lines = s:screen_lines(1, 20)
+ let expect = [
+ \ "> aaaaaaaaaaaaaaaaaa",
+ \ "> aaaaaaaaaaaaaaaaaa",
+ \ "> aaaaaaaaaaaaaaaaaa",
+ \ ]
+ call s:compare_lines(expect, lines)
+
+ setl breakindent briopt=min:18 sbr=>
+ norm! 5gj
+ let lines = s:screen_lines(1, 20)
+ let expect = [
+ \ ">aaaaaaaaaaaaaaaaaaa",
+ \ ">aaaaaaaaaaaaaaaaaaa",
+ \ ">aaaaaaaaaaaaaaaaaaa",
+ \ ]
+ call s:compare_lines(expect, lines)
+ call s:close_windows('set breakindent& briopt& sbr&')
+endfunc
+
+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))
+ norm! 1gg
+ redraw!
+ let lines = s:screen_lines(1, 20)
+ let expect = [
+ \ " 1 aaaaaaaaaaaaaaaa",
+ \ " aaaaaaaaaaaaaaaa",
+ \ " aaaaaaaaaaaaaaaa",
+ \ ]
+ call s:compare_lines(expect, lines)
+ " Scroll down one screen line
+ setl scrolloff=5
+ norm! 5gj
+ redraw!
+ let lines = s:screen_lines(1, 20)
+ let expect = [
+ \ "--1 aaaaaaaaaaaaaaaa",
+ \ " aaaaaaaaaaaaaaaa",
+ \ " aaaaaaaaaaaaaaaa",
+ \ ]
+ call s:compare_lines(expect, lines)
+
+ setl briopt+=shift:2
+ norm! 1gg
+ let lines = s:screen_lines(1, 20)
+ let expect = [
+ \ " 1 aaaaaaaaaaaaaaaa",
+ \ " aaaaaaaaaaaaaa",
+ \ " aaaaaaaaaaaaaa",
+ \ ]
+ call s:compare_lines(expect, lines)
+ " Scroll down one screen line
+ norm! 5gj
+ let lines = s:screen_lines(1, 20)
+ let expect = [
+ \ "--1 aaaaaaaaaaaaaa",
+ \ " aaaaaaaaaaaaaa",
+ \ " aaaaaaaaaaaaaa",
+ \ ]
+ call s:compare_lines(expect, lines)
+
+ call s:close_windows('set breakindent& briopt& cpo& number&')
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_bufline.vim b/src/nvim/testdir/test_bufline.vim
index a924ce0002..076f03fdd8 100644
--- a/src/nvim/testdir/test_bufline.vim
+++ b/src/nvim/testdir/test_bufline.vim
@@ -1,7 +1,7 @@
" Tests for setbufline(), getbufline(), appendbufline(), deletebufline()
source shared.vim
-" source screendump.vim
+source screendump.vim
func Test_setbufline_getbufline()
new
diff --git a/src/nvim/testdir/test_bufwintabinfo.vim b/src/nvim/testdir/test_bufwintabinfo.vim
index 0e8c7d1dc1..cb7ab44798 100644
--- a/src/nvim/testdir/test_bufwintabinfo.vim
+++ b/src/nvim/testdir/test_bufwintabinfo.vim
@@ -139,3 +139,20 @@ function Test_get_win_options()
set foldlevel=0
endif
endfunc
+
+func Test_getbufinfo_lines()
+ new Xfoo
+ call setline(1, ['a', 'bc', 'd'])
+ let bn = bufnr('%')
+ hide
+ call assert_equal(3, getbufinfo(bn)[0]["linecount"])
+ edit Xfoo
+ bw!
+endfunc
+
+function Test_getbufinfo_lastused()
+ new Xfoo
+ let info = getbufinfo('Xfoo')[0]
+ call assert_equal(has_key(info, 'lastused'), 1)
+ call assert_equal(type(info.lastused), type(0))
+endfunc
diff --git a/src/nvim/testdir/test_cindent.vim b/src/nvim/testdir/test_cindent.vim
index 7c2c5e341c..debc9da46d 100644
--- a/src/nvim/testdir/test_cindent.vim
+++ b/src/nvim/testdir/test_cindent.vim
@@ -18,25 +18,25 @@ endfunc
func Test_cino_extern_c()
" Test for cino-E
- let without_ind = [
- \ '#ifdef __cplusplus',
- \ 'extern "C" {',
- \ '#endif',
- \ 'int func_a(void);',
- \ '#ifdef __cplusplus',
- \ '}',
- \ '#endif'
- \ ]
+ let without_ind =<< trim [CODE]
+ #ifdef __cplusplus
+ extern "C" {
+ #endif
+ int func_a(void);
+ #ifdef __cplusplus
+ }
+ #endif
+ [CODE]
- let with_ind = [
- \ '#ifdef __cplusplus',
- \ 'extern "C" {',
- \ '#endif',
- \ "\tint func_a(void);",
- \ '#ifdef __cplusplus',
- \ '}',
- \ '#endif'
- \ ]
+ let with_ind =<< trim [CODE]
+ #ifdef __cplusplus
+ extern "C" {
+ #endif
+ int func_a(void);
+ #ifdef __cplusplus
+ }
+ #endif
+ [CODE]
new
setlocal cindent cinoptions=E0
call setline(1, without_ind)
@@ -89,17 +89,42 @@ func Test_cindent_expr()
return v:lnum == 1 ? shiftwidth() : 0
endfunc
setl expandtab sw=8 indentkeys+=; indentexpr=MyIndentFunction()
- call setline(1, ['var_a = something()', 'b = something()'])
+ let testinput =<< trim [CODE]
+ var_a = something()
+ b = something()
+ [CODE]
+ call setline(1, testinput)
call cursor(1, 1)
call feedkeys("^\<c-v>j$A;\<esc>", 'tnix')
- call assert_equal([' var_a = something();', 'b = something();'], getline(1, '$'))
+ let expected =<< [CODE]
+ var_a = something();
+b = something();
+[CODE]
+ call assert_equal(expected, getline(1, '$'))
%d
- call setline(1, [' var_a = something()', ' b = something()'])
+ let testinput =<< [CODE]
+ var_a = something()
+ b = something()
+[CODE]
+ call setline(1, testinput)
call cursor(1, 1)
call feedkeys("^\<c-v>j$A;\<esc>", 'tnix')
- call assert_equal([' var_a = something();', ' b = something()'], getline(1, '$'))
+ let expected =<< [CODE]
+ var_a = something();
+ b = something()
+[CODE]
+ call assert_equal(expected, getline(1, '$'))
bw!
endfunc
+" this was going beyond the end of the line.
+func Test_cindent_case()
+ new
+ call setline(1, "case x: // x")
+ set cindent
+ norm! f:a:
+ bwipe!
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_clientserver.vim b/src/nvim/testdir/test_clientserver.vim
index 813cb338a5..3377f86126 100644
--- a/src/nvim/testdir/test_clientserver.vim
+++ b/src/nvim/testdir/test_clientserver.vim
@@ -28,12 +28,11 @@ func Test_client_server()
let name = 'XVIMTEST'
let cmd .= ' --servername ' . name
let job = job_start(cmd, {'stoponexit': 'kill', 'out_io': 'null'})
- call WaitFor({-> job_status(job) == "run"})
+ call WaitForAssert({-> assert_equal("run", job_status(job))})
" Takes a short while for the server to be active.
" When using valgrind it takes much longer.
- call WaitFor('serverlist() =~ "' . name . '"')
- call assert_match(name, serverlist())
+ call WaitForAssert({-> assert_match(name, serverlist())})
call remote_foreground(name)
@@ -54,12 +53,10 @@ func Test_client_server()
endif
" Wait for the server to be up and answering requests.
sleep 100m
- call WaitFor('remote_expr("' . name . '", "v:version", "", 1) != ""')
- call assert_true(remote_expr(name, "v:version", "", 1) != "")
+ call WaitForAssert({-> assert_true(remote_expr(name, "v:version", "", 1) != "")})
call remote_send(name, ":let testvar = 'maybe'\<CR>")
- call WaitFor('remote_expr("' . name . '", "testvar", "", 1) == "maybe"')
- call assert_equal('maybe', remote_expr(name, "testvar", "", 2))
+ call WaitForAssert({-> assert_equal('maybe', remote_expr(name, "testvar", "", 2))})
endif
call assert_fails('call remote_send("XXX", ":let testvar = ''yes''\<CR>")', 'E241')
@@ -94,7 +91,7 @@ func Test_client_server()
call remote_send(name, ":qa!\<CR>")
try
- call WaitFor({-> job_status(job) == "dead"})
+ call WaitForAssert({-> assert_equal("dead", job_status(job))})
finally
if job_status(job) != 'dead'
call assert_report('Server did not exit')
diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim
index e6aafd964b..f8d84f1a49 100644
--- a/src/nvim/testdir/test_cmdline.vim
+++ b/src/nvim/testdir/test_cmdline.vim
@@ -1,6 +1,5 @@
" Tests for editing the command line.
-
func Test_complete_tab()
call writefile(['testfile'], 'Xtestfile')
call feedkeys(":e Xtestf\t\r", "tx")
@@ -79,26 +78,45 @@ func Test_map_completion()
call feedkeys(":map <silent> <sp\<Tab>\<Home>\"\<CR>", 'xt')
call assert_equal('"map <silent> <special>', getreg(':'))
+ map <Middle>x middle
+
map ,f commaf
map ,g commaf
+ map <Left> left
+ map <A-Left>x shiftleft
call feedkeys(":map ,\<Tab>\<Home>\"\<CR>", 'xt')
call assert_equal('"map ,f', getreg(':'))
call feedkeys(":map ,\<Tab>\<Tab>\<Home>\"\<CR>", 'xt')
call assert_equal('"map ,g', getreg(':'))
+ call feedkeys(":map <L\<Tab>\<Home>\"\<CR>", 'xt')
+ call assert_equal('"map <Left>', getreg(':'))
+ call feedkeys(":map <A-Left>\<Tab>\<Home>\"\<CR>", 'xt')
+ call assert_equal("\"map <A-Left>\<Tab>", getreg(':'))
unmap ,f
unmap ,g
+ unmap <Left>
+ unmap <A-Left>x
set cpo-=< cpo-=B 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(':'))
unmap <Left>
" set cpo+=<
map <Left> left
+ exe "set t_k6=\<Esc>[17~"
+ call feedkeys(":map \<Esc>[17~x f6x\<CR>", 'xt')
call feedkeys(":map <L\<Tab>\<Home>\"\<CR>", 'xt')
call assert_equal('"map <Left>', getreg(':'))
+ if !has('gui_running')
+ call feedkeys(":map \<Esc>[17~\<Tab>\<Home>\"\<CR>", 'xt')
+ " call assert_equal("\"map <F6>x", getreg(':'))
+ endif
unmap <Left>
+ call feedkeys(":unmap \<Esc>[17~x\<CR>", 'xt')
set cpo-=<
set cpo+=B
@@ -114,6 +132,9 @@ func Test_map_completion()
call assert_equal('"map <Left>', getreg(':'))
unmap <Left>
" set cpo-=k
+
+ unmap <Middle>x
+ set cpo&vim
endfunc
func Test_match_completion()
@@ -160,6 +181,7 @@ func Test_expr_completion()
endif
for cmd in [
\ 'let a = ',
+ \ 'const a = ',
\ 'if',
\ 'elseif',
\ 'while',
@@ -302,7 +324,7 @@ func Test_getcompletion()
call assert_equal([], l)
let l = getcompletion('.', 'shellcmd')
- call assert_equal(['./', '../'], l[0:1])
+ call assert_equal(['./', '../'], filter(l, 'v:val =~ "\\./"'))
call assert_equal(-1, match(l[2:], '^\.\.\?/$'))
let root = has('win32') ? 'C:\\' : '/'
let l = getcompletion(root, 'shellcmd')
@@ -376,6 +398,29 @@ func Test_getcompletion()
call assert_fails('call getcompletion("", "burp")', 'E475:')
endfunc
+func Test_shellcmd_completion()
+ let save_path = $PATH
+
+ call mkdir('Xpathdir/Xpathsubdir', 'p')
+ call writefile([''], 'Xpathdir/Xfile.exe')
+ call setfperm('Xpathdir/Xfile.exe', 'rwx------')
+
+ " Set PATH to example directory without trailing slash.
+ let $PATH = getcwd() . '/Xpathdir'
+
+ " Test for the ":!<TAB>" case. Previously, this would include subdirs of
+ " dirs in the PATH, even though they won't be executed. We check that only
+ " subdirs of the PWD and executables from the PATH are included in the
+ " suggestions.
+ let actual = getcompletion('X', 'shellcmd')
+ let expected = map(filter(glob('*', 0, 1), 'isdirectory(v:val) && v:val[0] == "X"'), 'v:val . "/"')
+ 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')
@@ -477,6 +522,51 @@ func Test_cmdline_complete_user_cmd()
delcommand Foo
endfunc
+func Test_cmdline_complete_user_names()
+ if has('unix') && executable('whoami')
+ let whoami = systemlist('whoami')[0]
+ let first_letter = whoami[0]
+ if len(first_letter) > 0
+ " Trying completion of :e ~x where x is the first letter of
+ " the user name should complete to at least the user name.
+ call feedkeys(':e ~' . first_letter . "\<c-a>\<c-B>\"\<cr>", 'tx')
+ call assert_match('^"e \~.*\<' . whoami . '\>', @:)
+ endif
+ endif
+ if has('win32')
+ " Just in case: check that the system has an Administrator account.
+ let names = system('net user')
+ if names =~ 'Administrator'
+ " Trying completion of :e ~A should complete to Administrator.
+ " There could be other names starting with "A" before Administrator.
+ call feedkeys(':e ~A' . "\<c-a>\<c-B>\"\<cr>", 'tx')
+ call assert_match('^"e \~.*Administrator', @:)
+ endif
+ endif
+endfunc
+
+funct Test_cmdline_complete_languages()
+ let lang = substitute(execute('language messages'), '.*"\(.*\)"$', '\1', '')
+
+ call feedkeys(":language \<c-a>\<c-b>\"\<cr>", 'tx')
+ call assert_match('^"language .*\<ctype\>.*\<messages\>.*\<time\>', @:)
+
+ if has('unix')
+ " TODO: these tests don't work on Windows. lang appears to be 'C'
+ " but C does not appear in the completion. Why?
+ call assert_match('^"language .*\<' . lang . '\>', @:)
+
+ call feedkeys(":language messages \<c-a>\<c-b>\"\<cr>", 'tx')
+ call assert_match('^"language .*\<' . lang . '\>', @:)
+
+ call feedkeys(":language ctype \<c-a>\<c-b>\"\<cr>", 'tx')
+ call assert_match('^"language .*\<' . lang . '\>', @:)
+
+ call feedkeys(":language time \<c-a>\<c-b>\"\<cr>", 'tx')
+ call assert_match('^"language .*\<' . lang . '\>', @:)
+ endif
+endfunc
+
func Test_cmdline_write_alternatefile()
new
call setline('.', ['one', 'two'])
@@ -529,6 +619,8 @@ func Check_cmdline(cmdtype)
return ''
endfunc
+set cpo&
+
func Test_getcmdtype()
call feedkeys(":MyCmd a\<C-R>=Check_cmdline(':')\<CR>\<Esc>", "xt")
@@ -569,6 +661,53 @@ func Test_getcmdwintype()
call assert_equal('', getcmdwintype())
endfunc
+func Test_getcmdwin_autocmd()
+ let s:seq = []
+ augroup CmdWin
+ au WinEnter * call add(s:seq, 'WinEnter ' .. win_getid())
+ au WinLeave * call add(s:seq, 'WinLeave ' .. win_getid())
+ au BufEnter * call add(s:seq, 'BufEnter ' .. bufnr())
+ au BufLeave * call add(s:seq, 'BufLeave ' .. bufnr())
+ au CmdWinEnter * call add(s:seq, 'CmdWinEnter ' .. win_getid())
+ au CmdWinLeave * call add(s:seq, 'CmdWinLeave ' .. win_getid())
+
+ let org_winid = win_getid()
+ let org_bufnr = bufnr()
+ call feedkeys("q::let a = getcmdwintype()\<CR>:let s:cmd_winid = win_getid()\<CR>:let s:cmd_bufnr = bufnr()\<CR>:q\<CR>", 'x!')
+ call assert_equal(':', a)
+ call assert_equal([
+ \ 'WinLeave ' .. org_winid,
+ \ 'WinEnter ' .. s:cmd_winid,
+ \ 'BufLeave ' .. org_bufnr,
+ \ 'BufEnter ' .. s:cmd_bufnr,
+ \ 'CmdWinEnter ' .. s:cmd_winid,
+ \ 'CmdWinLeave ' .. s:cmd_winid,
+ \ 'BufLeave ' .. s:cmd_bufnr,
+ \ 'WinLeave ' .. s:cmd_winid,
+ \ 'WinEnter ' .. org_winid,
+ \ 'BufEnter ' .. org_bufnr,
+ \ ], s:seq)
+
+ au!
+ augroup END
+endfunc
+
+" Test error: "E135: *Filter* Autocommands must not change current buffer"
+func Test_cmd_bang_E135()
+ new
+ call setline(1, ['a', 'b', 'c', 'd'])
+ augroup test_cmd_filter_E135
+ au!
+ autocmd FilterReadPost * help
+ augroup END
+ call assert_fails('2,3!echo "x"', 'E135:')
+
+ augroup test_cmd_filter_E135
+ au!
+ augroup END
+ %bwipe!
+endfunc
+
func Test_verbosefile()
set verbosefile=Xlog
echomsg 'foo'
@@ -628,4 +767,66 @@ func Test_cmdline_overstrike()
let &encoding = encoding_save
endfunc
-set cpo&
+func Test_cmdwin_feedkeys()
+ " This should not generate E488
+ call feedkeys("q:\<CR>", 'x')
+endfunc
+
+func Test_buffers_lastused()
+ " check that buffers are sorted by time when wildmode has lastused
+ edit bufc " oldest
+
+ sleep 1200m
+ enew
+ edit bufa " middle
+
+ sleep 1200m
+ enew
+ edit bufb " newest
+
+ enew
+
+ call assert_equal(['bufc', 'bufa', 'bufb'],
+ \ getcompletion('', 'buffer'))
+
+ let save_wildmode = &wildmode
+ set wildmode=full:lastused
+
+ let cap = "\<c-r>=execute('let X=getcmdline()')\<cr>"
+ call feedkeys(":b \<tab>" .. cap .. "\<esc>", 'xt')
+ call assert_equal('b bufb', X)
+ call feedkeys(":b \<tab>\<tab>" .. cap .. "\<esc>", 'xt')
+ call assert_equal('b bufa', X)
+ call feedkeys(":b \<tab>\<tab>\<tab>" .. cap .. "\<esc>", 'xt')
+ call assert_equal('b bufc', X)
+ enew
+
+ sleep 1200m
+ edit other
+ call feedkeys(":b \<tab>" .. cap .. "\<esc>", 'xt')
+ call assert_equal('b bufb', X)
+ call feedkeys(":b \<tab>\<tab>" .. cap .. "\<esc>", 'xt')
+ call assert_equal('b bufa', X)
+ call feedkeys(":b \<tab>\<tab>\<tab>" .. cap .. "\<esc>", 'xt')
+ call assert_equal('b bufc', X)
+ enew
+
+ let &wildmode = save_wildmode
+
+ bwipeout bufa
+ bwipeout bufb
+ bwipeout bufc
+endfunc
+
+" test that ";" works to find a match at the start of the first line
+func Test_zero_line_search()
+ new
+ call setline(1, ["1, pattern", "2, ", "3, pattern"])
+ call cursor(1,1)
+ 0;/pattern/d
+ call assert_equal(["2, ", "3, pattern"], getline(1,'$'))
+ q!
+endfunc
+
+
+" vim: shiftwidth=2 sts=2 expandtab " vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_compiler.vim b/src/nvim/testdir/test_compiler.vim
index 46c14d8bc3..6bb602717f 100644
--- a/src/nvim/testdir/test_compiler.vim
+++ b/src/nvim/testdir/test_compiler.vim
@@ -10,6 +10,10 @@ func Test_compiler()
unlet $LANG
endif
+ " %:S does not work properly with 'shellslash' set
+ let save_shellslash = &shellslash
+ set noshellslash
+
e Xfoo.pl
compiler perl
call assert_equal('perl', b:current_compiler)
@@ -24,18 +28,21 @@ func Test_compiler()
w!
call feedkeys(":make\<CR>\<CR>", 'tx')
let a=execute('clist')
- call assert_match("\n 1 Xfoo.pl:3: Global symbol \"\$foo\" "
- \ . "requires explicit package name", a)
+ call assert_match('\n \d\+ Xfoo.pl:3: Global symbol "$foo" '
+ \ . 'requires explicit package name', a)
+
+ let &shellslash = save_shellslash
call delete('Xfoo.pl')
bw!
endfunc
func Test_compiler_without_arg()
- let a=split(execute('compiler'))
- call assert_match('^.*runtime/compiler/ant.vim$', a[0])
- call assert_match('^.*runtime/compiler/bcc.vim$', a[1])
- call assert_match('^.*runtime/compiler/xmlwf.vim$', a[-1])
+ let runtime = substitute($VIMRUNTIME, '\\', '/', 'g')
+ let a = split(execute('compiler'))
+ call assert_match(runtime .. '/compiler/ant.vim$', a[0])
+ call assert_match(runtime .. '/compiler/bcc.vim$', a[1])
+ call assert_match(runtime .. '/compiler/xmlwf.vim$', a[-1])
endfunc
func Test_compiler_completion()
diff --git a/src/nvim/testdir/test_const.vim b/src/nvim/testdir/test_const.vim
index 06062c5e58..eaf200e9bb 100644
--- a/src/nvim/testdir/test_const.vim
+++ b/src/nvim/testdir/test_const.vim
@@ -176,6 +176,26 @@ func Test_cannot_modify_existing_variable()
call assert_fails('const [i2, f2, s2] = [1, 1.1, "vim"]', 'E995:')
endfunc
+func Test_const_with_condition()
+ const x = 0
+ if 0 | const x = 1 | endif
+ call assert_equal(0, x)
+endfunc
+
+func Test_lockvar()
+ let x = 'hello'
+ lockvar x
+ call assert_fails('let x = "there"', 'E741')
+ if 0 | unlockvar x | endif
+ call assert_fails('let x = "there"', 'E741')
+ unlockvar x
+ let x = 'there'
+
+ if 0 | lockvar x | endif
+ let x = 'again'
+endfunc
+
+
func Test_const_with_index_access()
let l = [1, 2, 3]
call assert_fails('const l[0] = 4', 'E996:')
diff --git a/src/nvim/testdir/test_cursor_func.vim b/src/nvim/testdir/test_cursor_func.vim
index 037918fa31..2e190911b2 100644
--- a/src/nvim/testdir/test_cursor_func.vim
+++ b/src/nvim/testdir/test_cursor_func.vim
@@ -66,6 +66,7 @@ func Test_curswant_with_cursorline()
endfunc
func Test_screenpos()
+ throw 'skipped: TODO: '
rightbelow new
rightbelow 20vsplit
call setline(1, ["\tsome text", "long wrapping line here", "next line"])
@@ -92,3 +93,18 @@ func Test_screenpos()
close
bwipe!
endfunc
+
+func Test_screenpos_number()
+ rightbelow new
+ rightbelow 73vsplit
+ call setline (1, repeat('x', 66))
+ setlocal number
+ redraw
+ let winid = win_getid()
+ let [winrow, wincol] = win_screenpos(winid)
+ let pos = screenpos(winid, 1, 66)
+ call assert_equal(winrow, pos.row)
+ call assert_equal(wincol + 66 + 3, pos.col)
+ close
+ bwipe!
+endfunc
diff --git a/src/nvim/testdir/test_debugger.vim b/src/nvim/testdir/test_debugger.vim
index 3ef460b4fe..811717208e 100644
--- a/src/nvim/testdir/test_debugger.vim
+++ b/src/nvim/testdir/test_debugger.vim
@@ -1,7 +1,7 @@
" Tests for the Vim script debug commands
source shared.vim
-" source screendump.vim
+source screendump.vim
" Run a Vim debugger command
" If the expected output argument is supplied, then check for it.
@@ -26,27 +26,29 @@ func Test_Debugger()
endif
" Create a Vim script with some functions
- call writefile([
- \ 'func Foo()',
- \ ' let var1 = 1',
- \ ' let var2 = Bar(var1) + 9',
- \ ' return var2',
- \ 'endfunc',
- \ 'func Bar(var)',
- \ ' let var1 = 2 + a:var',
- \ ' let var2 = Bazz(var1) + 4',
- \ ' return var2',
- \ 'endfunc',
- \ 'func Bazz(var)',
- \ ' try',
- \ ' let var1 = 3 + a:var',
- \ ' let var3 = "another var"',
- \ ' let var3 = "value2"',
- \ ' catch',
- \ ' let var4 = "exception"',
- \ ' endtry',
- \ ' return var1',
- \ 'endfunc'], 'Xtest.vim')
+ let lines =<< trim END
+ func Foo()
+ let var1 = 1
+ let var2 = Bar(var1) + 9
+ return var2
+ endfunc
+ func Bar(var)
+ let var1 = 2 + a:var
+ let var2 = Bazz(var1) + 4
+ return var2
+ endfunc
+ func Bazz(var)
+ try
+ let var1 = 3 + a:var
+ let var3 = "another var"
+ let var3 = "value2"
+ catch
+ let var4 = "exception"
+ endtry
+ return var1
+ endfunc
+ END
+ call writefile(lines, 'Xtest.vim')
" Start Vim in a terminal
let buf = RunVimInTerminal('-S Xtest.vim', {})
@@ -294,11 +296,13 @@ func Test_Debugger()
" Tests for :breakadd file and :breakadd here
" Breakpoints should be set before sourcing the file
- call writefile([
- \ 'let var1 = 10',
- \ 'let var2 = 20',
- \ 'let var3 = 30',
- \ 'let var4 = 40'], 'Xtest.vim')
+ let lines =<< trim END
+ let var1 = 10
+ let var2 = 20
+ let var3 = 30
+ let var4 = 40
+ END
+ call writefile(lines, 'Xtest.vim')
" Start Vim in a terminal
let buf = RunVimInTerminal('Xtest.vim', {})
diff --git a/src/nvim/testdir/test_diffmode.vim b/src/nvim/testdir/test_diffmode.vim
index 57b19aa817..a1f1dd3bab 100644
--- a/src/nvim/testdir/test_diffmode.vim
+++ b/src/nvim/testdir/test_diffmode.vim
@@ -1,4 +1,7 @@
" Tests for diff mode
+source shared.vim
+source screendump.vim
+source check.vim
func Test_diff_fold_sync()
enew!
@@ -67,7 +70,7 @@ func Common_vert_split()
set foldmethod=marker foldcolumn=4
call assert_equal(0, &diff)
call assert_equal('marker', &foldmethod)
- call assert_equal(4, &foldcolumn)
+ call assert_equal('4', &foldcolumn)
call assert_equal(0, &scrollbind)
call assert_equal(0, &cursorbind)
call assert_equal(1, &wrap)
@@ -76,7 +79,7 @@ func Common_vert_split()
vert diffsplit Xtest2
call assert_equal(1, &diff)
call assert_equal('diff', &foldmethod)
- call assert_equal(2, &foldcolumn)
+ call assert_equal('2', &foldcolumn)
call assert_equal(1, &scrollbind)
call assert_equal(1, &cursorbind)
call assert_equal(0, &wrap)
@@ -142,7 +145,7 @@ func Common_vert_split()
1wincmd w
call assert_equal(0, &diff)
call assert_equal('marker', &foldmethod)
- call assert_equal(4, &foldcolumn)
+ call assert_equal('4', &foldcolumn)
call assert_equal(0, &scrollbind)
call assert_equal(0, &cursorbind)
call assert_equal(1, &wrap)
@@ -150,7 +153,7 @@ func Common_vert_split()
wincmd w
call assert_equal(0, &diff)
call assert_equal('marker', &foldmethod)
- call assert_equal(4, &foldcolumn)
+ call assert_equal('4', &foldcolumn)
call assert_equal(0, &scrollbind)
call assert_equal(0, &cursorbind)
call assert_equal(1, &wrap)
@@ -158,7 +161,7 @@ func Common_vert_split()
wincmd w
call assert_equal(0, &diff)
call assert_equal('marker', &foldmethod)
- call assert_equal(4, &foldcolumn)
+ call assert_equal('4', &foldcolumn)
call assert_equal(0, &scrollbind)
call assert_equal(0, &cursorbind)
call assert_equal(1, &wrap)
@@ -773,3 +776,78 @@ func Test_diff_of_diff()
call StopVimInTerminal(buf)
call delete('Xtest_diff_diff')
endfunc
+
+func CloseoffSetup()
+ enew
+ call setline(1, ['one', 'two', 'three'])
+ diffthis
+ new
+ call setline(1, ['one', 'tow', 'three'])
+ diffthis
+ call assert_equal(1, &diff)
+ only!
+endfunc
+
+func Test_diff_closeoff()
+ " "closeoff" included by default: last diff win gets 'diff' reset'
+ call CloseoffSetup()
+ call assert_equal(0, &diff)
+ enew!
+
+ " "closeoff" excluded: last diff win keeps 'diff' set'
+ set diffopt-=closeoff
+ call CloseoffSetup()
+ call assert_equal(1, &diff)
+ diffoff!
+ enew!
+endfunc
+
+func Test_diff_rnu()
+ CheckScreendump
+
+ let content =<< trim END
+ call setline(1, ['a', 'a', 'a', 'y', 'b', 'b', 'b', 'b', 'b'])
+ vnew
+ call setline(1, ['a', 'a', 'a', 'x', 'x', 'x', 'b', 'b', 'b', 'b', 'b'])
+ call setline(1, ['a', 'a', 'a', 'y', 'b', 'b', 'b', 'b', 'b'])
+ vnew
+ call setline(1, ['a', 'a', 'a', 'x', 'x', 'x', 'b', 'b', 'b', 'b', 'b'])
+ windo diffthis
+ setlocal number rnu foldcolumn=0
+ END
+ call writefile(content, 'Xtest_diff_rnu')
+ let buf = RunVimInTerminal('-S Xtest_diff_rnu', {})
+
+ call VerifyScreenDump(buf, 'Test_diff_rnu_01', {})
+
+ call term_sendkeys(buf, "j")
+ call VerifyScreenDump(buf, 'Test_diff_rnu_02', {})
+ call term_sendkeys(buf, "j")
+ call VerifyScreenDump(buf, 'Test_diff_rnu_03', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+ call delete('Xtest_diff_rnu')
+endfunc
+
+func Test_diff_and_scroll()
+ " this was causing an ml_get error
+ set ls=2
+ for i in range(winheight(0) * 2)
+ call setline(i, i < winheight(0) - 10 ? i : i + 10)
+ endfor
+ vnew
+ for i in range(winheight(0)*2 + 10)
+ call setline(i, i < winheight(0) - 10 ? 0 : i)
+ endfor
+ diffthis
+ wincmd p
+ diffthis
+ execute 'normal ' . winheight(0) . "\<C-d>"
+
+ bwipe!
+ bwipe!
+ set ls&
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_digraph.vim b/src/nvim/testdir/test_digraph.vim
index 62a5da33df..1792dcc00b 100644
--- a/src/nvim/testdir/test_digraph.vim
+++ b/src/nvim/testdir/test_digraph.vim
@@ -433,6 +433,18 @@ func Test_digraphs_output()
call assert_equal('Z% Ж 1046', matchstr(out, '\C\<Z%\D*1046\>'))
call assert_equal('u- Å« 363', matchstr(out, '\C\<u-\D*363\>'))
call assert_equal('SH ^A 1', matchstr(out, '\C\<SH\D*1\>'))
+ call assert_notmatch('Latin supplement', out)
+
+ let out_bang_without_custom = execute(':digraph!')
+ digraph lt 60
+ let out_bang_with_custom = execute(':digraph!')
+ call assert_notmatch('lt', out_bang_without_custom)
+ call assert_match("^\n"
+ \ .. "NU ^@ 10 .*\n"
+ \ .. "Latin supplement\n"
+ \ .. "!I ¡ 161 .*\n"
+ \ .. ".*\n"
+ \ .. 'Custom\n.*\<lt < 60\>', out_bang_with_custom)
bw!
endfunc
@@ -465,4 +477,17 @@ func Test_show_digraph()
bwipe!
endfunc
+func Test_show_digraph_cp1251()
+ throw 'skipped: Nvim supports ''utf8'' encoding only'
+ if !has('multi_byte')
+ return
+ endif
+ new
+ set encoding=cp1251
+ call Put_Dig("='")
+ call assert_equal("\n<\xfa> <|z> <M-z> 250, Hex fa, Oct 372, Digr ='", execute('ascii'))
+ set encoding=utf-8
+ bwipe!
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_display.vim b/src/nvim/testdir/test_display.vim
index 5feb59eef1..1c2f5a05ff 100644
--- a/src/nvim/testdir/test_display.vim
+++ b/src/nvim/testdir/test_display.vim
@@ -69,3 +69,59 @@ func! Test_display_foldtext_mbyte()
set foldtext& fillchars& foldmethod& fdc&
bw!
endfunc
+
+func Test_display_listchars_precedes()
+ set fillchars+=vert:\|
+ call NewWindow(10, 10)
+ " Need a physical line that wraps over the complete
+ " window size
+ call append(0, repeat('aaa aaa aa ', 10))
+ call append(1, repeat(['bbb bbb bbb bbb'], 2))
+ " remove blank trailing line
+ $d
+ set list nowrap
+ call cursor(1, 1)
+ " move to end of line and scroll 2 characters back
+ norm! $2zh
+ let lines=ScreenLines([1,4], winwidth(0)+1)
+ let expect = [
+ \ " aaa aa $ |",
+ \ "$ |",
+ \ "$ |",
+ \ "~ |",
+ \ ]
+ call assert_equal(expect, lines)
+ set list listchars+=precedes:< nowrap
+ call cursor(1, 1)
+ " move to end of line and scroll 2 characters back
+ norm! $2zh
+ let lines = ScreenLines([1,4], winwidth(0)+1)
+ let expect = [
+ \ "<aaa aa $ |",
+ \ "< |",
+ \ "< |",
+ \ "~ |",
+ \ ]
+ call assert_equal(expect, lines)
+ set wrap
+ call cursor(1, 1)
+ " the complete line should be displayed in the window
+ norm! $
+
+ let lines = ScreenLines([1,10], winwidth(0)+1)
+ let expect = [
+ \ "<aaa aaa a|",
+ \ "a aaa aaa |",
+ \ "aa aaa aaa|",
+ \ " aa aaa aa|",
+ \ "a aa aaa a|",
+ \ "aa aa aaa |",
+ \ "aaa aa aaa|",
+ \ " aaa aa aa|",
+ \ "a aaa aa a|",
+ \ "aa aaa aa |",
+ \ ]
+ call assert_equal(expect, lines)
+ set list& listchars& wrap&
+ bw!
+endfunc
diff --git a/src/nvim/testdir/test_edit.vim b/src/nvim/testdir/test_edit.vim
index 98fa9a3c47..12d5d9790e 100644
--- a/src/nvim/testdir/test_edit.vim
+++ b/src/nvim/testdir/test_edit.vim
@@ -1439,7 +1439,7 @@ func Test_edit_alt()
call delete('XAltFile')
endfunc
-func Test_leave_insert_autocmd()
+func Test_edit_InsertLeave()
new
au InsertLeave * let g:did_au = 1
let g:did_au = 0
@@ -1469,6 +1469,21 @@ func Test_leave_insert_autocmd()
iunmap x
endfunc
+func Test_edit_InsertLeave_undo()
+ new XtestUndo
+ set undofile
+ au InsertLeave * wall
+ exe "normal ofoo\<Esc>"
+ call assert_equal(2, line('$'))
+ normal u
+ call assert_equal(1, line('$'))
+
+ bwipe!
+ au! InsertLeave
+ call delete('XtestUndo')
+ set undofile&
+endfunc
+
" Test for inserting characters using CTRL-V followed by a number.
func Test_edit_special_chars()
new
diff --git a/src/nvim/testdir/test_escaped_glob.vim b/src/nvim/testdir/test_escaped_glob.vim
index aad3a1e835..2bfd82c296 100644
--- a/src/nvim/testdir/test_escaped_glob.vim
+++ b/src/nvim/testdir/test_escaped_glob.vim
@@ -17,7 +17,7 @@ function Test_glob()
" Setting 'shell' to an invalid name causes a memory leak.
sandbox call assert_equal("", glob('Xxx\{'))
sandbox call assert_equal("", glob('Xxx\$'))
- w! Xxx{
+ w! Xxx\{
" } to fix highlighting
w! Xxx\$
sandbox call assert_equal("Xxx{", glob('Xxx\{'))
diff --git a/src/nvim/testdir/test_excmd.vim b/src/nvim/testdir/test_excmd.vim
index f5ce979208..4a027c3864 100644
--- a/src/nvim/testdir/test_excmd.vim
+++ b/src/nvim/testdir/test_excmd.vim
@@ -8,3 +8,35 @@ func Test_ex_delete()
.dl
call assert_equal(['a', 'c'], getline(1, 2))
endfunc
+
+func Test_buffers_lastused()
+ edit bufc " oldest
+
+ sleep 1200m
+ edit bufa " middle
+
+ sleep 1200m
+ edit bufb " newest
+
+ enew
+
+ let ls = split(execute('buffers t', 'silent!'), '\n')
+ let bufs = []
+ for line in ls
+ let bufs += [split(line, '"\s*')[1:2]]
+ endfor
+
+ let names = []
+ for buf in bufs
+ if buf[0] !=# '[No Name]'
+ let names += [buf[0]]
+ endif
+ endfor
+
+ call assert_equal(['bufb', 'bufa', 'bufc'], names)
+ call assert_match('[0-2] seconds ago', bufs[1][1])
+
+ bwipeout bufa
+ bwipeout bufb
+ bwipeout bufc
+endfunc
diff --git a/src/nvim/testdir/test_exit.vim b/src/nvim/testdir/test_exit.vim
index 8f02fd29e3..99a401d4a4 100644
--- a/src/nvim/testdir/test_exit.vim
+++ b/src/nvim/testdir/test_exit.vim
@@ -3,52 +3,78 @@
source shared.vim
func Test_exiting()
- let after = [
- \ 'au QuitPre * call writefile(["QuitPre"], "Xtestout")',
- \ 'au ExitPre * call writefile(["ExitPre"], "Xtestout", "a")',
- \ 'quit',
- \ ]
+ let after =<< trim [CODE]
+ au QuitPre * call writefile(["QuitPre"], "Xtestout")
+ au ExitPre * call writefile(["ExitPre"], "Xtestout", "a")
+ quit
+ [CODE]
+
if RunVim([], after, '')
call assert_equal(['QuitPre', 'ExitPre'], readfile('Xtestout'))
endif
call delete('Xtestout')
- let after = [
- \ 'au QuitPre * call writefile(["QuitPre"], "Xtestout")',
- \ 'au ExitPre * call writefile(["ExitPre"], "Xtestout", "a")',
- \ 'help',
- \ 'wincmd w',
- \ 'quit',
- \ ]
+ let after =<< trim [CODE]
+ au QuitPre * call writefile(["QuitPre"], "Xtestout")
+ au ExitPre * call writefile(["ExitPre"], "Xtestout", "a")
+ help
+ wincmd w
+ quit
+ [CODE]
+
if RunVim([], after, '')
call assert_equal(['QuitPre', 'ExitPre'], readfile('Xtestout'))
endif
call delete('Xtestout')
- let after = [
- \ 'au QuitPre * call writefile(["QuitPre"], "Xtestout")',
- \ 'au ExitPre * call writefile(["ExitPre"], "Xtestout", "a")',
- \ 'split',
- \ 'new',
- \ 'qall',
- \ ]
+ let after =<< trim [CODE]
+ au QuitPre * call writefile(["QuitPre"], "Xtestout")
+ au ExitPre * call writefile(["ExitPre"], "Xtestout", "a")
+ split
+ new
+ qall
+ [CODE]
+
if RunVim([], after, '')
call assert_equal(['QuitPre', 'ExitPre'], readfile('Xtestout'))
endif
call delete('Xtestout')
- let after = [
- \ 'au QuitPre * call writefile(["QuitPre"], "Xtestout", "a")',
- \ 'au ExitPre * call writefile(["ExitPre"], "Xtestout", "a")',
- \ 'augroup nasty',
- \ ' au ExitPre * split',
- \ 'augroup END',
- \ 'quit',
- \ 'augroup nasty',
- \ ' au! ExitPre',
- \ 'augroup END',
- \ 'quit',
- \ ]
+ " ExitPre autocommand splits the window, so that it's no longer the last one.
+ let after =<< trim [CODE]
+ au QuitPre * call writefile(["QuitPre"], "Xtestout", "a")
+ au ExitPre * call writefile(["ExitPre"], "Xtestout", "a")
+ augroup nasty
+ au ExitPre * split
+ augroup END
+ quit
+ augroup nasty
+ au! ExitPre
+ augroup END
+ quit
+ [CODE]
+
+ if RunVim([], after, '')
+ call assert_equal(['QuitPre', 'ExitPre', 'QuitPre', 'ExitPre'],
+ \ readfile('Xtestout'))
+ endif
+ call delete('Xtestout')
+
+ " ExitPre autocommand splits and closes the window, so that there is still
+ " one window but it's a different one.
+ let after =<< trim [CODE]
+ au QuitPre * call writefile(["QuitPre"], "Xtestout", "a")
+ au ExitPre * call writefile(["ExitPre"], "Xtestout", "a")
+ augroup nasty
+ au ExitPre * split | only
+ augroup END
+ quit
+ augroup nasty
+ au! ExitPre
+ augroup END
+ quit
+ [CODE]
+
if RunVim([], after, '')
call assert_equal(['QuitPre', 'ExitPre', 'QuitPre', 'ExitPre'],
\ readfile('Xtestout'))
diff --git a/src/nvim/testdir/test_expr.vim b/src/nvim/testdir/test_expr.vim
index 4f99625e73..264d8b000f 100644
--- a/src/nvim/testdir/test_expr.vim
+++ b/src/nvim/testdir/test_expr.vim
@@ -279,6 +279,9 @@ function Test_printf_misc()
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('1%', printf('%d%%', 1))
endfunc
@@ -472,6 +475,8 @@ func Test_funcref()
let OneByRef = funcref('One')
call assert_equal(2, OneByRef())
call assert_fails('echo funcref("{")', 'E475:')
+ let OneByRef = funcref("One", repeat(["foo"], 20))
+ call assert_fails('let OneByRef = funcref("One", repeat(["foo"], 21))', 'E118:')
endfunc
func Test_empty_concatenate()
diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim
index 7512d599b8..2e280417ae 100644
--- a/src/nvim/testdir/test_filetype.vim
+++ b/src/nvim/testdir/test_filetype.vim
@@ -54,6 +54,7 @@ let s:filename_checks = {
\ 'acedb': ['file.wrm'],
\ 'ada': ['file.adb', 'file.ads', 'file.ada', 'file.gpr'],
\ 'ahdl': ['file.tdf'],
+ \ 'aidl': ['file.aidl'],
\ 'alsaconf': ['.asoundrc', '/usr/share/alsa/alsa.conf', '/etc/asound.conf'],
\ 'aml': ['file.aml'],
\ 'ampl': ['file.run'],
@@ -79,6 +80,7 @@ let s:filename_checks = {
\ 'bib': ['file.bib'],
\ 'bindzone': ['named.root'],
\ 'blank': ['file.bl'],
+ \ 'bsdl': ['file.bsd', 'file.bsdl'],
\ 'bst': ['file.bst'],
\ 'bzr': ['bzr_log.any'],
\ 'c': ['enlightenment/file.cfg', 'file.qc', 'file.c'],
@@ -122,6 +124,7 @@ let s:filename_checks = {
\ 'cvs': ['cvs123'],
\ 'cvsrc': ['.cvsrc'],
\ 'cynpp': ['file.cyn'],
+ \ 'dart': ['file.dart', 'file.drt'],
\ 'datascript': ['file.ds'],
\ 'dcd': ['file.dcd'],
\ 'debcontrol': ['/debian/control'],
@@ -129,7 +132,7 @@ let s:filename_checks = {
\ 'def': ['file.def'],
\ 'denyhosts': ['denyhosts.conf'],
\ 'desc': ['file.desc'],
- \ 'desktop': ['file.desktop', '.directory'],
+ \ 'desktop': ['file.desktop', '.directory', 'file.directory'],
\ 'dictconf': ['dict.conf', '.dictrc'],
\ 'dictdconf': ['dictd.conf'],
\ 'diff': ['file.diff', 'file.rej'],
@@ -137,8 +140,8 @@ let s:filename_checks = {
\ 'dnsmasq': ['/etc/dnsmasq.conf'],
\ 'dockerfile': ['Dockerfile', 'file.Dockerfile'],
\ 'dosbatch': ['file.bat', 'file.sys'],
- \ 'dosini': ['.editorconfig', '/etc/yum.conf', 'file.ini'],
- \ 'dot': ['file.dot'],
+ \ 'dosini': ['.editorconfig', '/etc/pacman.conf', '/etc/yum.conf', 'file.ini'],
+ \ 'dot': ['file.dot', 'file.gv'],
\ 'dracula': ['file.drac', 'file.drc', 'filelvs', 'filelpe'],
\ 'dsl': ['file.dsl'],
\ 'dtd': ['file.dtd'],
@@ -148,7 +151,8 @@ let s:filename_checks = {
\ 'dylanlid': ['file.lid'],
\ 'ecd': ['file.ecd'],
\ 'edif': ['file.edf', 'file.edif', 'file.edo'],
- \ 'elinks': ['/etc/elinks.conf', '/.elinks/elinks.conf'],
+ \ 'elinks': ['elinks.conf'],
+ \ 'elm': ['file.elm'],
\ 'elmfilt': ['filter-rules'],
\ 'erlang': ['file.erl', 'file.hrl', 'file.yaws'],
\ 'eruby': ['file.erb', 'file.rhtml'],
@@ -201,6 +205,7 @@ let s:filename_checks = {
\ 'hex': ['file.hex', 'file.h32'],
\ 'hgcommit': ['hg-editor-file.txt'],
\ 'hog': ['file.hog', 'snort.conf', 'vision.conf'],
+ \ 'hollywood': ['file.hws'],
\ 'hostconf': ['/etc/host.conf'],
\ 'hostsaccess': ['/etc/hosts.allow', '/etc/hosts.deny'],
\ 'template': ['file.tmpl'],
@@ -221,7 +226,7 @@ let s:filename_checks = {
\ 'jam': ['file.jpl', 'file.jpr'],
\ 'java': ['file.java', 'file.jav'],
\ 'javacc': ['file.jj', 'file.jjt'],
- \ 'javascript': ['file.js', 'file.javascript', 'file.es', 'file.mjs'],
+ \ 'javascript': ['file.js', 'file.javascript', 'file.es', 'file.mjs', 'file.cjs'],
\ 'javascriptreact': ['file.jsx'],
\ 'jess': ['file.clp'],
\ 'jgraph': ['file.jgr'],
@@ -232,6 +237,7 @@ let s:filename_checks = {
\ 'kconfig': ['Kconfig', 'Kconfig.debug'],
\ 'kivy': ['file.kv'],
\ 'kix': ['file.kix'],
+ \ 'kotlin': ['file.kt', 'file.ktm', 'file.kts'],
\ 'kscript': ['file.ks'],
\ 'kwt': ['file.k'],
\ 'lace': ['file.ace', 'file.ACE'],
@@ -247,7 +253,7 @@ let s:filename_checks = {
\ 'lilo': ['lilo.conf'],
\ 'limits': ['/etc/limits', '/etc/anylimits.conf', '/etc/anylimits.d/file.conf'],
\ 'liquid': ['file.liquid'],
- \ 'lisp': ['sbclrc', '.sbclrc'],
+ \ 'lisp': ['file.lsp', 'file.lisp', 'file.el', 'file.cl', '.emacs', '.sawfishrc', 'sbclrc', '.sbclrc'],
\ 'lite': ['file.lite', 'file.lt'],
\ 'litestep': ['/LiteStep/any/file.rc'],
\ 'loginaccess': ['/etc/login.access'],
@@ -273,6 +279,7 @@ let s:filename_checks = {
\ 'mason': ['file.mason', 'file.mhtml', 'file.comp'],
\ 'master': ['file.mas', 'file.master'],
\ 'mel': ['file.mel'],
+ \ 'meson': ['meson.build', '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',
@@ -316,13 +323,14 @@ let s:filename_checks = {
\ 'openroad': ['file.or'],
\ 'ora': ['file.ora'],
\ 'pamconf': ['/etc/pam.conf'],
+ \ 'pamenv': ['/etc/security/pam_env.conf', '/home/user/.pam_environment'],
\ 'papp': ['file.papp', 'file.pxml', 'file.pxsl'],
\ 'pascal': ['file.pas', 'file.dpr'],
\ 'passwd': ['any/etc/passwd', 'any/etc/passwd-', 'any/etc/passwd.edit', 'any/etc/shadow', 'any/etc/shadow-', 'any/etc/shadow.edit', 'any/var/backups/passwd.bak', 'any/var/backups/shadow.bak'],
\ 'pccts': ['file.g'],
\ 'pdf': ['file.pdf'],
\ 'perl': ['file.plx', 'file.al', 'file.psgi', 'gitolite.rc', '.gitolite.rc', 'example.gitolite.rc'],
- \ 'perl6': ['file.p6', 'file.pm6', 'file.pl6'],
+ \ 'perl6': ['file.p6', 'file.pm6', 'file.pl6', 'file.raku', 'file.rakumod'],
\ 'pf': ['pf.conf'],
\ 'pfmain': ['main.cf'],
\ 'php': ['file.php', 'file.php9', 'file.phtml', 'file.ctp'],
@@ -353,7 +361,7 @@ let s:filename_checks = {
\ 'protocols': ['/etc/protocols'],
\ 'psf': ['file.psf'],
\ 'pyrex': ['file.pyx', 'file.pxd'],
- \ 'python': ['file.py', 'file.pyw', '.pythonstartup', '.pythonrc', 'file.ptl', 'file.pyi'],
+ \ 'python': ['file.py', 'file.pyw', '.pythonstartup', '.pythonrc', 'file.ptl', 'file.pyi', 'SConstruct'],
\ 'quake': ['anybaseq2/file.cfg', 'anyid1/file.cfg', 'quake3/file.cfg'],
\ 'radiance': ['file.rad', 'file.mat'],
\ 'ratpoison': ['.ratpoisonrc', 'ratpoisonrc'],
@@ -361,6 +369,7 @@ let s:filename_checks = {
\ 'rcs': ['file,v'],
\ 'readline': ['.inputrc', 'inputrc'],
\ 'remind': ['.reminders', 'file.remind', 'file.rem'],
+ \ 'rego': ['file.rego'],
\ 'resolv': ['resolv.conf'],
\ 'reva': ['file.frt'],
\ 'rexx': ['file.rex', 'file.orx', 'file.rxo', 'file.rxj', 'file.jrexx', 'file.rexxj', 'file.rexx', 'file.testGroup', 'file.testUnit'],
@@ -419,16 +428,19 @@ let s:filename_checks = {
\ 'sqr': ['file.sqr', 'file.sqi'],
\ 'squid': ['squid.conf'],
\ 'srec': ['file.s19', 'file.s28', 'file.s37', 'file.mot', 'file.srec'],
- \ 'sshconfig': ['ssh_config', '/.ssh/config'],
- \ 'sshdconfig': ['sshd_config'],
+ \ 'sshconfig': ['ssh_config', '/.ssh/config', '/etc/ssh/ssh_config.d/file.conf', 'any/etc/ssh/ssh_config.d/file.conf'],
+ \ 'sshdconfig': ['sshd_config', '/etc/ssh/sshd_config.d/file.conf', 'any/etc/ssh/sshd_config.d/file.conf'],
\ 'st': ['file.st'],
\ 'stata': ['file.ado', 'file.do', 'file.imata', 'file.mata'],
\ 'stp': ['file.stp'],
\ 'sudoers': ['any/etc/sudoers', 'sudoers.tmp'],
\ 'svg': ['file.svg'],
\ 'svn': ['svn-commitfile.tmp'],
+ \ 'swift': ['file.swift'],
+ \ 'swiftgyb': ['file.swift.gyb'],
+ \ 'sil': ['file.sil'],
\ 'sysctl': ['/etc/sysctl.conf', '/etc/sysctl.d/file.conf'],
- \ 'systemd': ['any/systemd/file.automount', 'any/systemd/file.mount', 'any/systemd/file.path', 'any/systemd/file.service', 'any/systemd/file.socket', 'any/systemd/file.swap', 'any/systemd/file.target', 'any/systemd/file.timer', '/etc/systemd/system/some.d/file.conf', '/etc/systemd/system/some.d/.#file'],
+ \ 'systemd': ['any/systemd/file.automount', 'any/systemd/file.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'],
\ 'systemverilog': ['file.sv', 'file.svh'],
\ 'tags': ['tags'],
\ 'tak': ['file.tak'],
@@ -472,7 +484,7 @@ let s:filename_checks = {
\ 'verilog': ['file.v'],
\ 'verilogams': ['file.va', 'file.vams'],
\ 'vgrindefs': ['vgrindefs'],
- \ 'vhdl': ['file.hdl', 'file.vhd', 'file.vhdl', 'file.vbe', 'file.vst', 'file.vhdl_123'],
+ \ 'vhdl': ['file.hdl', 'file.vhd', 'file.vhdl', 'file.vbe', 'file.vst', 'file.vhdl_123', 'file.vho'],
\ 'vim': ['file.vim', 'file.vba', '.exrc', '_exrc'],
\ 'viminfo': ['.viminfo', '_viminfo'],
\ 'vmasm': ['file.mar'],
@@ -592,11 +604,22 @@ let s:script_checks = {
\ 'haskell': [['#!/path/haskell']],
\ 'cpp': [['// Standard iostream objects -*- C++ -*-'],
\ ['// -*- C++ -*-']],
+ \ 'yaml': [['%YAML 1.2']],
\ }
-func Test_script_detection()
+" Various forms of "env" optional arguments.
+let s:script_env_checks = {
+ \ 'perl': [['#!/usr/bin/env VAR=val perl']],
+ \ 'scala': [['#!/usr/bin/env VAR=val VVAR=vval scala']],
+ \ 'awk': [['#!/usr/bin/env VAR=val -i awk']],
+ \ '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']],
+ \ }
+
+func Run_script_detection(test_dict)
filetype on
- for [ft, files] in items(s:script_checks)
+ for [ft, files] in items(a:test_dict)
for file in files
call writefile(file, 'Xtest')
split Xtest
@@ -608,7 +631,32 @@ func Test_script_detection()
filetype off
endfunc
+func Test_script_detection()
+ call Run_script_detection(s:script_checks)
+ call Run_script_detection(s:script_env_checks)
+endfunc
+
func Test_setfiletype_completion()
call feedkeys(":setfiletype java\<C-A>\<C-B>\"\<CR>", 'tx')
call assert_equal('"setfiletype java javacc javascript javascriptreact', @:)
endfunc
+
+func Test_hook_file()
+ filetype on
+
+ call writefile(['[Trigger]', 'this is pacman config'], 'Xfile.hook')
+ split Xfile.hook
+ call assert_equal('dosini', &filetype)
+ bwipe!
+
+ call writefile(['not pacman'], 'Xfile.hook')
+ split Xfile.hook
+ call assert_notequal('dosini', &filetype)
+ bwipe!
+
+ call delete('Xfile.hook')
+ filetype off
+endfunc
+
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_flatten.vim b/src/nvim/testdir/test_flatten.vim
new file mode 100644
index 0000000000..99086611e1
--- /dev/null
+++ b/src/nvim/testdir/test_flatten.vim
@@ -0,0 +1,81 @@
+" Test for flatting list.
+func Test_flatten()
+ call assert_fails('call flatten(1)', 'E686:')
+ call assert_fails('call flatten({})', 'E686:')
+ call assert_fails('call flatten("string")', 'E686:')
+ call assert_fails('call flatten([], [])', 'E745:')
+ call assert_fails('call flatten([], -1)', 'E900: maxdepth')
+
+ call assert_equal([], flatten([]))
+ call assert_equal([], flatten([[]]))
+ call assert_equal([], flatten([[[]]]))
+
+ call assert_equal([1, 2, 3], flatten([1, 2, 3]))
+ call assert_equal([1, 2, 3], flatten([[1], 2, 3]))
+ call assert_equal([1, 2, 3], flatten([1, [2], 3]))
+ call assert_equal([1, 2, 3], flatten([1, 2, [3]]))
+ call assert_equal([1, 2, 3], flatten([[1], [2], 3]))
+ call assert_equal([1, 2, 3], flatten([1, [2], [3]]))
+ call assert_equal([1, 2, 3], flatten([[1], 2, [3]]))
+ call assert_equal([1, 2, 3], flatten([[1], [2], [3]]))
+
+ call assert_equal([1, 2, 3], flatten([[1, 2, 3], []]))
+ call assert_equal([1, 2, 3], flatten([[], [1, 2, 3]]))
+ call assert_equal([1, 2, 3], flatten([[1, 2], [], [3]]))
+ call assert_equal([1, 2, 3], flatten([[], [1, 2, 3], []]))
+ call assert_equal([1, 2, 3, 4], flatten(range(1, 4)))
+
+ " example in the help
+ call assert_equal([1, 2, 3, 4, 5], flatten([1, [2, [3, 4]], 5]))
+ call assert_equal([1, 2, [3, 4], 5], flatten([1, [2, [3, 4]], 5], 1))
+
+ call assert_equal([0, [1], 2, [3], 4], flatten([[0, [1]], 2, [[3], 4]], 1))
+ call assert_equal([1, 2, 3], flatten([[[[1]]], [2], [3]], 3))
+ call assert_equal([[1], [2], [3]], flatten([[[1], [2], [3]]], 1))
+ call assert_equal([[1]], flatten([[1]], 0))
+
+ " Make it flatten if the given maxdepth is larger than actual depth.
+ call assert_equal([1, 2, 3], flatten([[1, 2, 3]], 1))
+ call assert_equal([1, 2, 3], flatten([[1, 2, 3]], 2))
+
+ let l:list = [[1], [2], [3]]
+ call assert_equal([1, 2, 3], flatten(l:list))
+ call assert_equal([1, 2, 3], l:list)
+
+ " Tests for checking reference counter works well.
+ let l:x = {'foo': 'bar'}
+ call assert_equal([1, 2, l:x, 3], flatten([1, [2, l:x], 3]))
+ call test_garbagecollect_now()
+ call assert_equal('bar', l:x.foo)
+
+ let l:list = [[1], [2], [3]]
+ call assert_equal([1, 2, 3], flatten(l:list))
+ call test_garbagecollect_now()
+ call assert_equal([1, 2, 3], l:list)
+
+ " Tests for checking circular reference list can be flatten.
+ let l:x = [1]
+ let l:y = [x]
+ let l:z = flatten(l:y)
+ call assert_equal([1], l:z)
+ call test_garbagecollect_now()
+ let l:x[0] = 2
+ call assert_equal([2], l:x)
+ call assert_equal([1], l:z) " NOTE: primitive types are copied.
+ call assert_equal([1], l:y)
+
+ let l:x = [2]
+ let l:y = [1, [l:x], 3] " [1, [[2]], 3]
+ let l:z = flatten(l:y, 1)
+ call assert_equal([1, [2], 3], l:z)
+ let l:x[0] = 9
+ call assert_equal([1, [9], 3], l:z) " Reference to l:x is kept.
+ call assert_equal([1, [9], 3], l:y)
+
+ let l:x = [1]
+ let l:y = [2]
+ call add(x, y) " l:x = [1, [2]]
+ 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)
+endfunc
diff --git a/src/nvim/testdir/test_fnamemodify.vim b/src/nvim/testdir/test_fnamemodify.vim
new file mode 100644
index 0000000000..116d23ba88
--- /dev/null
+++ b/src/nvim/testdir/test_fnamemodify.vim
@@ -0,0 +1,75 @@
+" Test filename modifiers.
+
+func Test_fnamemodify()
+ let save_home = $HOME
+ let save_shell = &shell
+ let $HOME = fnamemodify('.', ':p:h:h')
+ set shell=sh
+
+ call assert_equal('/', fnamemodify('.', ':p')[-1:])
+ call assert_equal('r', fnamemodify('.', ':p:h')[-1:])
+ call assert_equal('t', fnamemodify('test.out', ':p')[-1:])
+ call assert_equal('test.out', fnamemodify('test.out', ':.'))
+ call assert_equal('a', fnamemodify('../testdir/a', ':.'))
+ call assert_equal('~/testdir/test.out', fnamemodify('test.out', ':~'))
+ call assert_equal('~/testdir/a', fnamemodify('../testdir/a', ':~'))
+ call assert_equal('a', fnamemodify('../testdir/a', ':t'))
+ call assert_equal('', fnamemodify('.', ':p:t'))
+ call assert_equal('test.out', fnamemodify('test.out', ':p:t'))
+ call assert_equal('out', fnamemodify('test.out', ':p:e'))
+ call assert_equal('out', fnamemodify('test.out', ':p:t:e'))
+ call assert_equal('abc.fb2.tar', fnamemodify('abc.fb2.tar.gz', ':r'))
+ call assert_equal('abc.fb2', fnamemodify('abc.fb2.tar.gz', ':r:r'))
+ call assert_equal('abc', fnamemodify('abc.fb2.tar.gz', ':r:r:r'))
+ call assert_equal('testdir/abc.fb2', substitute(fnamemodify('abc.fb2.tar.gz', ':p:r:r'), '.*\(testdir/.*\)', '\1', ''))
+ call assert_equal('gz', fnamemodify('abc.fb2.tar.gz', ':e'))
+ call assert_equal('tar.gz', fnamemodify('abc.fb2.tar.gz', ':e:e'))
+ call assert_equal('fb2.tar.gz', fnamemodify('abc.fb2.tar.gz', ':e:e:e'))
+ call assert_equal('fb2.tar.gz', fnamemodify('abc.fb2.tar.gz', ':e:e:e:e'))
+ call assert_equal('tar', fnamemodify('abc.fb2.tar.gz', ':e:e:r'))
+
+ call assert_equal('''abc def''', fnamemodify('abc def', ':S'))
+ call assert_equal('''abc" "def''', fnamemodify('abc" "def', ':S'))
+ call assert_equal('''abc"%"def''', fnamemodify('abc"%"def', ':S'))
+ call assert_equal('''abc''\'''' ''\''''def''', fnamemodify('abc'' ''def', ':S'))
+ call assert_equal('''abc''\''''%''\''''def''', fnamemodify('abc''%''def', ':S'))
+ sp test_alot.vim
+ call assert_equal(expand('%:r:S'), shellescape(expand('%:r')))
+ call assert_equal('test_alot,''test_alot'',test_alot.vim', join([expand('%:r'), expand('%:r:S'), expand('%')], ','))
+ quit
+
+ call assert_equal("'abc\ndef'", fnamemodify("abc\ndef", ':S'))
+ set shell=tcsh
+ call assert_equal("'abc\\\ndef'", fnamemodify("abc\ndef", ':S'))
+
+ let $HOME = save_home
+ let &shell = save_shell
+endfunc
+
+func Test_fnamemodify_er()
+ call assert_equal("with", fnamemodify("path/to/file.with.extensions", ':e:e:r:r'))
+
+ call assert_equal('c', fnamemodify('a.c', ':e'))
+ call assert_equal('c', fnamemodify('a.c', ':e:e'))
+ call assert_equal('c', fnamemodify('a.c', ':e:e:r'))
+ call assert_equal('c', fnamemodify('a.c', ':e:e:r:r'))
+
+ call assert_equal('rb', fnamemodify('a.spec.rb', ':e:r'))
+ call assert_equal('rb', fnamemodify('a.spec.rb', ':e:r'))
+ call assert_equal('spec.rb', fnamemodify('a.spec.rb', ':e:e'))
+ call assert_equal('spec', fnamemodify('a.spec.rb', ':e:e:r'))
+ call assert_equal('spec', fnamemodify('a.spec.rb', ':e:e:r:r'))
+ call assert_equal('spec', fnamemodify('a.b.spec.rb', ':e:e:r'))
+ call assert_equal('b.spec', fnamemodify('a.b.spec.rb', ':e:e:e:r'))
+ call assert_equal('b', fnamemodify('a.b.spec.rb', ':e:e:e:r:r'))
+
+ call assert_equal('spec', fnamemodify('a.b.spec.rb', ':r:e'))
+ call assert_equal('b', fnamemodify('a.b.spec.rb', ':r:r:e'))
+
+ call assert_equal('c', fnamemodify('a.b.c.d.e', ':r:r:e'))
+ call assert_equal('b.c', fnamemodify('a.b.c.d.e', ':r:r:e:e'))
+
+ " :e never includes the whole filename, so "a.b":e:e:e --> "b"
+ call assert_equal('b.c', fnamemodify('a.b.c.d.e', ':r:r:e:e:e'))
+ call assert_equal('b.c', fnamemodify('a.b.c.d.e', ':r:r:e:e:e:e'))
+endfunc
diff --git a/src/nvim/testdir/test_fold.vim b/src/nvim/testdir/test_fold.vim
index 3cb42579be..692f6e4780 100644
--- a/src/nvim/testdir/test_fold.vim
+++ b/src/nvim/testdir/test_fold.vim
@@ -1,6 +1,7 @@
" Test for folding
source view_util.vim
+source screendump.vim
func PrepIndent(arg)
return [a:arg] + repeat(["\t".a:arg], 5)
@@ -520,17 +521,18 @@ func Test_fold_create_marker_in_C()
set fdm=marker fdl=9
set filetype=c
- let content = [
- \ '/*',
- \ ' * comment',
- \ ' * ',
- \ ' *',
- \ ' */',
- \ 'int f(int* p) {',
- \ ' *p = 3;',
- \ ' return 0;',
- \ '}'
- \]
+ let content =<< trim [CODE]
+ /*
+ * comment
+ *
+ *
+ */
+ int f(int* p) {
+ *p = 3;
+ return 0;
+ }
+ [CODE]
+
for c in range(len(content) - 1)
bw!
call append(0, content)
@@ -756,3 +758,40 @@ func Test_fold_delete_with_marker()
bwipe!
bwipe!
endfunc
+
+func Test_fold_delete_with_marker_and_whichwrap()
+ new
+ let content1 = ['']
+ let content2 = ['folded line 1 "{{{1', ' test', ' test2', ' test3', '', 'folded line 2 "{{{1', ' test', ' test2', ' test3']
+ call setline(1, content1 + content2)
+ set fdm=marker ww+=l
+ normal! x
+ call assert_equal(content2, getline(1, '$'))
+ set fdm& ww&
+ bwipe!
+endfunc
+
+func Test_fold_delete_first_line()
+ new
+ call setline(1, [
+ \ '" x {{{1',
+ \ '" a',
+ \ '" aa',
+ \ '" x {{{1',
+ \ '" b',
+ \ '" bb',
+ \ '" x {{{1',
+ \ '" c',
+ \ '" cc',
+ \ ])
+ set foldmethod=marker
+ 1
+ normal dj
+ call assert_equal([
+ \ '" x {{{1',
+ \ '" c',
+ \ '" cc',
+ \ ], getline(1,'$'))
+ bwipe!
+ set foldmethod&
+endfunc
diff --git a/src/nvim/testdir/test_functions.vim b/src/nvim/testdir/test_functions.vim
index a36c51f56f..51689db9c4 100644
--- a/src/nvim/testdir/test_functions.vim
+++ b/src/nvim/testdir/test_functions.vim
@@ -186,6 +186,32 @@ func Test_strftime()
call assert_fails('call strftime([])', 'E730:')
call assert_fails('call strftime("%Y", [])', 'E745:')
+
+ " Check that the time changes after we change the timezone
+ " Save previous timezone value, if any
+ if exists('$TZ')
+ let tz = $TZ
+ endif
+
+ " Force EST and then UTC, save the current hour (24-hour clock) for each
+ let $TZ = 'EST' | let est = strftime('%H')
+ let $TZ = 'UTC' | let utc = strftime('%H')
+
+ " Those hours should be two bytes long, and should not be the same; if they
+ " are, a tzset(3) call may have failed somewhere
+ call assert_equal(strlen(est), 2)
+ call assert_equal(strlen(utc), 2)
+ " TODO: this fails on MS-Windows
+ if has('unix')
+ call assert_notequal(est, utc)
+ endif
+
+ " If we cached a timezone value, put it back, otherwise clear it
+ if exists('tz')
+ let $TZ = tz
+ else
+ unlet $TZ
+ endif
endfunc
func Test_resolve()
@@ -640,6 +666,16 @@ func Test_getbufvar()
call assert_equal('iso-8859-2', getbufvar(bufnr('%'), '&fenc'))
close
+ " Get the b: dict.
+ let b:testvar = 'one'
+ new
+ let b:testvar = 'two'
+ let thebuf = bufnr()
+ wincmd w
+ call assert_equal('two', getbufvar(thebuf, 'testvar'))
+ call assert_equal('two', getbufvar(thebuf, '').testvar)
+ bwipe!
+
set fileformats&
endfunc
@@ -1132,6 +1168,13 @@ func Test_reg_executing_and_recording()
" :normal command saves and restores reg_executing
let s:reg_stat = ''
+ let @q = ":call TestFunc()\<CR>:call s:save_reg_stat()\<CR>"
+ func TestFunc() abort
+ normal! ia
+ endfunc
+ call feedkeys("@q", 'xt')
+ call assert_equal(':q', s:reg_stat)
+ delfunc TestFunc
" getchar() command saves and restores reg_executing
map W :call TestFunc()<CR>
@@ -1264,3 +1307,33 @@ func Test_bufadd_bufload()
bwipe otherName
call assert_equal(0, bufexists('someName'))
endfunc
+
+func Test_readdir()
+ call mkdir('Xdir')
+ call writefile([], 'Xdir/foo.txt')
+ call writefile([], 'Xdir/bar.txt')
+ call mkdir('Xdir/dir')
+
+ " All results
+ let files = readdir('Xdir')
+ call assert_equal(['bar.txt', 'dir', 'foo.txt'], sort(files))
+
+ " Only results containing "f"
+ let files = readdir('Xdir', { x -> stridx(x, 'f') !=- 1 })
+ call assert_equal(['foo.txt'], sort(files))
+
+ " Only .txt files
+ let files = readdir('Xdir', { x -> x =~ '.txt$' })
+ call assert_equal(['bar.txt', 'foo.txt'], sort(files))
+
+ " Only .txt files with string
+ let files = readdir('Xdir', 'v:val =~ ".txt$"')
+ call assert_equal(['bar.txt', 'foo.txt'], sort(files))
+
+ " Limit to 1 result.
+ let l = []
+ let files = readdir('Xdir', {x -> len(add(l, x)) == 2 ? -1 : 1})
+ call assert_equal(1, len(files))
+
+ call delete('Xdir', 'rf')
+endfunc
diff --git a/src/nvim/testdir/test_ga.vim b/src/nvim/testdir/test_ga.vim
index ea3d211aeb..87f1382342 100644
--- a/src/nvim/testdir/test_ga.vim
+++ b/src/nvim/testdir/test_ga.vim
@@ -24,6 +24,7 @@ func Test_ga_command()
" Test a few multi-bytes characters.
call assert_equal("\n<é> 233, Hex 00e9, Oct 351, Digr e'", Do_ga('é'))
call assert_equal("\n<ẻ> 7867, Hex 1ebb, Oct 17273, Digr e2", Do_ga('ẻ'))
+ call assert_equal("\n<\U00012345> 74565, Hex 00012345, Octal 221505", Do_ga("\U00012345"))
" Test with combining characters.
call assert_equal("\n<e> 101, Hex 65, Octal 145 < Ì> 769, Hex 0301, Octal 1401", Do_ga("e\u0301"))
diff --git a/src/nvim/testdir/test_gf.vim b/src/nvim/testdir/test_gf.vim
index accd21e9a3..4a4ffcefa1 100644
--- a/src/nvim/testdir/test_gf.vim
+++ b/src/nvim/testdir/test_gf.vim
@@ -58,6 +58,14 @@ func Test_gF()
call assert_equal('Xfile', bufname('%'))
call assert_equal(3, getcurpos()[1])
+ enew!
+ call setline(1, ['one', 'the Xfile line 2, and more', 'three'])
+ w! Xfile2
+ normal 2GfX
+ normal gF
+ call assert_equal('Xfile', bufname('%'))
+ call assert_equal(2, getcurpos()[1])
+
set isfname&
call delete('Xfile')
call delete('Xfile2')
@@ -99,3 +107,28 @@ func Test_gf()
call delete('Xtest1')
call delete('Xtestgf')
endfunc
+
+func Test_gf_visual()
+ call writefile([], "Xtest_gf_visual")
+ new
+ call setline(1, 'XXXtest_gf_visualXXX')
+ set hidden
+
+ " Visually select Xtest_gf_visual and use gf to go to that file
+ norm! ttvtXgf
+ call assert_equal('Xtest_gf_visual', bufname('%'))
+
+ bwipe!
+ call delete('Xtest_gf_visual')
+ set hidden&
+endfunc
+
+func Test_gf_error()
+ new
+ call assert_fails('normal gf', 'E446:')
+ call assert_fails('normal gF', 'E446:')
+ call setline(1, '/doesnotexist')
+ call assert_fails('normal gf', 'E447:')
+ call assert_fails('normal gF', 'E447:')
+ bwipe!
+endfunc
diff --git a/src/nvim/testdir/test_gn.vim b/src/nvim/testdir/test_gn.vim
index 5e74289b00..d41675be0c 100644
--- a/src/nvim/testdir/test_gn.vim
+++ b/src/nvim/testdir/test_gn.vim
@@ -129,6 +129,33 @@ func Test_gn_command()
call assert_equal([' nnoremap', '', 'match'], getline(1,'$'))
sil! %d_
+ " make sure it works correctly for one-char wide search items
+ call setline('.', ['abcdefghi'])
+ let @/ = 'a'
+ exe "norm! 0fhvhhgNgU"
+ call assert_equal(['ABCDEFGHi'], getline(1,'$'))
+ call setline('.', ['abcdefghi'])
+ let @/ = 'b'
+ " this gn wraps around the end of the file
+ exe "norm! 0fhvhhgngU"
+ call assert_equal(['aBCDEFGHi'], getline(1,'$'))
+ sil! %d _
+ call setline('.', ['abcdefghi'])
+ let @/ = 'f'
+ exe "norm! 0vllgngU"
+ call assert_equal(['ABCDEFghi'], getline(1,'$'))
+ sil! %d _
+ call setline('.', ['12345678'])
+ let @/ = '5'
+ norm! gg0f7vhhhhgnd
+ call assert_equal(['12348'], getline(1,'$'))
+ sil! %d _
+ call setline('.', ['12345678'])
+ let @/ = '5'
+ norm! gg0f2vf7gNd
+ call assert_equal(['1678'], getline(1,'$'))
+ sil! %d _
+
set wrapscan&vim
set belloff&vim
endfu
diff --git a/src/nvim/testdir/test_goto.vim b/src/nvim/testdir/test_goto.vim
index c0235b1707..19513b315a 100644
--- a/src/nvim/testdir/test_goto.vim
+++ b/src/nvim/testdir/test_goto.vim
@@ -15,262 +15,283 @@ func XTest_goto_decl(cmd, lines, line, col)
endfunc
func Test_gD()
- let lines = [
- \ 'int x;',
- \ '',
- \ 'int func(void)',
- \ '{',
- \ ' return x;',
- \ '}',
- \ ]
+ let lines =<< trim [CODE]
+ int x;
+
+ int func(void)
+ {
+ return x;
+ }
+ [CODE]
+
call XTest_goto_decl('gD', lines, 1, 5)
endfunc
func Test_gD_too()
- let lines = [
- \ 'Filename x;',
- \ '',
- \ 'int Filename',
- \ 'int func() {',
- \ ' Filename x;',
- \ ' return x;',
- \ ]
+ let lines =<< trim [CODE]
+ Filename x;
+
+ int Filename
+ int func() {
+ Filename x;
+ return x;
+ [CODE]
+
call XTest_goto_decl('gD', lines, 1, 10)
endfunc
func Test_gD_comment()
- let lines = [
- \ '/* int x; */',
- \ 'int x;',
- \ '',
- \ 'int func(void)',
- \ '{',
- \ ' return x;',
- \ '}',
- \ ]
+ let lines =<< trim [CODE]
+ /* int x; */
+ int x;
+
+ int func(void)
+ {
+ return x;
+ }
+ [CODE]
+
call XTest_goto_decl('gD', lines, 2, 5)
endfunc
func Test_gD_inline_comment()
- let lines = [
- \ 'int y /* , x */;',
- \ 'int x;',
- \ '',
- \ 'int func(void)',
- \ '{',
- \ ' return x;',
- \ '}',
- \ ]
+ let lines =<< trim [CODE]
+ int y /* , x */;
+ int x;
+
+ int func(void)
+ {
+ return x;
+ }
+ [CODE]
+
call XTest_goto_decl('gD', lines, 2, 5)
endfunc
func Test_gD_string()
- let lines = [
- \ 'char *s[] = "x";',
- \ 'int x = 1;',
- \ '',
- \ 'int func(void)',
- \ '{',
- \ ' return x;',
- \ '}',
- \ ]
+ let lines =<< trim [CODE]
+ char *s[] = "x";
+ int x = 1;
+
+ int func(void)
+ {
+ return x;
+ }
+ [CODE]
+
call XTest_goto_decl('gD', lines, 2, 5)
endfunc
func Test_gD_string_same_line()
- let lines = [
- \ 'char *s[] = "x", int x = 1;',
- \ '',
- \ 'int func(void)',
- \ '{',
- \ ' return x;',
- \ '}',
- \ ]
+ let lines =<< trim [CODE]
+ char *s[] = "x", int x = 1;
+
+ int func(void)
+ {
+ return x;
+ }
+ [CODE]
+
call XTest_goto_decl('gD', lines, 1, 22)
endfunc
func Test_gD_char()
- let lines = [
- \ "char c = 'x';",
- \ 'int x = 1;',
- \ '',
- \ 'int func(void)',
- \ '{',
- \ ' return x;',
- \ '}',
- \ ]
+ let lines =<< trim [CODE]
+ char c = 'x';
+ int x = 1;
+
+ int func(void)
+ {
+ return x;
+ }
+ [CODE]
+
call XTest_goto_decl('gD', lines, 2, 5)
endfunc
func Test_gd()
- let lines = [
- \ 'int x;',
- \ '',
- \ 'int func(int x)',
- \ '{',
- \ ' return x;',
- \ '}',
- \ ]
+ let lines =<< trim [CODE]
+ int x;
+
+ int func(int x)
+ {
+ return x;
+ }
+ [CODE]
+
call XTest_goto_decl('gd', lines, 3, 14)
endfunc
func Test_gd_not_local()
- let lines = [
- \ 'int func1(void)',
- \ '{',
- \ ' return x;',
- \ '}',
- \ '',
- \ 'int func2(int x)',
- \ '{',
- \ ' return x;',
- \ '}',
- \ ]
+ let lines =<< trim [CODE]
+ int func1(void)
+ {
+ return x;
+ }
+
+ int func2(int x)
+ {
+ return x;
+ }
+ [CODE]
+
call XTest_goto_decl('gd', lines, 3, 10)
endfunc
func Test_gd_kr_style()
- let lines = [
- \ 'int func(x)',
- \ ' int x;',
- \ '{',
- \ ' return x;',
- \ '}',
- \ ]
+ let lines =<< trim [CODE]
+ int func(x)
+ int x;
+ {
+ return x;
+ }
+ [CODE]
+
call XTest_goto_decl('gd', lines, 2, 7)
endfunc
func Test_gd_missing_braces()
- let lines = [
- \ 'def func1(a)',
- \ ' a + 1',
- \ 'end',
- \ '',
- \ 'a = 1',
- \ '',
- \ 'def func2()',
- \ ' return a',
- \ 'end',
- \ ]
+ let lines =<< trim [CODE]
+ def func1(a)
+ a + 1
+ end
+
+ a = 1
+
+ def func2()
+ return a
+ end
+ [CODE]
+
call XTest_goto_decl('gd', lines, 1, 11)
endfunc
func Test_gd_comment()
- let lines = [
- \ 'int func(void)',
- \ '{',
- \ ' /* int x; */',
- \ ' int x;',
- \ ' return x;',
- \ '}',
- \]
+ let lines =<< trim [CODE]
+ int func(void)
+ {
+ /* int x; */
+ int x;
+ return x;
+ }
+ [CODE]
+
call XTest_goto_decl('gd', lines, 4, 7)
endfunc
func Test_gd_comment_in_string()
- let lines = [
- \ 'int func(void)',
- \ '{',
- \ ' char *s ="//"; int x;',
- \ ' int x;',
- \ ' return x;',
- \ '}',
- \]
+ let lines =<< trim [CODE]
+ int func(void)
+ {
+ char *s ="//"; int x;
+ int x;
+ return x;
+ }
+ [CODE]
+
call XTest_goto_decl('gd', lines, 3, 22)
endfunc
func Test_gd_string_in_comment()
set comments=
- let lines = [
- \ 'int func(void)',
- \ '{',
- \ ' /* " */ int x;',
- \ ' int x;',
- \ ' return x;',
- \ '}',
- \]
+ let lines =<< trim [CODE]
+ int func(void)
+ {
+ /* " */ int x;
+ int x;
+ return x;
+ }
+ [CODE]
+
call XTest_goto_decl('gd', lines, 3, 15)
set comments&
endfunc
func Test_gd_inline_comment()
- let lines = [
- \ 'int func(/* x is an int */ int x)',
- \ '{',
- \ ' return x;',
- \ '}',
- \ ]
+ let lines =<< trim [CODE]
+ int func(/* x is an int */ int x)
+ {
+ return x;
+ }
+ [CODE]
+
call XTest_goto_decl('gd', lines, 1, 32)
endfunc
func Test_gd_inline_comment_only()
- let lines = [
- \ 'int func(void) /* one lonely x */',
- \ '{',
- \ ' return x;',
- \ '}',
- \ ]
+ let lines =<< trim [CODE]
+ int func(void) /* one lonely x */
+ {
+ return x;
+ }
+ [CODE]
+
call XTest_goto_decl('gd', lines, 3, 10)
endfunc
func Test_gd_inline_comment_body()
- let lines = [
- \ 'int func(void)',
- \ '{',
- \ ' int y /* , x */;',
- \ '',
- \ ' for (/* int x = 0 */; y < 2; y++);',
- \ '',
- \ ' int x = 0;',
- \ '',
- \ ' return x;',
- \ '}',
- \ ]
+ let lines =<< trim [CODE]
+ int func(void)
+ {
+ int y /* , x */;
+
+ for (/* int x = 0 */; y < 2; y++);
+
+ int x = 0;
+
+ return x;
+ }
+ [CODE]
+
call XTest_goto_decl('gd', lines, 7, 7)
endfunc
func Test_gd_trailing_multiline_comment()
- let lines = [
- \ 'int func(int x) /* x is an int */',
- \ '{',
- \ ' return x;',
- \ '}',
- \ ]
+ let lines =<< trim [CODE]
+ int func(int x) /* x is an int */
+ {
+ return x;
+ }
+ [CODE]
+
call XTest_goto_decl('gd', lines, 1, 14)
endfunc
func Test_gd_trailing_comment()
- let lines = [
- \ 'int func(int x) // x is an int',
- \ '{',
- \ ' return x;',
- \ '}',
- \ ]
+ let lines =<< trim [CODE]
+ int func(int x) // x is an int
+ {
+ return x;
+ }
+ [CODE]
+
call XTest_goto_decl('gd', lines, 1, 14)
endfunc
func Test_gd_string()
- let lines = [
- \ 'int func(void)',
- \ '{',
- \ ' char *s = "x";',
- \ ' int x = 1;',
- \ '',
- \ ' return x;',
- \ '}',
- \ ]
+ let lines =<< trim [CODE]
+ int func(void)
+ {
+ char *s = "x";
+ int x = 1;
+
+ return x;
+ }
+ [CODE]
+
call XTest_goto_decl('gd', lines, 4, 7)
endfunc
func Test_gd_string_only()
- let lines = [
- \ 'int func(void)',
- \ '{',
- \ ' char *s = "x";',
- \ '',
- \ ' return x;',
- \ '}',
- \ ]
+ let lines =<< trim [CODE]
+ int func(void)
+ {
+ char *s = "x";
+
+ return x;
+ }
+ [CODE]
+
call XTest_goto_decl('gd', lines, 5, 10)
endfunc
@@ -289,24 +310,25 @@ func Test_cursorline_keep_col()
endfunc
func Test_gd_local_block()
- let lines = [
- \ ' int main()',
- \ '{',
- \ ' char *a = "NOT NULL";',
- \ ' if(a)',
- \ ' {',
- \ ' char *b = a;',
- \ ' printf("%s\n", b);',
- \ ' }',
- \ ' else',
- \ ' {',
- \ ' char *b = "NULL";',
- \ ' return b;',
- \ ' }',
- \ '',
- \ ' return 0;',
- \ '}',
- \ ]
+ let lines =<< trim [CODE]
+ int main()
+ {
+ char *a = "NOT NULL";
+ if(a)
+ {
+ char *b = a;
+ printf("%s\n", b);
+ }
+ else
+ {
+ char *b = "NULL";
+ return b;
+ }
+
+ return 0;
+ }
+ [CODE]
+
call XTest_goto_decl('1gd', lines, 11, 11)
endfunc
diff --git a/src/nvim/testdir/test_hardcopy.vim b/src/nvim/testdir/test_hardcopy.vim
index ced13b107c..6125f9b993 100644
--- a/src/nvim/testdir/test_hardcopy.vim
+++ b/src/nvim/testdir/test_hardcopy.vim
@@ -1,39 +1,137 @@
" Test :hardcopy
-func Test_printoptions_parsing()
- " Only test that this doesn't throw an error.
- set printoptions=left:5in,right:10pt,top:8mm,bottom:2pc
- set printoptions=left:2in,top:30pt,right:16mm,bottom:3pc
- set printoptions=header:3,syntax:y,number:7,wrap:n
- set printoptions=duplex:short,collate:n,jobsplit:y,portrait:n
- set printoptions=paper:10x14
- set printoptions=paper:A3
- set printoptions=paper:A4
- set printoptions=paper:A5
- set printoptions=paper:B4
- set printoptions=paper:B5
- set printoptions=paper:executive
- set printoptions=paper:folio
- set printoptions=paper:ledger
- set printoptions=paper:legal
- set printoptions=paper:letter
- set printoptions=paper:quarto
- set printoptions=paper:statement
- set printoptions=paper:tabloid
- set printoptions=formfeed:y
- set printoptions=
- set printoptions&
+func Test_printoptions()
+ edit test_hardcopy.vim
+ syn on
+
+ for opt in ['left:5in,right:10pt,top:8mm,bottom:2pc',
+ \ 'left:2in,top:30pt,right:16mm,bottom:3pc',
+ \ 'header:3,syntax:y,number:y,wrap:n',
+ \ 'header:3,syntax:n,number:y,wrap:y',
+ \ 'duplex:short,collate:n,jobsplit:y,portrait:n',
+ \ 'duplex:long,collate:y,jobsplit:n,portrait:y',
+ \ 'paper:10x14',
+ \ 'paper:A3',
+ \ 'paper:A4',
+ \ 'paper:A5',
+ \ 'paper:B4',
+ \ 'paper:B5',
+ \ 'paper:executive',
+ \ 'paper:folio',
+ \ 'paper:ledger',
+ \ 'paper:legal',
+ \ 'paper:letter',
+ \ 'paper:quarto',
+ \ 'paper:statement',
+ \ 'paper:tabloid',
+ \ 'formfeed:y',
+ \ '']
+ exe 'set printoptions=' .. opt
+ if has('postscript')
+ hardcopy > Xhardcopy_printoptions
+ let lines = readfile('Xhardcopy_printoptions')
+ call assert_true(len(lines) > 20, opt)
+ call assert_true(lines[0] =~ 'PS-Adobe', opt)
+ call delete('Xhardcopy_printoptions')
+ endif
+ endfor
call assert_fails('set printoptions=paper', 'E550:')
call assert_fails('set printoptions=shredder:on', 'E551:')
call assert_fails('set printoptions=left:no', 'E552:')
+ set printoptions&
+ bwipe
endfunc
-func Test_printmbfont_parsing()
- " Only test that this doesn't throw an error.
- set printmbfont=r:WadaMin-Regular,b:WadaMin-Bold,i:WadaMin-Italic,o:WadaMin-Bold-Italic,c:yes,a:no
- set printmbfont=
+func Test_printmbfont()
+ " Print a small help page which contains tabs to cover code that expands tabs to spaces.
+ help help
+ syn on
+
+ for opt in [':WadaMin-Regular,b:WadaMin-Bold,i:WadaMin-Italic,o:WadaMin-Bold-Italic,c:yes,a:no',
+ \ '']
+ exe 'set printmbfont=' .. opt
+ if has('postscript')
+ hardcopy > Xhardcopy_printmbfont
+ let lines = readfile('Xhardcopy_printmbfont')
+ call assert_true(len(lines) > 20, opt)
+ call assert_true(lines[0] =~ 'PS-Adobe', opt)
+ call delete('Xhardcopy_printmbfont')
+ endif
+ endfor
set printmbfont&
+ bwipe
+endfunc
+
+func Test_printexpr()
+ if !has('unix')
+ return
+ endif
+
+ " Not a very useful printexpr value, but enough to test
+ " hardcopy with 'printexpr'.
+ function PrintFile(fname)
+ call writefile(['Test printexpr: ' .. v:cmdarg],
+ \ 'Xhardcopy_printexpr')
+ call delete(a:fname)
+ return 0
+ endfunc
+ set printexpr=PrintFile(v:fname_in)
+
+ help help
+ hardcopy dummy args
+ call assert_equal(['Test printexpr: dummy args'],
+ \ readfile('Xhardcopy_printexpr'))
+ call delete('Xhardcopy_printexpr')
+
+ " Function return 1 to test print failure.
+ function PrintFails(fname)
+ call delete(a:fname)
+ return 1
+ endfunc
+ set printexpr=PrintFails(v:fname_in)
+ call assert_fails('hardcopy', 'E365:')
+
+ set printexpr&
+ bwipe
+endfunc
+
+func Test_errors()
+ " FIXME: Windows fails differently than Unix.
+ if has('unix')
+ edit test_hardcopy.vim
+ call assert_fails('hardcopy >', 'E324:')
+ bwipe
+ endif
+endfunc
+
+func Test_dark_background()
+ edit test_hardcopy.vim
+ syn on
+
+ for bg in ['dark', 'light']
+ exe 'set background=' .. bg
+
+ if has('postscript')
+ hardcopy > Xhardcopy_dark_background
+ let lines = readfile('Xhardcopy_dark_background')
+ call assert_true(len(lines) > 20)
+ call assert_true(lines[0] =~ 'PS-Adobe')
+ call delete('Xhardcopy_dark_background')
+ endif
+ endfor
+
+ set background&
+ bwipe
+endfun
+
+func Test_empty_buffer()
+ " FIXME: Unclear why this fails on Windows.
+ if has('unix')
+ new
+ call assert_equal("\nNo text to be printed", execute('hardcopy'))
+ bwipe
+ endif
endfunc
func Test_printheader_parsing()
@@ -46,22 +144,6 @@ func Test_printheader_parsing()
set printheader&
endfunc
-" Test that :hardcopy produces a non-empty file.
-" We don't check much of the contents.
-func Test_with_syntax()
- if has('postscript')
- edit test_hardcopy.vim
- set printoptions=syntax:y
- syn on
- hardcopy > Xhardcopy
- let lines = readfile('Xhardcopy')
- call assert_true(len(lines) > 20)
- call assert_true(lines[0] =~ 'PS-Adobe')
- call delete('Xhardcopy')
- set printoptions&
- endif
-endfunc
-
func Test_fname_with_spaces()
if !has('postscript')
return
@@ -86,4 +168,3 @@ func Test_illegal_byte()
bwipe!
call delete('Xpstest')
endfunc
-
diff --git a/src/nvim/testdir/test_help.vim b/src/nvim/testdir/test_help.vim
index ed3181564c..01fb9917e9 100644
--- a/src/nvim/testdir/test_help.vim
+++ b/src/nvim/testdir/test_help.vim
@@ -21,6 +21,12 @@ func Test_help_errors()
bwipe!
endfunc
+func Test_help_expr()
+ help expr-!~?
+ call assert_equal('eval.txt', expand('%:t'))
+ close
+endfunc
+
func Test_help_keyword()
new
set keywordprg=:help
diff --git a/src/nvim/testdir/test_highlight.vim b/src/nvim/testdir/test_highlight.vim
index d94eb7c3a2..6aa187b17e 100644
--- a/src/nvim/testdir/test_highlight.vim
+++ b/src/nvim/testdir/test_highlight.vim
@@ -1,6 +1,7 @@
" Tests for ":highlight" and highlighting.
source view_util.vim
+source screendump.vim
func Test_highlight()
" basic test if ":highlight" doesn't crash
@@ -591,3 +592,13 @@ func Test_cursorline_with_visualmode()
call StopVimInTerminal(buf)
call delete('Xtest_cursorline_with_visualmode')
endfunc
+
+" This test must come before the Test_cursorline test, as it appears this
+" defines the Normal highlighting group anyway.
+func Test_1_highlight_Normalgroup_exists()
+ " MS-Windows GUI sets the font
+ if !has('win32') || !has('gui_running')
+ let hlNormal = HighlightArgs('Normal')
+ call assert_match('hi Normal\s*clear', hlNormal)
+ endif
+endfunc
diff --git a/src/nvim/testdir/test_increment.vim b/src/nvim/testdir/test_increment.vim
index ab11d943d9..f81f8edbde 100644
--- a/src/nvim/testdir/test_increment.vim
+++ b/src/nvim/testdir/test_increment.vim
@@ -779,4 +779,40 @@ func Test_increment_empty_line()
bwipe!
endfunc
+func Test_normal_increment_with_virtualedit()
+ set virtualedit=all
+
+ call setline(1, ["\<TAB>1"])
+ exec "norm! 0\<C-A>"
+ call assert_equal("\<TAB>2", getline(1))
+ call assert_equal([0, 1, 2, 0], getpos('.'))
+
+ call setline(1, ["\<TAB>1"])
+ exec "norm! 0l\<C-A>"
+ call assert_equal("\<TAB>2", getline(1))
+ call assert_equal([0, 1, 2, 0], getpos('.'))
+
+ call setline(1, ["\<TAB>1"])
+ exec "norm! 07l\<C-A>"
+ call assert_equal("\<TAB>2", getline(1))
+ call assert_equal([0, 1, 2, 0], getpos('.'))
+
+ call setline(1, ["\<TAB>1"])
+ exec "norm! 0w\<C-A>"
+ call assert_equal("\<TAB>2", getline(1))
+ call assert_equal([0, 1, 2, 0], getpos('.'))
+
+ call setline(1, ["\<TAB>1"])
+ exec "norm! 0wl\<C-A>"
+ call assert_equal("\<TAB>1", getline(1))
+ call assert_equal([0, 1, 3, 0], getpos('.'))
+
+ call setline(1, ["\<TAB>1"])
+ exec "norm! 0w30l\<C-A>"
+ call assert_equal("\<TAB>1", getline(1))
+ call assert_equal([0, 1, 3, 29], getpos('.'))
+
+ set virtualedit&
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_ins_complete.vim b/src/nvim/testdir/test_ins_complete.vim
index 7f52481ba8..1c275d5bd1 100644
--- a/src/nvim/testdir/test_ins_complete.vim
+++ b/src/nvim/testdir/test_ins_complete.vim
@@ -98,6 +98,15 @@ func Test_ins_complete()
call delete('Xdir', 'rf')
endfunc
+func s:CompleteDone_CompleteFuncNone( findstart, base )
+ throw 'skipped: Nvim does not support v:none'
+ if a:findstart
+ return 0
+ endif
+
+ return v:none
+endfunc
+
function! s:CompleteDone_CompleteFuncDict( findstart, base )
if a:findstart
return 0
@@ -111,32 +120,58 @@ function! s:CompleteDone_CompleteFuncDict( findstart, base )
\ 'menu': 'extra text',
\ 'info': 'words are cool',
\ 'kind': 'W',
- \ 'user_data': 'test'
+ \ 'user_data': ['one', 'two']
\ }
\ ]
\ }
endfunction
-function! s:CompleteDone_CheckCompletedItemDict()
+func s:CompleteDone_CheckCompletedItemNone()
+ let s:called_completedone = 1
+endfunc
+
+func s:CompleteDone_CheckCompletedItemDict(pre)
call assert_equal( 'aword', v:completed_item[ 'word' ] )
call assert_equal( 'wrd', v:completed_item[ 'abbr' ] )
call assert_equal( 'extra text', v:completed_item[ 'menu' ] )
call assert_equal( 'words are cool', v:completed_item[ 'info' ] )
call assert_equal( 'W', v:completed_item[ 'kind' ] )
- call assert_equal( 'test', v:completed_item[ 'user_data' ] )
+ call assert_equal( ['one', 'two'], v:completed_item[ 'user_data' ] )
+
+ if a:pre
+ call assert_equal('function', complete_info().mode)
+ endif
let s:called_completedone = 1
-endfunction
+endfunc
-function Test_CompleteDoneDict()
- au CompleteDone * :call <SID>CompleteDone_CheckCompletedItemDict()
+func Test_CompleteDoneNone()
+ throw 'skipped: Nvim does not support v:none'
+ au CompleteDone * :call <SID>CompleteDone_CheckCompletedItemNone()
+ let oldline = join(map(range(&columns), 'nr2char(screenchar(&lines-1, v:val+1))'), '')
+
+ set completefunc=<SID>CompleteDone_CompleteFuncNone
+ execute "normal a\<C-X>\<C-U>\<C-Y>"
+ set completefunc&
+ let newline = join(map(range(&columns), 'nr2char(screenchar(&lines-1, v:val+1))'), '')
+
+ call assert_true(s:called_completedone)
+ call assert_equal(oldline, newline)
+
+ let s:called_completedone = 0
+ au! CompleteDone
+endfunc
+
+func Test_CompleteDoneDict()
+ au CompleteDonePre * :call <SID>CompleteDone_CheckCompletedItemDict(1)
+ au CompleteDone * :call <SID>CompleteDone_CheckCompletedItemDict(0)
set completefunc=<SID>CompleteDone_CompleteFuncDict
execute "normal a\<C-X>\<C-U>\<C-Y>"
set completefunc&
- call assert_equal( 'test', v:completed_item[ 'user_data' ] )
- call assert_true( s:called_completedone )
+ call assert_equal(['one', 'two'], v:completed_item[ 'user_data' ])
+ call assert_true(s:called_completedone)
let s:called_completedone = 0
au! CompleteDone
@@ -155,7 +190,7 @@ func Test_CompleteDone_undo()
au! CompleteDone
endfunc
-function! s:CompleteDone_CompleteFuncDictNoUserData( findstart, base )
+func s:CompleteDone_CompleteFuncDictNoUserData(findstart, base)
if a:findstart
return 0
endif
@@ -171,9 +206,9 @@ function! s:CompleteDone_CompleteFuncDictNoUserData( findstart, base )
\ }
\ ]
\ }
-endfunction
+endfunc
-function! s:CompleteDone_CheckCompletedItemDictNoUserData()
+func s:CompleteDone_CheckCompletedItemDictNoUserData()
call assert_equal( 'aword', v:completed_item[ 'word' ] )
call assert_equal( 'wrd', v:completed_item[ 'abbr' ] )
call assert_equal( 'extra text', v:completed_item[ 'menu' ] )
@@ -182,31 +217,31 @@ function! s:CompleteDone_CheckCompletedItemDictNoUserData()
call assert_equal( '', v:completed_item[ 'user_data' ] )
let s:called_completedone = 1
-endfunction
+endfunc
-function Test_CompleteDoneDictNoUserData()
+func Test_CompleteDoneDictNoUserData()
au CompleteDone * :call <SID>CompleteDone_CheckCompletedItemDictNoUserData()
set completefunc=<SID>CompleteDone_CompleteFuncDictNoUserData
execute "normal a\<C-X>\<C-U>\<C-Y>"
set completefunc&
- call assert_equal( '', v:completed_item[ 'user_data' ] )
- call assert_true( s:called_completedone )
+ call assert_equal('', v:completed_item[ 'user_data' ])
+ call assert_true(s:called_completedone)
let s:called_completedone = 0
au! CompleteDone
endfunc
-function! s:CompleteDone_CompleteFuncList( findstart, base )
+func s:CompleteDone_CompleteFuncList(findstart, base)
if a:findstart
return 0
endif
return [ 'aword' ]
-endfunction
+endfunc
-function! s:CompleteDone_CheckCompletedItemList()
+func s:CompleteDone_CheckCompletedItemList()
call assert_equal( 'aword', v:completed_item[ 'word' ] )
call assert_equal( '', v:completed_item[ 'abbr' ] )
call assert_equal( '', v:completed_item[ 'menu' ] )
@@ -215,17 +250,17 @@ function! s:CompleteDone_CheckCompletedItemList()
call assert_equal( '', v:completed_item[ 'user_data' ] )
let s:called_completedone = 1
-endfunction
+endfunc
-function Test_CompleteDoneList()
+func Test_CompleteDoneList()
au CompleteDone * :call <SID>CompleteDone_CheckCompletedItemList()
set completefunc=<SID>CompleteDone_CompleteFuncList
execute "normal a\<C-X>\<C-U>\<C-Y>"
set completefunc&
- call assert_equal( '', v:completed_item[ 'user_data' ] )
- call assert_true( s:called_completedone )
+ call assert_equal('', v:completed_item[ 'user_data' ])
+ call assert_true(s:called_completedone)
let s:called_completedone = 0
au! CompleteDone
@@ -285,3 +320,21 @@ func Test_compl_feedkeys()
bwipe!
set completeopt&
endfunc
+
+func Test_compl_in_cmdwin()
+ set wildmenu wildchar=<Tab>
+ com! -nargs=1 -complete=command GetInput let input = <q-args>
+ com! -buffer TestCommand echo 'TestCommand'
+
+ let input = ''
+ call feedkeys("q:iGetInput T\<C-x>\<C-v>\<CR>", 'tx!')
+ call assert_equal('TestCommand', input)
+
+ let input = ''
+ call feedkeys("q::GetInput T\<Tab>\<CR>:q\<CR>", 'tx!')
+ call assert_equal('T', input)
+
+ delcom TestCommand
+ delcom GetInput
+ set wildmenu& wildchar&
+endfunc
diff --git a/src/nvim/testdir/test_join.vim b/src/nvim/testdir/test_join.vim
index 1c97414164..ac6ef8f29f 100644
--- a/src/nvim/testdir/test_join.vim
+++ b/src/nvim/testdir/test_join.vim
@@ -9,6 +9,27 @@ func Test_join_with_count()
call setline(1, ['one', 'two', 'three', 'four'])
normal 10J
call assert_equal('one two three four', getline(1))
+
+ call setline(1, ['one', '', 'two'])
+ normal J
+ call assert_equal('one', getline(1))
+
+ call setline(1, ['one', ' ', 'two'])
+ normal J
+ call assert_equal('one', getline(1))
+
+ call setline(1, ['one', '', '', 'two'])
+ normal JJ
+ call assert_equal('one', getline(1))
+
+ call setline(1, ['one', ' ', ' ', 'two'])
+ normal JJ
+ call assert_equal('one', getline(1))
+
+ call setline(1, ['one', '', '', 'two'])
+ normal 2J
+ call assert_equal('one', getline(1))
+
quit!
endfunc
@@ -33,3 +54,388 @@ func Test_join_marks()
call assert_equal([0, 4, 67, 0], getpos("']"))
enew!
endfunc
+
+" Test for joining lines and marks in them
+" in compatible and nocompatible modes
+" and with 'joinspaces' set or not
+" and with 'cpoptions' flag 'j' set or not
+func Test_join_spaces_marks()
+ new
+ " Text used for the test
+ insert
+asdfasdf.
+asdf
+asdfasdf.
+asdf
+asdfasdf.
+asdf
+asdfasdf.
+asdf
+asdfasdf.
+asdf
+asdfasdf.
+asdf
+asdfasdf.
+asdf
+asdfasdf
+asdf
+asdfasdf
+asdf
+asdfasdf
+asdf
+asdfasdf
+asdf
+asdfasdf
+asdf
+asdfasdf
+asdf
+asdfasdf
+asdf
+zx cvn.
+as dfg?
+hjkl iop!
+ert
+zx cvn.
+as dfg?
+hjkl iop!
+ert
+.
+ let text = getline(1, '$')
+ normal gg
+
+ set nojoinspaces
+ set cpoptions-=j
+ normal JjJjJjJjJjJjJjJjJjJjJjJjJjJ
+ normal j05lmx
+ normal 2j06lmy
+ normal 2k4Jy3l$p
+ normal `xyl$p
+ normal `yy2l$p
+
+ " set cpoptions+=j
+ normal j05lmx
+ normal 2j06lmy
+ normal 2k4Jy3l$p
+ normal `xyl$p
+ normal `yy2l$p
+
+ " Expected output
+ let expected =<< trim [DATA]
+ asdfasdf. asdf
+ asdfasdf. asdf
+ asdfasdf. asdf
+ asdfasdf. asdf
+ asdfasdf. asdf
+ asdfasdf. asdf
+ asdfasdf. asdf
+ asdfasdf asdf
+ asdfasdf asdf
+ asdfasdf asdf
+ asdfasdf asdf
+ asdfasdf asdf
+ asdfasdf asdf
+ asdfasdf asdf
+ zx cvn. as dfg? hjkl iop! ert ernop
+ zx cvn. as dfg? hjkl iop! ert ernop
+ [DATA]
+
+ call assert_equal(expected, getline(1, '$'))
+ throw 'skipped: Nvim does not support "set compatible" or "set cpoptions+=j"'
+
+ enew!
+ call append(0, text)
+ normal gg
+
+ set cpoptions-=j
+ set joinspaces
+ normal JjJjJjJjJjJjJjJjJjJjJjJjJjJ
+ normal j05lmx
+ normal 2j06lmy
+ normal 2k4Jy3l$p
+ normal `xyl$p
+ normal `yy2l$p
+
+ set cpoptions+=j
+ normal j05lmx
+ normal 2j06lmy
+ normal 2k4Jy3l$p
+ normal `xyl$p
+ normal `yy2l$p
+
+ " Expected output
+ let expected =<< trim [DATA]
+ asdfasdf. asdf
+ asdfasdf. asdf
+ asdfasdf. asdf
+ asdfasdf. asdf
+ asdfasdf. asdf
+ asdfasdf. asdf
+ asdfasdf. asdf
+ asdfasdf asdf
+ asdfasdf asdf
+ asdfasdf asdf
+ asdfasdf asdf
+ asdfasdf asdf
+ asdfasdf asdf
+ asdfasdf asdf
+ zx cvn. as dfg? hjkl iop! ert enop
+ zx cvn. as dfg? hjkl iop! ert ernop
+
+ [DATA]
+
+ call assert_equal(expected, getline(1, '$'))
+
+ enew!
+ call append(0, text)
+ normal gg
+
+ set cpoptions-=j
+ set nojoinspaces
+ set compatible
+
+ normal JjJjJjJjJjJjJjJjJjJjJjJjJjJ
+ normal j4Jy3l$pjdG
+
+ " Expected output
+ let expected =<< trim [DATA]
+ asdfasdf. asdf
+ asdfasdf. asdf
+ asdfasdf. asdf
+ asdfasdf. asdf
+ asdfasdf. asdf
+ asdfasdf. asdf
+ asdfasdf. asdf
+ asdfasdf asdf
+ asdfasdf asdf
+ asdfasdf asdf
+ asdfasdf asdf
+ asdfasdf asdf
+ asdfasdf asdf
+ asdfasdf asdf
+ zx cvn. as dfg? hjkl iop! ert a
+ [DATA]
+
+ call assert_equal(expected, getline(1, '$'))
+
+ set nocompatible
+ set cpoptions&vim
+ set joinspaces&vim
+ close!
+endfunc
+
+" Test for joining lines with comments
+func Test_join_lines_with_comments()
+ new
+
+ " Text used by the test
+ insert
+{
+
+/*
+* Make sure the previous comment leader is not removed.
+*/
+
+/*
+* Make sure the previous comment leader is not removed.
+*/
+
+// Should the next comment leader be left alone?
+// Yes.
+
+// Should the next comment leader be left alone?
+// Yes.
+
+/* Here the comment leader should be left intact. */
+// And so should this one.
+
+/* Here the comment leader should be left intact. */
+// And so should this one.
+
+if (condition) // Remove the next comment leader!
+// OK, I will.
+action();
+
+if (condition) // Remove the next comment leader!
+// OK, I will.
+action();
+}
+.
+
+ call cursor(2, 1)
+ set comments=s1:/*,mb:*,ex:*/,://
+ set nojoinspaces fo=j
+ set backspace=eol,start
+
+ .,+3join
+ exe "normal j4J\<CR>"
+ .,+2join
+ exe "normal j3J\<CR>"
+ .,+2join
+ exe "normal j3J\<CR>"
+ .,+2join
+ exe "normal jj3J\<CR>"
+
+ " Expected output
+ let expected =<< trim [CODE]
+ {
+ /* Make sure the previous comment leader is not removed. */
+ /* Make sure the previous comment leader is not removed. */
+ // Should the next comment leader be left alone? Yes.
+ // Should the next comment leader be left alone? Yes.
+ /* Here the comment leader should be left intact. */ // And so should this one.
+ /* Here the comment leader should be left intact. */ // And so should this one.
+ if (condition) // Remove the next comment leader! OK, I will.
+ action();
+ if (condition) // Remove the next comment leader! OK, I will.
+ action();
+ }
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+
+ set comments&vim
+ set joinspaces&vim
+ set fo&vim
+ set backspace&vim
+ close!
+endfunc
+
+" Test for joining lines with different comment leaders
+func Test_join_comments_2()
+ new
+
+ insert
+{
+
+/*
+ * Make sure the previous comment leader is not removed.
+ */
+
+/*
+ * Make sure the previous comment leader is not removed.
+ */
+
+/* List:
+ * - item1
+ * foo bar baz
+ * foo bar baz
+ * - item2
+ * foo bar baz
+ * foo bar baz
+ */
+
+/* List:
+ * - item1
+ * foo bar baz
+ * foo bar baz
+ * - item2
+ * foo bar baz
+ * foo bar baz
+ */
+
+// Should the next comment leader be left alone?
+// Yes.
+
+// Should the next comment leader be left alone?
+// Yes.
+
+/* Here the comment leader should be left intact. */
+// And so should this one.
+
+/* Here the comment leader should be left intact. */
+// And so should this one.
+
+if (condition) // Remove the next comment leader!
+ // OK, I will.
+ action();
+
+if (condition) // Remove the next comment leader!
+ // OK, I will.
+ action();
+
+int i = 7 /* foo *// 3
+ // comment
+ ;
+
+int i = 7 /* foo *// 3
+ // comment
+ ;
+
+># Note that the last character of the ending comment leader (left angle
+ # bracket) is a comment leader itself. Make sure that this comment leader is
+ # not removed from the next line #<
+< On this line a new comment is opened which spans 2 lines. This comment should
+< retain its comment leader.
+
+># Note that the last character of the ending comment leader (left angle
+ # bracket) is a comment leader itself. Make sure that this comment leader is
+ # not removed from the next line #<
+< On this line a new comment is opened which spans 2 lines. This comment should
+< retain its comment leader.
+
+}
+.
+
+ call cursor(2, 1)
+ set comments=sO:*\ -,mO:*\ \ ,exO:*/
+ set comments+=s1:/*,mb:*,ex:*/,://
+ set comments+=s1:>#,mb:#,ex:#<,:<
+ set cpoptions-=j joinspaces fo=j
+ set backspace=eol,start
+
+ .,+3join
+ exe "normal j4J\<CR>"
+ .,+8join
+ exe "normal j9J\<CR>"
+ .,+2join
+ exe "normal j3J\<CR>"
+ .,+2join
+ exe "normal j3J\<CR>"
+ .,+2join
+ exe "normal jj3J\<CR>j"
+ .,+2join
+ exe "normal jj3J\<CR>j"
+ .,+5join
+ exe "normal j6J\<CR>"
+ exe "normal oSome code!\<CR>// Make sure backspacing does not remove this comment leader.\<Esc>0i\<C-H>\<Esc>"
+
+ " Expected output
+ let expected =<< trim [CODE]
+ {
+ /* Make sure the previous comment leader is not removed. */
+ /* Make sure the previous comment leader is not removed. */
+ /* List: item1 foo bar baz foo bar baz item2 foo bar baz foo bar baz */
+ /* List: item1 foo bar baz foo bar baz item2 foo bar baz foo bar baz */
+ // Should the next comment leader be left alone? Yes.
+ // Should the next comment leader be left alone? Yes.
+ /* Here the comment leader should be left intact. */ // And so should this one.
+ /* Here the comment leader should be left intact. */ // And so should this one.
+ if (condition) // Remove the next comment leader! OK, I will.
+ action();
+ if (condition) // Remove the next comment leader! OK, I will.
+ action();
+ int i = 7 /* foo *// 3 // comment
+ ;
+ int i = 7 /* foo *// 3 // comment
+ ;
+ ># Note that the last character of the ending comment leader (left angle bracket) is a comment leader itself. Make sure that this comment leader is not removed from the next line #< < On this line a new comment is opened which spans 2 lines. This comment should retain its comment leader.
+ ># Note that the last character of the ending comment leader (left angle bracket) is a comment leader itself. Make sure that this comment leader is not removed from the next line #< < On this line a new comment is opened which spans 2 lines. This comment should retain its comment leader.
+
+ Some code!// Make sure backspacing does not remove this comment leader.
+ }
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ close!
+endfunc
+
+func Test_join_lines()
+ new
+ call setline(1, ['a', 'b', '', 'c', 'd'])
+ %join
+ call assert_equal('a b c d', getline(1))
+ call setline(1, ['a', 'b', '', 'c', 'd'])
+ normal 5J
+ call assert_equal('a b c d', getline(1))
+ bwipe!
+endfunc
diff --git a/src/nvim/testdir/test_let.vim b/src/nvim/testdir/test_let.vim
index 8a6f1bc320..0b9331ee38 100644
--- a/src/nvim/testdir/test_let.vim
+++ b/src/nvim/testdir/test_let.vim
@@ -24,6 +24,10 @@ func Test_let()
let out = execute('let a {0 == 1 ? "a" : "b"}')
let s = "\na #1\nb #2"
call assert_equal(s, out)
+
+ let x = 0
+ if 0 | let x = 1 | endif
+ call assert_equal(0, x)
endfunc
func s:set_arg1(a) abort
@@ -140,3 +144,161 @@ func Test_let_varg_fail()
call assert_fails('call s:set_varg7(1)', 'E742:')
call s:set_varg8([0])
endfunction
+
+func Test_let_utf8_environment()
+ let $a = 'ĀĒĪŌŪã‚ã„ã†ãˆãŠ'
+ call assert_equal('ĀĒĪŌŪã‚ã„ã†ãˆãŠ', $a)
+endfunc
+
+func Test_let_heredoc_fails()
+ call assert_fails('let v =<< marker', 'E991:')
+
+ let text =<< trim END
+ func WrongSyntax()
+ let v =<< that there
+ endfunc
+ END
+ call writefile(text, 'XheredocFail')
+ call assert_fails('source XheredocFail', 'E126:')
+ call delete('XheredocFail')
+
+ let text =<< trim CodeEnd
+ func MissingEnd()
+ let v =<< END
+ endfunc
+ CodeEnd
+ call writefile(text, 'XheredocWrong')
+ call assert_fails('source XheredocWrong', 'E126:')
+ call delete('XheredocWrong')
+
+ let text =<< trim TEXTend
+ let v =<< " comment
+ TEXTend
+ call writefile(text, 'XheredocNoMarker')
+ call assert_fails('source XheredocNoMarker', 'E172:')
+ call delete('XheredocNoMarker')
+
+ let text =<< trim TEXTend
+ let v =<< text
+ TEXTend
+ call writefile(text, 'XheredocBadMarker')
+ call assert_fails('source XheredocBadMarker', 'E221:')
+ call delete('XheredocBadMarker')
+endfunc
+
+func Test_let_heredoc_trim_no_indent_marker()
+ let text =<< trim END
+ Text
+ with
+ indent
+END
+ call assert_equal(['Text', 'with', 'indent'], text)
+endfunc
+
+" Test for the setting a variable using the heredoc syntax
+func Test_let_heredoc()
+ let var1 =<< END
+Some sample text
+ Text with indent
+ !@#$%^&*()-+_={}|[]\~`:";'<>?,./
+END
+
+ call assert_equal(["Some sample text", "\tText with indent", " !@#$%^&*()-+_={}|[]\\~`:\";'<>?,./"], var1)
+
+ let var2 =<< XXX
+Editor
+XXX
+ call assert_equal(['Editor'], var2)
+
+ let var3 =<<END
+END
+ call assert_equal([], var3)
+
+ let var3 =<<END
+vim
+
+end
+ END
+END
+END
+ call assert_equal(['vim', '', 'end', ' END', 'END '], var3)
+
+ let var1 =<< trim END
+ Line1
+ Line2
+ Line3
+ END
+ END
+ call assert_equal(['Line1', ' Line2', "\tLine3", ' END'], var1)
+
+ let var1 =<< trim !!!
+ Line1
+ line2
+ Line3
+ !!!
+ !!!
+ call assert_equal(['Line1', ' line2', "\tLine3", '!!!',], var1)
+
+ let var1 =<< trim XX
+ Line1
+ XX
+ call assert_equal(['Line1'], var1)
+
+ " ignore "endfunc"
+ let var1 =<< END
+something
+endfunc
+END
+ call assert_equal(['something', 'endfunc'], var1)
+
+ " ignore "endfunc" with trim
+ let var1 =<< trim END
+ something
+ endfunc
+ END
+ call assert_equal(['something', 'endfunc'], var1)
+
+ " ignore "python << xx"
+ let var1 =<<END
+something
+python << xx
+END
+ call assert_equal(['something', 'python << xx'], var1)
+
+ " ignore "python << xx" with trim
+ let var1 =<< trim END
+ something
+ python << xx
+ END
+ call assert_equal(['something', 'python << xx'], var1)
+
+ " ignore "append"
+ let var1 =<< E
+something
+app
+E
+ call assert_equal(['something', 'app'], var1)
+
+ " ignore "append" with trim
+ let var1 =<< trim END
+ something
+ app
+ END
+ call assert_equal(['something', 'app'], var1)
+
+ let check = []
+ if 0
+ let check =<< trim END
+ from heredoc
+ END
+ endif
+ call assert_equal([], check)
+
+ " unpack assignment
+ let [a, b, c] =<< END
+ x
+ \y
+ z
+END
+ call assert_equal([' x', ' \y', ' z'], [a, b, c])
+endfunc
diff --git a/src/nvim/testdir/test_listchars.vim b/src/nvim/testdir/test_listchars.vim
index 57cfaa298e..dcc588120c 100644
--- a/src/nvim/testdir/test_listchars.vim
+++ b/src/nvim/testdir/test_listchars.vim
@@ -58,6 +58,26 @@ func Test_listchars()
call assert_equal([expected[i - 1]], ScreenLines(i, virtcol('$')))
endfor
+ " tab with 3rd character and linebreak set
+ set listchars-=tab:<=>
+ set listchars+=tab:<·>
+ set linebreak
+ let expected = [
+ \ '<······>aa<····>$',
+ \ '..bb<··>--$',
+ \ '...cccc>-$',
+ \ 'dd........ee--<>$',
+ \ '-$'
+ \ ]
+ redraw!
+ for i in range(1, 5)
+ call cursor(i, 1)
+ call assert_equal([expected[i - 1]], ScreenLines(i, virtcol('$')))
+ endfor
+ set nolinebreak
+ set listchars-=tab:<·>
+ set listchars+=tab:<=>
+
set listchars-=trail:-
let expected = [
\ '<======>aa<====>$',
diff --git a/src/nvim/testdir/test_listlbr.vim b/src/nvim/testdir/test_listlbr.vim
index d28dbc444c..cdc5e4cc7c 100644
--- a/src/nvim/testdir/test_listlbr.vim
+++ b/src/nvim/testdir/test_listlbr.vim
@@ -103,6 +103,37 @@ func Test_linebreak_with_conceal()
call s:close_windows()
endfunc
+func Test_linebreak_with_visual_operations()
+ call s:test_windows()
+ let line = '1234567890 2234567890 3234567890'
+ call setline(1, line)
+
+ " yank
+ exec "norm! ^w\<C-V>ey"
+ call assert_equal('2234567890', @@)
+ exec "norm! w\<C-V>ey"
+ call assert_equal('3234567890', @@)
+
+ " increment / decrement
+ exec "norm! ^w\<C-V>\<C-A>w\<C-V>\<C-X>"
+ call assert_equal('1234567890 3234567890 2234567890', getline(1))
+
+ " replace
+ exec "norm! ^w\<C-V>3lraw\<C-V>3lrb"
+ call assert_equal('1234567890 aaaa567890 bbbb567890', getline(1))
+
+ " tilde
+ exec "norm! ^w\<C-V>2l~w\<C-V>2l~"
+ call assert_equal('1234567890 AAAa567890 BBBb567890', getline(1))
+
+ " delete and insert
+ exec "norm! ^w\<C-V>3lc2345\<Esc>w\<C-V>3lc3456\<Esc>"
+ call assert_equal('1234567890 2345567890 3456567890', getline(1))
+ call assert_equal('BBBb', @@)
+
+ call s:close_windows()
+endfunc
+
func Test_virtual_block()
call s:test_windows('setl sbr=+')
call setline(1, [
diff --git a/src/nvim/testdir/test_maparg.vim b/src/nvim/testdir/test_maparg.vim
index 0b941d51ec..238d2f900d 100644
--- a/src/nvim/testdir/test_maparg.vim
+++ b/src/nvim/testdir/test_maparg.vim
@@ -15,23 +15,23 @@ function Test_maparg()
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, 'lhs': 'foo<C-V>',
+ call assert_equal({'silent': 0, 'noremap': 0, 'script': 0, 'lhs': 'foo<C-V>',
\ '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, 'lhs': 'bar', 'mode': 'v',
+ call assert_equal({'silent': 1, 'noremap': 1, 'script': 1, 'lhs': 'bar', 'mode': 'v',
\ 'nowait': 0, 'expr': 1, 'sid': sid, 'lnum': lnum + 2,
\ 'rhs': 'isbar', 'buffer': 1},
\ maparg('bar', '', 0, 1))
let lnum = expand('<sflnum>')
map <buffer> <nowait> foo bar
- call assert_equal({'silent': 0, 'noremap': 0, 'lhs': 'foo', 'mode': ' ',
+ call assert_equal({'silent': 0, 'noremap': 0, 'script': 0, 'lhs': '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, 'lhs': 'baz', 'mode': 't',
+ call assert_equal({'silent': 0, 'noremap': 0, 'script': 0, 'lhs': 'baz', 'mode': 't',
\ 'nowait': 0, 'expr': 0, 'sid': sid, 'lnum': lnum + 1, 'rhs': 'foo',
\ 'buffer': 0},
\ maparg('baz', 't', 0, 1))
@@ -41,6 +41,11 @@ function Test_maparg()
map abc y<S-char-114>y
call assert_equal("yRy", maparg('abc'))
+ omap { w
+ let d = maparg('{', 'o', 0, 1)
+ call assert_equal(['{', 'w', 'o'], [d.lhs, d.rhs, d.mode])
+ ounmap {
+
map abc <Nop>
call assert_equal("<Nop>", maparg('abc'))
unmap abc
@@ -62,3 +67,5 @@ function Test_range_map()
execute "normal a\uf040\<Esc>"
call assert_equal("abcd", getline(1))
endfunction
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_mapping.vim b/src/nvim/testdir/test_mapping.vim
index 34e62e80e8..82562339f6 100644
--- a/src/nvim/testdir/test_mapping.vim
+++ b/src/nvim/testdir/test_mapping.vim
@@ -287,7 +287,7 @@ func Test_map_timeout_with_timer_interrupt()
set timeout timeoutlen=200
func ExitCb(job, status)
- let g:timer = timer_start(1, {_ -> feedkeys("3\<Esc>", 't')})
+ let g:timer = timer_start(1, {-> feedkeys("3\<Esc>", 't')})
endfunc
call job_start([&shell, &shellcmdflag, 'echo'], {'exit_cb': 'ExitCb'})
@@ -390,3 +390,77 @@ func Test_motionforce_omap()
delfunc Select
delfunc GetCommand
endfunc
+
+" Test for mapping errors
+func Test_map_error()
+ call assert_fails('unmap', 'E474:')
+ call assert_fails("exe 'map ' .. repeat('a', 51) .. ' :ls'", 'E474:')
+ call assert_fails('unmap abc', 'E31:')
+ call assert_fails('unabbr abc', 'E24:')
+ call assert_equal('', maparg(''))
+ call assert_fails('echo maparg("abc", [])', 'E730:')
+
+ " unique map
+ map ,w /[#&!]<CR>
+ call assert_fails("map <unique> ,w /[#&!]<CR>", 'E227:')
+ " unique buffer-local map
+ call assert_fails("map <buffer> <unique> ,w /[.,;]<CR>", 'E225:')
+ unmap ,w
+
+ " unique abbreviation
+ abbr SP special
+ call assert_fails("abbr <unique> SP special", 'E226:')
+ " unique buffer-local map
+ call assert_fails("abbr <buffer> <unique> SP special", 'E224:')
+ unabbr SP
+
+ call assert_fails('mapclear abc', 'E474:')
+ call assert_fails('abclear abc', 'E474:')
+endfunc
+
+" Test for <special> key mapping
+func Test_map_special()
+ throw 'skipped: Nvim does not support cpoptions flag "<"'
+ new
+ let old_cpo = &cpo
+ set cpo+=<
+ imap <F12> Blue
+ call feedkeys("i\<F12>", "x")
+ call assert_equal("<F12>", getline(1))
+ call feedkeys("ddi<F12>", "x")
+ call assert_equal("Blue", getline(1))
+ iunmap <F12>
+ imap <special> <F12> Green
+ call feedkeys("ddi\<F12>", "x")
+ call assert_equal("Green", getline(1))
+ call feedkeys("ddi<F12>", "x")
+ call assert_equal("<F12>", getline(1))
+ iunmap <special> <F12>
+ let &cpo = old_cpo
+ %bwipe!
+endfunc
+
+" Test for hasmapto()
+func Test_hasmapto()
+ call assert_equal(0, hasmapto('/^\k\+ ('))
+ call assert_equal(0, hasmapto('/^\k\+ (', 'n'))
+ nmap ,f /^\k\+ (<CR>
+ call assert_equal(1, hasmapto('/^\k\+ ('))
+ call assert_equal(1, hasmapto('/^\k\+ (', 'n'))
+ call assert_equal(0, hasmapto('/^\k\+ (', 'v'))
+
+ call assert_equal(0, hasmapto('/^\k\+ (', 'n', 1))
+endfunc
+
+" Test for command-line completion of maps
+func Test_mapcomplete()
+ call assert_equal(['<buffer>', '<expr>', '<nowait>', '<script>',
+ \ '<silent>', '<special>', '<unique>'],
+ \ getcompletion('', 'mapping'))
+ call assert_equal([], getcompletion(',d', 'mapping'))
+
+ call feedkeys(":abbr! \<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_match("abbr! \x01", @:)
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_marks.vim b/src/nvim/testdir/test_marks.vim
index 272553c29f..66df57ea39 100644
--- a/src/nvim/testdir/test_marks.vim
+++ b/src/nvim/testdir/test_marks.vim
@@ -26,11 +26,11 @@ function! Test_Incr_Marks()
endfunction
func Test_setpos()
- new one
+ new Xone
let onebuf = bufnr('%')
let onewin = win_getid()
call setline(1, ['aaa', 'bbb', 'ccc'])
- new two
+ new Xtwo
let twobuf = bufnr('%')
let twowin = win_getid()
call setline(1, ['aaa', 'bbb', 'ccc'])
@@ -63,7 +63,24 @@ func Test_setpos()
call setpos("'N", [onebuf, 1, 3, 0])
call assert_equal([onebuf, 1, 3, 0], getpos("'N"))
+ " try invalid column and check virtcol()
call win_gotoid(onewin)
+ call setpos("'a", [0, 1, 2, 0])
+ call assert_equal([0, 1, 2, 0], getpos("'a"))
+ call setpos("'a", [0, 1, -5, 0])
+ call assert_equal([0, 1, 2, 0], getpos("'a"))
+ call setpos("'a", [0, 1, 0, 0])
+ call assert_equal([0, 1, 1, 0], getpos("'a"))
+ call setpos("'a", [0, 1, 4, 0])
+ call assert_equal([0, 1, 4, 0], getpos("'a"))
+ call assert_equal(4, virtcol("'a"))
+ call setpos("'a", [0, 1, 5, 0])
+ call assert_equal([0, 1, 5, 0], getpos("'a"))
+ call assert_equal(4, virtcol("'a"))
+ call setpos("'a", [0, 1, 21341234, 0])
+ call assert_equal([0, 1, 21341234, 0], getpos("'a"))
+ call assert_equal(4, virtcol("'a"))
+
bwipe!
call win_gotoid(twowin)
bwipe!
@@ -77,33 +94,43 @@ func Test_marks_cmd()
new Xtwo
call setline(1, ['ccc', 'ddd'])
norm! $mcGmD
+ exe "norm! GVgg\<Esc>G"
w!
b Xone
let a = split(execute('marks'), "\n")
call assert_equal(9, len(a))
- call assert_equal('mark line col file/text', a[0])
- call assert_equal(" ' 2 0 bbb", a[1])
- call assert_equal(' a 1 0 aaa', a[2])
- call assert_equal(' B 2 2 bbb', a[3])
- call assert_equal(' D 2 0 Xtwo', a[4])
- call assert_equal(' " 1 0 aaa', a[5])
- call assert_equal(' [ 1 0 aaa', a[6])
- call assert_equal(' ] 2 0 bbb', a[7])
- call assert_equal(' . 2 0 bbb', a[8])
+ call assert_equal(['mark line col file/text',
+ \ " ' 2 0 bbb",
+ \ ' a 1 0 aaa',
+ \ ' B 2 2 bbb',
+ \ ' D 2 0 Xtwo',
+ \ ' " 1 0 aaa',
+ \ ' [ 1 0 aaa',
+ \ ' ] 2 0 bbb',
+ \ ' . 2 0 bbb'], a)
b Xtwo
let a = split(execute('marks'), "\n")
- call assert_equal(9, len(a))
- call assert_equal('mark line col file/text', a[0])
- call assert_equal(" ' 1 0 ccc", a[1])
- call assert_equal(' c 1 2 ccc', a[2])
- call assert_equal(' B 2 2 Xone', a[3])
- call assert_equal(' D 2 0 ddd', a[4])
- call assert_equal(' " 2 0 ddd', a[5])
- call assert_equal(' [ 1 0 ccc', a[6])
- call assert_equal(' ] 2 0 ddd', a[7])
- call assert_equal(' . 2 0 ddd', a[8])
+ call assert_equal(11, len(a))
+ call assert_equal(['mark line col file/text',
+ \ " ' 1 0 ccc",
+ \ ' c 1 2 ccc',
+ \ ' B 2 2 Xone',
+ \ ' D 2 0 ddd',
+ \ ' " 2 0 ddd',
+ \ ' [ 1 0 ccc',
+ \ ' ] 2 0 ddd',
+ \ ' . 2 0 ddd',
+ \ ' < 1 0 ccc',
+ \ ' > 2 0 ddd'], a)
+ norm! Gdd
+ w!
+ let a = split(execute('marks <>'), "\n")
+ call assert_equal(3, len(a))
+ call assert_equal(['mark line col file/text',
+ \ ' < 1 0 ccc',
+ \ ' > 2 0 -invalid-'], a)
b Xone
delmarks aB
diff --git a/src/nvim/testdir/test_match.vim b/src/nvim/testdir/test_match.vim
index 90dfcf952d..09448ca71b 100644
--- a/src/nvim/testdir/test_match.vim
+++ b/src/nvim/testdir/test_match.vim
@@ -1,6 +1,8 @@
" Test for :match, :2match, :3match, clearmatches(), getmatches(), matchadd(),
" matchaddpos(), matcharg(), matchdelete(), and setmatches().
+source screendump.vim
+
function Test_match()
highlight MyGroup1 term=bold ctermbg=red guibg=red
highlight MyGroup2 term=italic ctermbg=green guibg=green
@@ -147,6 +149,21 @@ function Test_match()
highlight MyGroup3 NONE
endfunc
+func Test_match_error()
+ call assert_fails('match Error', 'E475:')
+ call assert_fails('match Error /', 'E475:')
+ call assert_fails('4match Error /x/', 'E476:')
+ call assert_fails('match Error /x/ x', 'E488:')
+endfunc
+
+func Test_matchadd_error()
+ call assert_fails("call matchadd('GroupDoesNotExist', 'X')", 'E28:')
+ call assert_fails("call matchadd('Search', '\\(')", 'E475:')
+ call assert_fails("call matchadd('Search', 'XXX', 1, 123, 1)", 'E715:')
+ call assert_fails("call matchadd('Error', 'XXX', 1, 3)", 'E798:')
+ call assert_fails("call matchadd('Error', 'XXX', 1, 0)", 'E799:')
+endfunc
+
func Test_matchaddpos()
syntax on
set hlsearch
@@ -215,6 +232,19 @@ func Test_matchaddpos_otherwin()
call assert_equal(screenattr(1,2), screenattr(2,2))
call assert_notequal(screenattr(1,2), screenattr(1,4))
+ let savematches = getmatches(winid)
+ let expect = [
+ \ {'group': 'Search', 'pattern': '4', 'priority': 10, 'id': 4},
+ \ {'group': 'Error', 'id': 5, 'priority': 10, 'pos1': [1, 2, 1], 'pos2': [2, 2, 1]},
+ \]
+ call assert_equal(expect, savematches)
+
+ call clearmatches(winid)
+ call assert_equal([], getmatches(winid))
+
+ call setmatches(savematches, winid)
+ call assert_equal(expect, savematches)
+
wincmd w
bwipe!
call clearmatches()
@@ -248,4 +278,71 @@ func Test_matchaddpos_using_negative_priority()
set hlsearch&
endfunc
+func Test_matchaddpos_error()
+ call assert_fails("call matchaddpos('Error', 1)", 'E686:')
+ call assert_fails("call matchaddpos('Error', [1], 1, 1)", 'E798:')
+ call assert_fails("call matchaddpos('Error', [1], 1, 2)", 'E798:')
+ call assert_fails("call matchaddpos('Error', [1], 1, 0)", 'E799:')
+ call assert_fails("call matchaddpos('Error', [1], 1, 123, 1)", 'E715:')
+ call assert_fails("call matchaddpos('Error', [1], 1, 5, {'window':12345})", 'E957:')
+ " Why doesn't the following error have an error code E...?
+ call assert_fails("call matchaddpos('Error', [{}])", 'E5031:')
+endfunc
+
+func OtherWindowCommon()
+ let lines =<< trim END
+ call setline(1, 'Hello Vim world')
+ let mid = matchadd('Error', 'world', 1)
+ let winid = win_getid()
+ new
+ END
+ call writefile(lines, 'XscriptMatchCommon')
+ let buf = RunVimInTerminal('-S XscriptMatchCommon', #{rows: 12})
+ call term_wait(buf)
+ return buf
+endfunc
+
+func Test_matchdelete_other_window()
+ if !CanRunVimInTerminal()
+ throw 'Skipped: cannot make screendumps'
+ endif
+ let buf = OtherWindowCommon()
+ call term_sendkeys(buf, ":call matchdelete(mid, winid)\<CR>")
+ call VerifyScreenDump(buf, 'Test_matchdelete_1', {})
+
+ call StopVimInTerminal(buf)
+ call delete('XscriptMatchCommon')
+endfunc
+
+func Test_matchdelete_error()
+ call assert_fails("call matchdelete(0)", 'E802:')
+ call assert_fails("call matchdelete(1, -1)", 'E957:')
+endfunc
+
+func Test_matchclear_other_window()
+ if !CanRunVimInTerminal()
+ throw 'Skipped: cannot make screendumps'
+ endif
+ let buf = OtherWindowCommon()
+ call term_sendkeys(buf, ":call clearmatches(winid)\<CR>")
+ call VerifyScreenDump(buf, 'Test_matchclear_1', {})
+
+ call StopVimInTerminal(buf)
+ call delete('XscriptMatchCommon')
+endfunc
+
+func Test_matchadd_other_window()
+ if !CanRunVimInTerminal()
+ throw 'Skipped: cannot make screendumps'
+ endif
+ let buf = OtherWindowCommon()
+ call term_sendkeys(buf, ":call matchadd('Search', 'Hello', 1, -1, #{window: winid})\<CR>")
+ call term_sendkeys(buf, ":\<CR>")
+ call VerifyScreenDump(buf, 'Test_matchadd_1', {})
+
+ call StopVimInTerminal(buf)
+ call delete('XscriptMatchCommon')
+endfunc
+
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_messages.vim b/src/nvim/testdir/test_messages.vim
index 265dee66ce..7fbf04311d 100644
--- a/src/nvim/testdir/test_messages.vim
+++ b/src/nvim/testdir/test_messages.vim
@@ -1,4 +1,4 @@
-" Tests for :messages
+" Tests for :messages, :echomsg, :echoerr
function Test_messages()
let oldmore = &more
@@ -6,6 +6,9 @@ function Test_messages()
set nomore
" Avoid the "message maintainer" line.
let $LANG = ''
+ let $LC_ALL = ''
+ let $LC_MESSAGES = ''
+ let $LC_COLLATE = ''
let arr = map(range(10), '"hello" . v:val')
for s in arr
@@ -65,6 +68,35 @@ func Test_message_completion()
call assert_equal('"message clear', @:)
endfunc
+func Test_echomsg()
+ call assert_equal("\nhello", execute(':echomsg "hello"'))
+ call assert_equal("\n", execute(':echomsg ""'))
+ call assert_equal("\n12345", execute(':echomsg 12345'))
+ call assert_equal("\n[]", execute(':echomsg []'))
+ call assert_equal("\n[1, 2, 3]", execute(':echomsg [1, 2, 3]'))
+ call assert_equal("\n{}", execute(':echomsg {}'))
+ call assert_equal("\n{'a': 1, 'b': 2}", execute(':echomsg {"a": 1, "b": 2}'))
+ if has('float')
+ call assert_equal("\n1.23", execute(':echomsg 1.23'))
+ endif
+ call assert_match("function('<lambda>\\d*')", execute(':echomsg {-> 1234}'))
+endfunc
+
+func Test_echoerr()
+ throw 'skipped: Nvim does not support test_ignore_error()'
+ call test_ignore_error('IgNoRe')
+ call assert_equal("\nIgNoRe hello", execute(':echoerr "IgNoRe hello"'))
+ call assert_equal("\n12345 IgNoRe", execute(':echoerr 12345 "IgNoRe"'))
+ call assert_equal("\n[1, 2, 'IgNoRe']", execute(':echoerr [1, 2, "IgNoRe"]'))
+ call assert_equal("\n{'IgNoRe': 2, 'a': 1}", execute(':echoerr {"a": 1, "IgNoRe": 2}'))
+ if has('float')
+ call assert_equal("\n1.23 IgNoRe", execute(':echoerr 1.23 "IgNoRe"'))
+ endif
+ call test_ignore_error('<lambda>')
+ call assert_match("function('<lambda>\\d*')", execute(':echoerr {-> 1234}'))
+ call test_ignore_error('RESET')
+endfunc
+
func Test_echospace()
set noruler noshowcmd laststatus=1
call assert_equal(&columns - 1, v:echospace)
diff --git a/src/nvim/testdir/test_mksession.vim b/src/nvim/testdir/test_mksession.vim
index b7169444d1..9c9e04be07 100644
--- a/src/nvim/testdir/test_mksession.vim
+++ b/src/nvim/testdir/test_mksession.vim
@@ -2,10 +2,13 @@
scriptencoding latin1
-if !has('multi_byte') || !has('mksession')
+if !has('mksession')
finish
endif
+source shared.vim
+source term_util.vim
+
func Test_mksession()
tabnew
let wrap_save = &wrap
@@ -122,6 +125,34 @@ func Test_mksession_large_winheight()
call delete('Xtest_mks_winheight.out')
endfunc
+func Test_mksession_rtp()
+ if has('win32')
+ " TODO: fix problem with backslashes
+ return
+ endif
+ new
+ set sessionoptions+=options
+ let _rtp=&rtp
+ " Make a real long (invalid) runtimepath value,
+ " that should exceed PATH_MAX (hopefully)
+ let newrtp=&rtp.',~'.repeat('/foobar', 1000)
+ let newrtp.=",".expand("$HOME")."/.vim"
+ let &rtp=newrtp
+
+ " determine expected value
+ let expected=split(&rtp, ',')
+ let expected = map(expected, '"set runtimepath+=".v:val')
+ let expected = ['set runtimepath='] + expected
+ let expected = map(expected, {v,w -> substitute(w, $HOME, "~", "g")})
+
+ mksession! Xtest_mks.out
+ let &rtp=_rtp
+ let li = filter(readfile('Xtest_mks.out'), 'v:val =~# "runtimepath"')
+ call assert_equal(expected, li)
+
+ call delete('Xtest_mks.out')
+endfunc
+
" Verify that arglist is stored correctly to the session file.
func Test_mksession_arglist()
argdel *
diff --git a/src/nvim/testdir/test_mksession_utf8.vim b/src/nvim/testdir/test_mksession_utf8.vim
index 67af3a9ca2..722fd28beb 100644
--- a/src/nvim/testdir/test_mksession_utf8.vim
+++ b/src/nvim/testdir/test_mksession_utf8.vim
@@ -65,34 +65,35 @@ func Test_mksession_utf8()
call wincol()
mksession! test_mks.out
let li = filter(readfile('test_mks.out'), 'v:val =~# "\\(^ *normal! 0\\|^ *exe ''normal!\\)"')
- let expected = [
- \ 'normal! 016|',
- \ 'normal! 016|',
- \ 'normal! 016|',
- \ 'normal! 08|',
- \ 'normal! 08|',
- \ 'normal! 016|',
- \ 'normal! 016|',
- \ 'normal! 016|',
- \ " exe 'normal! ' . s:c . '|zs' . 16 . '|'",
- \ " normal! 016|",
- \ " exe 'normal! ' . s:c . '|zs' . 16 . '|'",
- \ " normal! 016|",
- \ " exe 'normal! ' . s:c . '|zs' . 16 . '|'",
- \ " normal! 016|",
- \ " exe 'normal! ' . s:c . '|zs' . 8 . '|'",
- \ " normal! 08|",
- \ " exe 'normal! ' . s:c . '|zs' . 8 . '|'",
- \ " normal! 08|",
- \ " exe 'normal! ' . s:c . '|zs' . 16 . '|'",
- \ " normal! 016|",
- \ " exe 'normal! ' . s:c . '|zs' . 16 . '|'",
- \ " normal! 016|",
- \ " exe 'normal! ' . s:c . '|zs' . 16 . '|'",
- \ " normal! 016|",
- \ " exe 'normal! ' . s:c . '|zs' . 16 . '|'",
- \ " normal! 016|"
- \ ]
+ let expected =<< trim [DATA]
+ normal! 016|
+ normal! 016|
+ normal! 016|
+ normal! 08|
+ normal! 08|
+ normal! 016|
+ normal! 016|
+ normal! 016|
+ exe 'normal! ' . s:c . '|zs' . 16 . '|'
+ normal! 016|
+ exe 'normal! ' . s:c . '|zs' . 16 . '|'
+ normal! 016|
+ exe 'normal! ' . s:c . '|zs' . 16 . '|'
+ normal! 016|
+ exe 'normal! ' . s:c . '|zs' . 8 . '|'
+ normal! 08|
+ exe 'normal! ' . s:c . '|zs' . 8 . '|'
+ normal! 08|
+ exe 'normal! ' . s:c . '|zs' . 16 . '|'
+ normal! 016|
+ exe 'normal! ' . s:c . '|zs' . 16 . '|'
+ normal! 016|
+ exe 'normal! ' . s:c . '|zs' . 16 . '|'
+ normal! 016|
+ exe 'normal! ' . s:c . '|zs' . 16 . '|'
+ normal! 016|
+ [DATA]
+
call assert_equal(expected, li)
tabclose!
diff --git a/src/nvim/testdir/test_modeline.vim b/src/nvim/testdir/test_modeline.vim
index 1e196e07f0..9bdada616c 100644
--- a/src/nvim/testdir/test_modeline.vim
+++ b/src/nvim/testdir/test_modeline.vim
@@ -5,12 +5,30 @@ func Test_modeline_invalid()
call writefile(['vi:0', 'nothing'], 'Xmodeline')
let modeline = &modeline
set modeline
- call assert_fails('set Xmodeline', 'E518:')
+ call assert_fails('split Xmodeline', 'E518:')
+
+ " Missing end colon (ignored).
+ call writefile(['// vim: set ts=2'], 'Xmodeline')
+ edit Xmodeline_version
+ call assert_equal(8, &ts)
+ bwipe!
+
+ " Missing colon at beginning (ignored).
+ call writefile(['// vim set ts=2:'], 'Xmodeline')
+ edit Xmodeline_version
+ call assert_equal(8, &ts)
+ bwipe!
+
+ " Missing space after vim (ignored).
+ call writefile(['// vim:ts=2:'], 'Xmodeline')
+ edit Xmodeline_version
+ call assert_equal(8, &ts)
+ bwipe!
let &modeline = modeline
bwipe!
call delete('Xmodeline')
- endfunc
+endfunc
func Test_modeline_filetype()
call writefile(['vim: set ft=c :', 'nothing'], 'Xmodeline_filetype')
@@ -60,8 +78,99 @@ func Test_modeline_keymap()
set keymap= iminsert=0 imsearch=-1
endfunc
+func Test_modeline_version()
+ let modeline = &modeline
+ set modeline
+
+ " Test with vim:{vers}: (version {vers} or later).
+ call writefile(['// vim' .. v:version .. ': ts=2:'], 'Xmodeline_version')
+ edit Xmodeline_version
+ call assert_equal(2, &ts)
+ bwipe!
+
+ call writefile(['// vim' .. (v:version - 100) .. ': ts=2:'], 'Xmodeline_version')
+ edit Xmodeline_version
+ call assert_equal(2, &ts)
+ bwipe!
+
+ call writefile(['// vim' .. (v:version + 100) .. ': ts=2:'], 'Xmodeline_version')
+ edit Xmodeline_version
+ call assert_equal(8, &ts)
+ bw!
+
+ " Test with vim>{vers}: (version after {vers}).
+ call writefile(['// vim>' .. v:version .. ': ts=2:'], 'Xmodeline_version')
+ edit Xmodeline_version
+ call assert_equal(8, &ts)
+ bwipe!
+
+ call writefile(['// vim>' .. (v:version - 100) .. ': ts=2:'], 'Xmodeline_version')
+ edit Xmodeline_version
+ call assert_equal(2, &ts)
+ bwipe!
+
+ call writefile(['// vim>' .. (v:version + 100) .. ': ts=2:'], 'Xmodeline_version')
+ edit Xmodeline_version
+ call assert_equal(8, &ts)
+ bwipe!
+
+ " Test with vim<{vers}: (version before {vers}).
+ call writefile(['// vim<' .. v:version .. ': ts=2:'], 'Xmodeline_version')
+ edit Xmodeline_version
+ call assert_equal(8, &ts)
+ bwipe!
+
+ call writefile(['// vim<' .. (v:version - 100) .. ': ts=2:'], 'Xmodeline_version')
+ edit Xmodeline_version
+ call assert_equal(8, &ts)
+ bwipe!
+
+ call writefile(['// vim<' .. (v:version + 100) .. ': ts=2:'], 'Xmodeline_version')
+ edit Xmodeline_version
+ call assert_equal(2, &ts)
+ bwipe!
+
+ " Test with vim={vers}: (version {vers} only).
+ call writefile(['// vim=' .. v:version .. ': ts=2:'], 'Xmodeline_version')
+ edit Xmodeline_version
+ call assert_equal(2, &ts)
+ bwipe!
+
+ call writefile(['// vim=' .. (v:version - 100) .. ': ts=2:'], 'Xmodeline_version')
+ edit Xmodeline_version
+ call assert_equal(8, &ts)
+ bwipe!
+
+ call writefile(['// vim=' .. (v:version + 100) .. ': ts=2:'], 'Xmodeline_version')
+ edit Xmodeline_version
+ call assert_equal(8, &ts)
+ bwipe!
+
+ let &modeline = modeline
+ call delete('Xmodeline_version')
+endfunc
+
+func Test_modeline_colon()
+ let modeline = &modeline
+ set modeline
+
+ call writefile(['// vim: set showbreak=\: ts=2: sw=2'], 'Xmodeline_colon')
+ edit Xmodeline_colon
+
+ " backlash colon should become colon.
+ call assert_equal(':', &showbreak)
+
+ " 'ts' should be set.
+ " 'sw' should be ignored because it is after the end colon.
+ call assert_equal(2, &ts)
+ call assert_equal(8, &sw)
+
+ let &modeline = modeline
+ call delete('Xmodeline_colon')
+endfunc
+
func s:modeline_fails(what, text, error)
- if !exists('+' . a:what)
+ if !exists('+' .. a:what)
return
endif
let fname = "Xmodeline_fails_" . a:what
@@ -119,7 +228,7 @@ func Test_modeline_fails_always()
call s:modeline_fails('mkspellmem', 'mkspellmem=Something()', 'E520:')
call s:modeline_fails('mzschemedll', 'mzschemedll=Something()', 'E520:')
call s:modeline_fails('mzschemegcdll', 'mzschemegcdll=Something()', 'E520:')
- call s:modeline_fails('modelineexpr', 'modelineexpr=Something()', 'E520:')
+ call s:modeline_fails('modelineexpr', 'modelineexpr', 'E520:')
call s:modeline_fails('omnifunc', 'omnifunc=Something()', 'E520:')
call s:modeline_fails('operatorfunc', 'operatorfunc=Something()', 'E520:')
call s:modeline_fails('perldll', 'perldll=Something()', 'E520:')
diff --git a/src/nvim/testdir/test_normal.vim b/src/nvim/testdir/test_normal.vim
index 532beb9c39..ad6d325510 100644
--- a/src/nvim/testdir/test_normal.vim
+++ b/src/nvim/testdir/test_normal.vim
@@ -2,12 +2,12 @@
source shared.vim
-func! Setup_NewWindow()
+func Setup_NewWindow()
10new
call setline(1, range(1,100))
endfunc
-func! MyFormatExpr()
+func MyFormatExpr()
" Adds '->$' at lines having numbers followed by trailing whitespace
for ln in range(v:lnum, v:lnum+v:count-1)
let line = getline(ln)
@@ -17,7 +17,7 @@ func! MyFormatExpr()
endfor
endfunc
-func! CountSpaces(type, ...)
+func CountSpaces(type, ...)
" for testing operatorfunc
" will count the number of spaces
" and return the result in g:a
@@ -37,7 +37,7 @@ func! CountSpaces(type, ...)
let @@ = reg_save
endfunc
-func! OpfuncDummy(type, ...)
+func OpfuncDummy(type, ...)
" for testing operatorfunc
let g:opt=&linebreak
@@ -81,7 +81,7 @@ fun! Test_normal00_optrans()
bw!
endfunc
-func! Test_normal01_keymodel()
+func Test_normal01_keymodel()
call Setup_NewWindow()
" Test 1: depending on 'keymodel' <s-down> does something different
50
@@ -115,7 +115,7 @@ func! Test_normal01_keymodel()
bw!
endfunc
-func! Test_normal02_selectmode()
+func Test_normal02_selectmode()
" some basic select mode tests
call Setup_NewWindow()
50
@@ -129,7 +129,7 @@ func! Test_normal02_selectmode()
bw!
endfunc
-func! Test_normal02_selectmode2()
+func Test_normal02_selectmode2()
" some basic select mode tests
call Setup_NewWindow()
50
@@ -139,7 +139,7 @@ func! Test_normal02_selectmode2()
bw!
endfunc
-func! Test_normal03_join()
+func Test_normal03_join()
" basic join test
call Setup_NewWindow()
50
@@ -159,7 +159,7 @@ func! Test_normal03_join()
bw!
endfunc
-func! Test_normal04_filter()
+func Test_normal04_filter()
" basic filter test
" only test on non windows platform
if has('win32')
@@ -185,7 +185,7 @@ func! Test_normal04_filter()
bw!
endfunc
-func! Test_normal05_formatexpr()
+func Test_normal05_formatexpr()
" basic formatexpr test
call Setup_NewWindow()
%d_
@@ -222,7 +222,7 @@ func Test_normal05_formatexpr_setopt()
set formatexpr=
endfunc
-func! Test_normal06_formatprg()
+func Test_normal06_formatprg()
" basic test for formatprg
" only test on non windows platform
if has('win32')
@@ -256,7 +256,7 @@ func! Test_normal06_formatprg()
call delete('Xsed_format.sh')
endfunc
-func! Test_normal07_internalfmt()
+func Test_normal07_internalfmt()
" basic test for internal formmatter to textwidth of 12
let list=range(1,11)
call map(list, 'v:val." "')
@@ -270,7 +270,7 @@ func! Test_normal07_internalfmt()
bw!
endfunc
-func! Test_normal08_fold()
+func Test_normal08_fold()
" basic tests for foldopen/folddelete
if !has("folding")
return
@@ -329,7 +329,7 @@ func! Test_normal08_fold()
bw!
endfunc
-func! Test_normal09_operatorfunc()
+func Test_normal09_operatorfunc()
" Test operatorfunc
call Setup_NewWindow()
" Add some spaces for counting
@@ -359,7 +359,7 @@ func! Test_normal09_operatorfunc()
bw!
endfunc
-func! Test_normal09a_operatorfunc()
+func Test_normal09a_operatorfunc()
" Test operatorfunc
call Setup_NewWindow()
" Add some spaces for counting
@@ -385,7 +385,7 @@ func! Test_normal09a_operatorfunc()
unlet! g:opt
endfunc
-func! Test_normal10_expand()
+func Test_normal10_expand()
" Test for expand()
10new
call setline(1, ['1', 'ifooar,,cbar'])
@@ -420,7 +420,7 @@ func! Test_normal10_expand()
bw!
endfunc
-func! Test_normal11_showcmd()
+func Test_normal11_showcmd()
" test for 'showcmd'
10new
exe "norm! ofoobar\<esc>"
@@ -435,7 +435,7 @@ func! Test_normal11_showcmd()
bw!
endfunc
-func! Test_normal12_nv_error()
+func Test_normal12_nv_error()
" Test for nv_error
10new
call setline(1, range(1,5))
@@ -445,7 +445,7 @@ func! Test_normal12_nv_error()
bw!
endfunc
-func! Test_normal13_help()
+func Test_normal13_help()
" Test for F1
call assert_equal(1, winnr())
call feedkeys("\<f1>", 'txi')
@@ -454,7 +454,7 @@ func! Test_normal13_help()
bw!
endfunc
-func! Test_normal14_page()
+func Test_normal14_page()
" basic test for Ctrl-F and Ctrl-B
call Setup_NewWindow()
exe "norm! \<c-f>"
@@ -488,7 +488,7 @@ func! Test_normal14_page()
bw!
endfunc
-func! Test_normal14_page_eol()
+func Test_normal14_page_eol()
10new
norm oxxxxxxx
exe "norm 2\<c-f>"
@@ -497,7 +497,7 @@ func! Test_normal14_page_eol()
bw!
endfunc
-func! Test_normal15_z_scroll_vert()
+func Test_normal15_z_scroll_vert()
" basic test for z commands that scroll the window
call Setup_NewWindow()
100
@@ -586,7 +586,7 @@ func! Test_normal15_z_scroll_vert()
bw!
endfunc
-func! Test_normal16_z_scroll_hor()
+func Test_normal16_z_scroll_hor()
" basic test for z commands that scroll the window
10new
15vsp
@@ -652,7 +652,7 @@ func! Test_normal16_z_scroll_hor()
bw!
endfunc
-func! Test_normal17_z_scroll_hor2()
+func Test_normal17_z_scroll_hor2()
" basic test for z commands that scroll the window
" using 'sidescrolloff' setting
10new
@@ -719,7 +719,7 @@ func! Test_normal17_z_scroll_hor2()
bw!
endfunc
-func! Test_normal18_z_fold()
+func Test_normal18_z_fold()
" basic tests for foldopen/folddelete
if !has("folding")
return
@@ -1090,7 +1090,7 @@ func! Test_normal18_z_fold()
bw!
endfunc
-func! Test_normal19_z_spell()
+func Test_normal19_z_spell()
if !has("spell") || !has('syntax')
return
endif
@@ -1245,7 +1245,7 @@ func! Test_normal19_z_spell()
bw!
endfunc
-func! Test_normal20_exmode()
+func Test_normal20_exmode()
if !has("unix")
" Reading from redirected file doesn't work on MS-Windows
return
@@ -1263,24 +1263,38 @@ func! Test_normal20_exmode()
bw!
endfunc
-func! Test_normal21_nv_hat()
- set hidden
- new
- " to many buffers opened already, will not work
- "call assert_fails(":b#", 'E23')
- "call assert_equal('', @#)
- e Xfoobar
- e Xfile2
- call feedkeys("\<c-^>", 't')
- call assert_equal("Xfile2", fnamemodify(bufname('%'), ':t'))
- call feedkeys("f\<c-^>", 't')
- call assert_equal("Xfile2", fnamemodify(bufname('%'), ':t'))
- " clean up
- set nohidden
- bw!
+func Test_normal21_nv_hat()
+
+ " Edit a fresh file and wipe the buffer list so that there is no alternate
+ " file present. Next, check for the expected command failures.
+ edit Xfoo | %bw
+ call assert_fails(':buffer #', 'E86')
+ call assert_fails(':execute "normal! \<C-^>"', 'E23')
+
+ " Test for the expected behavior when switching between two named buffers.
+ edit Xfoo | edit Xbar
+ call feedkeys("\<C-^>", 'tx')
+ call assert_equal('Xfoo', fnamemodify(bufname('%'), ':t'))
+ call feedkeys("\<C-^>", 'tx')
+ call assert_equal('Xbar', fnamemodify(bufname('%'), ':t'))
+
+ " Test for the expected behavior when only one buffer is named.
+ enew | let l:nr = bufnr('%')
+ call feedkeys("\<C-^>", 'tx')
+ call assert_equal('Xbar', fnamemodify(bufname('%'), ':t'))
+ call feedkeys("\<C-^>", 'tx')
+ call assert_equal('', bufname('%'))
+ call assert_equal(l:nr, bufnr('%'))
+
+ " Test that no action is taken by "<C-^>" when an operator is pending.
+ edit Xfoo
+ call feedkeys("ci\<C-^>", 'tx')
+ call assert_equal('Xfoo', fnamemodify(bufname('%'), ':t'))
+
+ %bw!
endfunc
-func! Test_normal22_zet()
+func Test_normal22_zet()
" Test for ZZ
" let shell = &shell
" let &shell = 'sh'
@@ -1308,7 +1322,7 @@ func! Test_normal22_zet()
" let &shell = shell
endfunc
-func! Test_normal23_K()
+func Test_normal23_K()
" Test for K command
new
call append(0, ['helphelp.txt', 'man', 'aa%bb', 'cc|dd'])
@@ -1353,8 +1367,9 @@ func! Test_normal23_K()
return
endif
- if has('mac')
- " In MacOS, the option for specifying a pager is different
+ let not_gnu_man = has('mac') || has('bsd')
+ if not_gnu_man
+ " In MacOS and BSD, the option for specifying a pager is different
set keywordprg=man\ -P\ cat
else
set keywordprg=man\ --pager=cat
@@ -1362,7 +1377,7 @@ func! Test_normal23_K()
" Test for using man
2
let a = execute('unsilent norm! K')
- if has('mac')
+ if not_gnu_man
call assert_match("man -P cat 'man'", a)
else
call assert_match("man --pager=cat 'man'", a)
@@ -1373,7 +1388,7 @@ func! Test_normal23_K()
bw!
endfunc
-func! Test_normal24_rot13()
+func Test_normal24_rot13()
" Testing for g?? g?g?
new
call append(0, 'abcdefghijklmnopqrstuvwxyzäüö')
@@ -1387,7 +1402,7 @@ func! Test_normal24_rot13()
bw!
endfunc
-func! Test_normal25_tag()
+func Test_normal25_tag()
" Testing for CTRL-] g CTRL-] g]
" CTRL-W g] CTRL-W CTRL-] CTRL-W g CTRL-]
h
@@ -1454,7 +1469,7 @@ func! Test_normal25_tag()
helpclose
endfunc
-func! Test_normal26_put()
+func Test_normal26_put()
" Test for ]p ]P [p and [P
new
call append(0, ['while read LINE', 'do', ' ((count++))', ' if [ $? -ne 0 ]; then', " echo 'Error writing file'", ' fi', 'done'])
@@ -1473,7 +1488,7 @@ func! Test_normal26_put()
bw!
endfunc
-func! Test_normal27_bracket()
+func Test_normal27_bracket()
" Test for [' [` ]' ]`
call Setup_NewWindow()
1,21s/.\+/ & b/
@@ -1524,7 +1539,7 @@ func! Test_normal27_bracket()
bw!
endfunc
-func! Test_normal28_parenthesis()
+func Test_normal28_parenthesis()
" basic testing for ( and )
new
call append(0, ['This is a test. With some sentences!', '', 'Even with a question? And one more. And no sentence here'])
@@ -1549,52 +1564,94 @@ endfunc
fun! Test_normal29_brace()
" basic test for { and } movements
- let text= ['A paragraph begins after each empty line, and also at each of a set of',
- \ 'paragraph macros, specified by the pairs of characters in the ''paragraphs''',
- \ 'option. The default is "IPLPPPQPP TPHPLIPpLpItpplpipbp", which corresponds to',
- \ 'the macros ".IP", ".LP", etc. (These are nroff macros, so the dot must be in',
- \ 'the first column). A section boundary is also a paragraph boundary.',
- \ 'Note that a blank line (only containing white space) is NOT a paragraph',
- \ 'boundary.',
- \ '',
- \ '',
- \ 'Also note that this does not include a ''{'' or ''}'' in the first column. When',
- \ 'the ''{'' flag is in ''cpoptions'' then ''{'' in the first column is used as a',
- \ 'paragraph boundary |posix|.',
- \ '{',
- \ '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']
+ let text =<< trim [DATA]
+ A paragraph begins after each empty line, and also at each of a set of
+ paragraph macros, specified by the pairs of characters in the 'paragraphs'
+ option. The default is "IPLPPPQPP TPHPLIPpLpItpplpipbp", which corresponds to
+ the macros ".IP", ".LP", etc. (These are nroff macros, so the dot must be in
+ the first column). A section boundary is also a paragraph boundary.
+ Note that a blank line (only containing white space) is NOT a paragraph
+ boundary.
+
+
+ Also note that this does not include a '{' or '}' in the first column. When
+ the '{' flag is in 'cpoptions' then '{' in the first column is used as a
+ paragraph boundary |posix|.
+ {
+ 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]
+
new
call append(0, text)
1
norm! 0d2}
- call assert_equal(['.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', ''], getline(1,'$'))
+
+ let expected =<< trim [DATA]
+ .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! 0d}
- call assert_equal(['.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', ''], getline(1, '$'))
+
+ let expected =<< trim [DATA]
+ .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{
- call assert_equal(['.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:', ''], getline(1, '$'))
+
+ let expected =<< trim [DATA]
+ .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:
+
+ [DATA]
+ call assert_equal(expected, getline(1, '$'))
+
norm! d{
- call assert_equal(['.LPIt does not matter, if afterwards some', 'more characters follow.', ''], getline(1,'$'))
+
+ let expected =<< trim [DATA]
+ .LPIt does not matter, if afterwards some
+ more characters follow.
+
+ [DATA]
+ call assert_equal(expected, getline(1, '$'))
+
" Test with { in cpooptions
%d
call append(0, text)
@@ -1602,21 +1659,62 @@ fun! Test_normal29_brace()
" set cpo+={
" 1
" norm! 0d2}
- " call assert_equal(['{', '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', ''], getline(1,'$'))
+ " 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}
- " call assert_equal(['{', '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', ''], getline(1,'$'))
+ " 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}
- " call assert_equal(['{', 'This is no paragraph', 'unless the ''{'' is set', 'in ''cpoptions''', '}', ''], getline(1,'$'))
+ "
+ " let expected =<< trim [DATA]
+ " {
+ " This is no paragraph
+ " unless the '{' is set
+ " in 'cpoptions'
+ " }
+
+ " [DATA]
+ " call assert_equal(expected, getline(1, '$'))
" clean up
set cpo-={
@@ -1718,7 +1816,7 @@ fun! Test_normal31_r_cmd()
bw!
endfunc
-func! Test_normal32_g_cmd1()
+func Test_normal32_g_cmd1()
" Test for g*, g#
new
call append(0, ['abc.x_foo', 'x_foobar.abc'])
@@ -1798,6 +1896,7 @@ fun! Test_normal33_g_cmd2()
set wrap listchars= sbr=
let lineA='abcdefghijklmnopqrstuvwxyz'
let lineB='0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
+ let lineC='0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567890123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
$put =lineA
$put =lineB
@@ -1831,9 +1930,30 @@ fun! Test_normal33_g_cmd2()
call assert_equal(15, col('.'))
call assert_equal('l', getreg(0))
+ norm! 2ggdd
+ $put =lineC
+
+ " Test for gM
+ norm! gMyl
+ call assert_equal(73, col('.'))
+ call assert_equal('0', getreg(0))
+ " Test for 20gM
+ norm! 20gMyl
+ call assert_equal(29, col('.'))
+ call assert_equal('S', getreg(0))
+ " Test for 60gM
+ norm! 60gMyl
+ call assert_equal(87, col('.'))
+ call assert_equal('E', getreg(0))
+
+ " Test for g Ctrl-G
+ set ff=unix
+ let a=execute(":norm! g\<c-g>")
+ call assert_match('Col 87 of 144; Line 2 of 2; Word 1 of 1; Byte 88 of 146', a)
+
" Test for gI
norm! gIfoo
- call assert_equal(['', 'fooabcdefghijk lmno0123456789AMNOPQRSTUVWXYZ'], getline(1,'$'))
+ call assert_equal(['', 'foo0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567890123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'], getline(1,'$'))
" Test for gi
wincmd c
@@ -1849,7 +1969,7 @@ fun! Test_normal33_g_cmd2()
bw!
endfunc
-func! Test_g_ctrl_g()
+func Test_g_ctrl_g()
new
let a = execute(":norm! g\<c-g>")
@@ -2139,7 +2259,7 @@ fun! Test_normal41_insert_reg()
bw!
endfunc
-func! Test_normal42_halfpage()
+func Test_normal42_halfpage()
" basic test for Ctrl-D and Ctrl-U
call Setup_NewWindow()
call assert_equal(5, &scroll)
@@ -2207,7 +2327,7 @@ fun! Test_normal43_textobject1()
bw!
endfunc
-func! Test_normal44_textobjects2()
+func Test_normal44_textobjects2()
" basic testing for is and as text objects
new
call append(0, ['This is a test. With some sentences!', '', 'Even with a question? And one more. And no sentence here'])
@@ -2262,7 +2382,7 @@ func! Test_normal44_textobjects2()
bw!
endfunc
-func! Test_normal45_drop()
+func Test_normal45_drop()
if !has('dnd')
" The ~ register does not exist
call assert_beeps('norm! "~')
@@ -2280,7 +2400,7 @@ func! Test_normal45_drop()
bw!
endfunc
-func! Test_normal46_ignore()
+func Test_normal46_ignore()
new
" How to test this?
" let's just for now test, that the buffer
@@ -2299,7 +2419,7 @@ func! Test_normal46_ignore()
bw!
endfunc
-func! Test_normal47_visual_buf_wipe()
+func Test_normal47_visual_buf_wipe()
" This was causing a crash or ml_get error.
enew!
call setline(1,'xxx')
@@ -2313,7 +2433,7 @@ func! Test_normal47_visual_buf_wipe()
set nomodified
endfunc
-func! Test_normal47_autocmd()
+func Test_normal47_autocmd()
" disabled, does not seem to be possible currently
throw "Skipped: not possible to test cursorhold autocmd while waiting for input in normal_cmd"
new
@@ -2331,14 +2451,14 @@ func! Test_normal47_autocmd()
bw!
endfunc
-func! Test_normal48_wincmd()
+func Test_normal48_wincmd()
new
exe "norm! \<c-w>c"
call assert_equal(1, winnr('$'))
call assert_fails(":norm! \<c-w>c", "E444")
endfunc
-func! Test_normal49_counts()
+func Test_normal49_counts()
new
call setline(1, 'one two three four five six seven eight nine ten')
1
@@ -2347,7 +2467,7 @@ func! Test_normal49_counts()
bw!
endfunc
-func! Test_normal50_commandline()
+func Test_normal50_commandline()
if !has("timers") || !has("cmdline_hist") || !has("vertsplit")
return
endif
@@ -2378,7 +2498,7 @@ func! Test_normal50_commandline()
bw!
endfunc
-func! Test_normal51_FileChangedRO()
+func Test_normal51_FileChangedRO()
if !has("autocmd")
return
endif
@@ -2395,7 +2515,7 @@ func! Test_normal51_FileChangedRO()
call delete("Xreadonly.log")
endfunc
-func! Test_normal52_rl()
+func Test_normal52_rl()
if !has("rightleft")
return
endif
@@ -2428,7 +2548,7 @@ func! Test_normal52_rl()
bw!
endfunc
-func! Test_normal53_digraph()
+func Test_normal53_digraph()
if !has('digraphs')
return
endif
@@ -2474,6 +2594,15 @@ func Test_normal_large_count()
bwipe!
endfunc
+func Test_delete_until_paragraph()
+ new
+ normal grádv}
+ call assert_equal('á', getline(1))
+ normal grád}
+ call assert_equal('', getline(1))
+ bwipe!
+endfunc
+
" Test for the gr (virtual replace) command
" Test for the bug fixed by 7.4.387
func Test_gr_command()
@@ -2516,13 +2645,27 @@ func Test_changelist()
let &ul = save_ul
endfunc
-func Test_delete_until_paragraph()
- new
- normal grádv}
- call assert_equal('á', getline(1))
- normal grád}
- call assert_equal('', getline(1))
- bwipe!
+func Test_nv_hat_count()
+ %bwipeout!
+ let l:nr = bufnr('%') + 1
+ call assert_fails(':execute "normal! ' . l:nr . '\<C-^>"', 'E92')
+
+ edit Xfoo
+ let l:foo_nr = bufnr('Xfoo')
+
+ edit Xbar
+ let l:bar_nr = bufnr('Xbar')
+
+ " Make sure we are not just using the alternate file.
+ edit Xbaz
+
+ call feedkeys(l:foo_nr . "\<C-^>", 'tx')
+ call assert_equal('Xfoo', fnamemodify(bufname('%'), ':t'))
+
+ call feedkeys(l:bar_nr . "\<C-^>", 'tx')
+ call assert_equal('Xbar', fnamemodify(bufname('%'), ':t'))
+
+ %bwipeout!
endfunc
func Test_message_when_using_ctrl_c()
@@ -2617,3 +2760,105 @@ Piece of Java
close!
endfunc
+
+fun! Test_normal_gdollar_cmd()
+ if !has("jumplist")
+ return
+ endif
+ " Tests for g cmds
+ call Setup_NewWindow()
+ " Make long lines that will wrap
+ %s/$/\=repeat(' foobar', 10)/
+ 20vsp
+ set wrap
+ " Test for g$ with count
+ norm! gg
+ norm! 0vg$y
+ call assert_equal(20, col("'>"))
+ call assert_equal('1 foobar foobar foob', getreg(0))
+ norm! gg
+ norm! 0v4g$y
+ call assert_equal(72, col("'>"))
+ call assert_equal('1 foobar foobar foobar foobar foobar foobar foobar foobar foobar foobar'.."\n", getreg(0))
+ norm! gg
+ norm! 0v6g$y
+ call assert_equal(40, col("'>"))
+ call assert_equal('1 foobar foobar foobar foobar foobar foobar foobar foobar foobar foobar'.. "\n"..
+ \ '2 foobar foobar foobar foobar foobar foo', getreg(0))
+ set nowrap
+ " clean up
+ norm! gg
+ norm! 0vg$y
+ call assert_equal(20, col("'>"))
+ call assert_equal('1 foobar foobar foob', getreg(0))
+ norm! gg
+ norm! 0v4g$y
+ call assert_equal(20, col("'>"))
+ call assert_equal('1 foobar foobar foobar foobar foobar foobar foobar foobar foobar foobar'.. "\n"..
+ \ '2 foobar foobar foobar foobar foobar foobar foobar foobar foobar foobar'.. "\n"..
+ \ '3 foobar foobar foobar foobar foobar foobar foobar foobar foobar foobar'.. "\n"..
+ \ '4 foobar foobar foob', getreg(0))
+ norm! gg
+ norm! 0v6g$y
+ call assert_equal(20, col("'>"))
+ call assert_equal('1 foobar foobar foobar foobar foobar foobar foobar foobar foobar foobar'.. "\n"..
+ \ '2 foobar foobar foobar foobar foobar foobar foobar foobar foobar foobar'.. "\n"..
+ \ '3 foobar foobar foobar foobar foobar foobar foobar foobar foobar foobar'.. "\n"..
+ \ '4 foobar foobar foobar foobar foobar foobar foobar foobar foobar foobar'.. "\n"..
+ \ '5 foobar foobar foobar foobar foobar foobar foobar foobar foobar foobar'.. "\n"..
+ \ '6 foobar foobar foob', getreg(0))
+ " Move to last line, also down movement is not possible, should still move
+ " the cursor to the last visible char
+ norm! G
+ norm! 0v6g$y
+ call assert_equal(20, col("'>"))
+ call assert_equal('100 foobar foobar fo', getreg(0))
+ bw!
+endfunc
+
+func Test_normal_gk()
+ " needs 80 column new window
+ new
+ vert 80new
+ put =[repeat('x',90)..' {{{1', 'x {{{1']
+ norm! gk
+ " In a 80 column wide terminal the window will be only 78 char
+ " (because Vim will leave space for the other window),
+ " but if the terminal is larger, it will be 80 chars, so verify the
+ " cursor column correctly.
+ call assert_equal(winwidth(0)+1, col('.'))
+ call assert_equal(winwidth(0)+1, virtcol('.'))
+ norm! j
+ call assert_equal(6, col('.'))
+ call assert_equal(6, virtcol('.'))
+ norm! gk
+ call assert_equal(95, col('.'))
+ call assert_equal(95, virtcol('.'))
+ bw!
+ bw!
+
+ " needs 80 column new window
+ new
+ vert 80new
+ set number
+ set numberwidth=10
+ set cpoptions+=n
+ put =[repeat('0',90), repeat('1',90)]
+ norm! 075l
+ call assert_equal(76, col('.'))
+ norm! gk
+ call assert_equal(1, col('.'))
+ norm! gk
+ call assert_equal(76, col('.'))
+ norm! gk
+ call assert_equal(1, col('.'))
+ norm! gj
+ call assert_equal(76, col('.'))
+ norm! gj
+ call assert_equal(1, col('.'))
+ norm! gj
+ call assert_equal(76, col('.'))
+ bw!
+ bw!
+ set cpoptions& number& numberwidth&
+endfunc
diff --git a/src/nvim/testdir/test_number.vim b/src/nvim/testdir/test_number.vim
index 59debcea0d..3c9afc41d5 100644
--- a/src/nvim/testdir/test_number.vim
+++ b/src/nvim/testdir/test_number.vim
@@ -252,3 +252,14 @@ func Test_numberwidth_adjusted()
call s:compare_lines(expect, lines)
call s:close_windows()
endfunc
+
+" This was causing a memcheck error
+func Test_relativenumber_uninitialised()
+ new
+ set rnu
+ call setline(1, ["a", "b"])
+ redraw
+ call feedkeys("j", 'xt')
+ redraw
+ bwipe!
+endfunc
diff --git a/src/nvim/testdir/test_options.vim b/src/nvim/testdir/test_options.vim
index f4f5cbca61..04a5c62f66 100644
--- a/src/nvim/testdir/test_options.vim
+++ b/src/nvim/testdir/test_options.vim
@@ -42,6 +42,13 @@ function Test_wildchar()
set wildchar&
endfunction
+func Test_wildoptions()
+ set wildoptions=
+ set wildoptions+=tagfile
+ set wildoptions+=tagfile
+ call assert_equal('tagfile', &wildoptions)
+endfunc
+
function! Test_options()
let caught = 'ok'
try
@@ -277,6 +284,21 @@ func Test_set_errors()
call assert_fails('set t_foo=', 'E846:')
endfunc
+" Must be executed before other tests that set 'term'.
+func Test_000_term_option_verbose()
+ if has('nvim') || has('gui_running')
+ return
+ endif
+ let verb_cm = execute('verbose set t_cm')
+ call assert_notmatch('Last set from', verb_cm)
+
+ let term_save = &term
+ set term=ansi
+ let verb_cm = execute('verbose set t_cm')
+ call assert_match('Last set from.*test_options.vim', verb_cm)
+ let &term = term_save
+endfunc
+
func Test_set_ttytype()
" Nvim does not support 'ttytype'.
if !has('nvim') && !has('gui_running') && has('unix')
@@ -476,13 +498,19 @@ func Test_shortmess_F2()
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', ''))
@@ -490,3 +518,61 @@ func Test_shortmess_F2()
bwipe
bwipe
endfunc
+
+func Test_local_scrolloff()
+ set so=5
+ set siso=7
+ split
+ call assert_equal(5, &so)
+ setlocal so=3
+ call assert_equal(3, &so)
+ wincmd w
+ call assert_equal(5, &so)
+ wincmd w
+ setlocal so<
+ call assert_equal(5, &so)
+ setlocal so=0
+ call assert_equal(0, &so)
+ setlocal so=-1
+ call assert_equal(5, &so)
+
+ call assert_equal(7, &siso)
+ setlocal siso=3
+ call assert_equal(3, &siso)
+ wincmd w
+ call assert_equal(7, &siso)
+ wincmd w
+ setlocal siso<
+ call assert_equal(7, &siso)
+ setlocal siso=0
+ call assert_equal(0, &siso)
+ setlocal siso=-1
+ call assert_equal(7, &siso)
+
+ close
+ set so&
+ set siso&
+endfunc
+
+func Test_visualbell()
+ set belloff=
+ set visualbell
+ call assert_beeps('normal 0h')
+ set novisualbell
+ set belloff=all
+endfunc
+
+" 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
+
diff --git a/src/nvim/testdir/test_plus_arg_edit.vim b/src/nvim/testdir/test_plus_arg_edit.vim
index f6d31e7626..e91a6e467a 100644
--- a/src/nvim/testdir/test_plus_arg_edit.vim
+++ b/src/nvim/testdir/test_plus_arg_edit.vim
@@ -8,3 +8,31 @@ function Test_edit()
call delete('Xfile1')
call delete('Xfile2')
endfunction
+
+func Test_edit_bad()
+ if !has('multi_byte')
+ finish
+ endif
+
+ " Test loading a utf8 file with bad utf8 sequences.
+ call writefile(["[\xff][\xc0][\xe2\x89\xf0][\xc2\xc2]"], "Xfile")
+ new
+
+ " Without ++bad=..., the default behavior is like ++bad=?
+ e! ++enc=utf8 Xfile
+ call assert_equal('[?][?][???][??]', getline(1))
+
+ e! ++enc=utf8 ++bad=_ Xfile
+ call assert_equal('[_][_][___][__]', getline(1))
+
+ e! ++enc=utf8 ++bad=drop Xfile
+ call assert_equal('[][][][]', getline(1))
+
+ e! ++enc=utf8 ++bad=keep Xfile
+ call assert_equal("[\xff][\xc0][\xe2\x89\xf0][\xc2\xc2]", getline(1))
+
+ call assert_fails('e! ++enc=utf8 ++bad=foo Xfile', 'E474:')
+
+ bw!
+ call delete('Xfile')
+endfunc
diff --git a/src/nvim/testdir/test_popup.vim b/src/nvim/testdir/test_popup.vim
index 53df30bb19..bb0ed6e00c 100644
--- a/src/nvim/testdir/test_popup.vim
+++ b/src/nvim/testdir/test_popup.vim
@@ -1,6 +1,7 @@
" Test for completion menu
source shared.vim
+source screendump.vim
let g:months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']
let g:setting = ''
@@ -680,18 +681,15 @@ func Test_popup_and_window_resize()
call term_sendkeys(buf, "\<c-v>")
call term_wait(buf, 100)
" popup first entry "!" must be at the top
- call WaitFor({-> term_getline(buf, 1) =~ "^!"})
- call assert_match('^!\s*$', term_getline(buf, 1))
+ call WaitForAssert({-> assert_match('^!\s*$', term_getline(buf, 1))})
exe 'resize +' . (h - 1)
call term_wait(buf, 100)
redraw!
" popup shifted down, first line is now empty
- call WaitFor({-> term_getline(buf, 1) == ""})
- call assert_equal('', term_getline(buf, 1))
+ call WaitForAssert({-> assert_equal('', term_getline(buf, 1))})
sleep 100m
" popup is below cursor line and shows first match "!"
- call WaitFor({-> term_getline(buf, term_getcursor(buf)[0] + 1) =~ "^!"})
- call assert_match('^!\s*$', term_getline(buf, term_getcursor(buf)[0] + 1))
+ call WaitForAssert({-> assert_match('^!\s*$', term_getline(buf, term_getcursor(buf)[0] + 1))})
" cursor line also shows !
call assert_match('^!\s*$', term_getline(buf, term_getcursor(buf)[0]))
bwipe!
@@ -735,6 +733,70 @@ func Test_popup_and_preview_autocommand()
bw!
endfunc
+func Test_popup_and_previewwindow_dump()
+ if !CanRunVimInTerminal()
+ return
+ endif
+ call writefile([
+ \ 'set previewheight=9',
+ \ 'silent! pedit',
+ \ 'call setline(1, map(repeat(["ab"], 10), "v:val. v:key"))',
+ \ 'exec "norm! G\<C-E>\<C-E>"',
+ \ ], 'Xscript')
+ let buf = RunVimInTerminal('-S Xscript', {})
+
+ " Test that popup and previewwindow do not overlap.
+ call term_sendkeys(buf, "o\<C-X>\<C-N>")
+ sleep 100m
+ call VerifyScreenDump(buf, 'Test_popup_and_previewwindow_01', {})
+
+ call term_sendkeys(buf, "\<Esc>u")
+ call StopVimInTerminal(buf)
+ call delete('Xscript')
+endfunc
+
+func Test_popup_position()
+ if !CanRunVimInTerminal()
+ return
+ endif
+ let lines =<< trim END
+ 123456789_123456789_123456789_a
+ 123456789_123456789_123456789_b
+ 123
+ END
+ call writefile(lines, 'Xtest')
+ let buf = RunVimInTerminal('Xtest', {})
+ call term_sendkeys(buf, ":vsplit\<CR>")
+
+ " default pumwidth in left window: overlap in right window
+ call term_sendkeys(buf, "GA\<C-N>")
+ call VerifyScreenDump(buf, 'Test_popup_position_01', {'rows': 8})
+ call term_sendkeys(buf, "\<Esc>u")
+
+ " default pumwidth: fill until right of window
+ call term_sendkeys(buf, "\<C-W>l")
+ call term_sendkeys(buf, "GA\<C-N>")
+ call VerifyScreenDump(buf, 'Test_popup_position_02', {'rows': 8})
+
+ " larger pumwidth: used as minimum width
+ call term_sendkeys(buf, "\<Esc>u")
+ call term_sendkeys(buf, ":set pumwidth=30\<CR>")
+ call term_sendkeys(buf, "GA\<C-N>")
+ call VerifyScreenDump(buf, 'Test_popup_position_03', {'rows': 8})
+
+ " completed text wider than the window and 'pumwidth' smaller than available
+ " space
+ call term_sendkeys(buf, "\<Esc>u")
+ call term_sendkeys(buf, ":set pumwidth=20\<CR>")
+ call term_sendkeys(buf, "ggI123456789_\<Esc>")
+ call term_sendkeys(buf, "jI123456789_\<Esc>")
+ call term_sendkeys(buf, "GA\<C-N>")
+ call VerifyScreenDump(buf, 'Test_popup_position_04', {'rows': 10})
+
+ call term_sendkeys(buf, "\<Esc>u")
+ call StopVimInTerminal(buf)
+ call delete('Xtest')
+endfunc
func Test_popup_complete_backwards()
new
@@ -746,6 +808,16 @@ func Test_popup_complete_backwards()
bwipe!
endfunc
+func Test_popup_complete_backwards_ctrl_p()
+ new
+ call setline(1, ['Post', 'Port', 'Po'])
+ let expected=['Post', 'Port', 'Port']
+ call cursor(3,2)
+ call feedkeys("A\<C-P>\<C-N>rt\<cr>", 'tx')
+ call assert_equal(expected, getline(1,'$'))
+ bwipe!
+endfunc
+
fun! Test_complete_o_tab()
throw 'skipped: Nvim does not support test_override()'
let s:o_char_pressed = 0
@@ -877,6 +949,20 @@ func Test_popup_complete_info_02()
bwipe!
endfunc
+func Test_popup_complete_info_no_pum()
+ new
+ call assert_false( pumvisible() )
+ let no_pum_info = complete_info()
+ let d = {
+ \ 'mode': '',
+ \ 'pum_visible': 0,
+ \ 'items': [],
+ \ 'selected': -1,
+ \ }
+ call assert_equal( d, complete_info() )
+ bwipe!
+endfunc
+
func Test_CompleteChanged()
new
call setline(1, ['foo', 'bar', 'foobar', ''])
@@ -893,9 +979,9 @@ func Test_CompleteChanged()
call cursor(4, 1)
call feedkeys("Sf\<C-N>", 'tx')
- call assert_equal({'completed_item': {}, 'width': 15,
- \ 'height': 2, 'size': 2,
- \ 'col': 0, 'row': 4, 'scrollbar': v:false}, g:event)
+ call assert_equal({'completed_item': {}, 'width': 15.0,
+ \ 'height': 2.0, 'size': 2,
+ \ 'col': 0.0, 'row': 4.0, 'scrollbar': v:false}, g:event)
call feedkeys("a\<C-N>\<C-N>\<C-E>", 'tx')
call assert_equal('foo', g:word)
call feedkeys("a\<C-N>\<C-N>\<C-N>\<C-E>", 'tx')
@@ -911,4 +997,32 @@ func Test_CompleteChanged()
bw!
endfunc
+function! GetPumPosition()
+ call assert_true( pumvisible() )
+ let g:pum_pos = pum_getpos()
+ return ''
+endfunction
+
+func Test_pum_getpos()
+ new
+ inoremap <buffer><F5> <C-R>=GetPumPosition()<CR>
+ setlocal completefunc=UserDefinedComplete
+
+ let d = {
+ \ 'height': 5.0,
+ \ 'width': 15.0,
+ \ 'row': 1.0,
+ \ 'col': 0.0,
+ \ 'size': 5,
+ \ 'scrollbar': v:false,
+ \ }
+ call feedkeys("i\<C-X>\<C-U>\<F5>", 'tx')
+ call assert_equal(d, g:pum_pos)
+
+ call assert_false( pumvisible() )
+ call assert_equal( {}, pum_getpos() )
+ bw!
+ unlet g:pum_pos
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_profile.vim b/src/nvim/testdir/test_profile.vim
index 7e853eeac3..4b0097617e 100644
--- a/src/nvim/testdir/test_profile.vim
+++ b/src/nvim/testdir/test_profile.vim
@@ -6,34 +6,35 @@ endif
source screendump.vim
func Test_profile_func()
- let lines = [
- \ 'profile start Xprofile_func.log',
- \ 'profile func Foo*"',
- \ "func! Foo1()",
- \ "endfunc",
- \ "func! Foo2()",
- \ " let l:count = 100",
- \ " while l:count > 0",
- \ " let l:count = l:count - 1",
- \ " endwhile",
- \ "endfunc",
- \ "func! Foo3()",
- \ "endfunc",
- \ "func! Bar()",
- \ "endfunc",
- \ "call Foo1()",
- \ "call Foo1()",
- \ "profile pause",
- \ "call Foo1()",
- \ "profile continue",
- \ "call Foo2()",
- \ "call Foo3()",
- \ "call Bar()",
- \ "if !v:profiling",
- \ " delfunc Foo2",
- \ "endif",
- \ "delfunc Foo3",
- \ ]
+ let lines =<< trim [CODE]
+ profile start Xprofile_func.log
+ profile func Foo*
+ func! Foo1()
+ endfunc
+ func! Foo2()
+ let l:count = 100
+ while l:count > 0
+ let l:count = l:count - 1
+ endwhile
+ sleep 1m
+ endfunc
+ func! Foo3()
+ endfunc
+ func! Bar()
+ endfunc
+ call Foo1()
+ call Foo1()
+ profile pause
+ call Foo1()
+ profile continue
+ call Foo2()
+ call Foo3()
+ call Bar()
+ if !v:profiling
+ delfunc Foo2
+ endif
+ delfunc Foo3
+ [CODE]
call writefile(lines, 'Xprofile_func.vim')
call system(v:progpath
@@ -51,10 +52,10 @@ func Test_profile_func()
" - Unlike Foo3(), Foo2() should not be deleted since there is a check
" for v:profiling.
" - Bar() is not reported since it does not match "profile func Foo*".
- call assert_equal(30, len(lines))
+ call assert_equal(31, len(lines))
call assert_equal('FUNCTION Foo1()', lines[0])
- call assert_match('Defined:.*Xprofile_func.vim', lines[1])
+ call assert_match('Defined:.*Xprofile_func.vim:3', lines[1])
call assert_equal('Called 2 times', lines[2])
call assert_match('^Total time:\s\+\d\+\.\d\+$', lines[3])
call assert_match('^ Self time:\s\+\d\+\.\d\+$', lines[4])
@@ -71,55 +72,56 @@ func Test_profile_func()
call assert_match('^\s*101\s\+.*\swhile l:count > 0$', lines[16])
call assert_match('^\s*100\s\+.*\s let l:count = l:count - 1$', lines[17])
call assert_match('^\s*101\s\+.*\sendwhile$', lines[18])
- call assert_equal('', lines[19])
- call assert_equal('FUNCTIONS SORTED ON TOTAL TIME', lines[20])
- call assert_equal('count total (s) self (s) function', lines[21])
- call assert_match('^\s*1\s\+\d\+\.\d\+\s\+Foo2()$', lines[22])
- call assert_match('^\s*2\s\+\d\+\.\d\+\s\+Foo1()$', lines[23])
- call assert_equal('', lines[24])
- call assert_equal('FUNCTIONS SORTED ON SELF TIME', lines[25])
- call assert_equal('count total (s) self (s) function', lines[26])
- call assert_match('^\s*1\s\+\d\+\.\d\+\s\+Foo2()$', lines[27])
- call assert_match('^\s*2\s\+\d\+\.\d\+\s\+Foo1()$', lines[28])
- call assert_equal('', lines[29])
+ call assert_match('^\s*1\s\+.\+sleep 1m$', lines[19])
+ call assert_equal('', lines[20])
+ call assert_equal('FUNCTIONS SORTED ON TOTAL TIME', lines[21])
+ call assert_equal('count total (s) self (s) function', lines[22])
+ call assert_match('^\s*1\s\+\d\+\.\d\+\s\+Foo2()$', lines[23])
+ call assert_match('^\s*2\s\+\d\+\.\d\+\s\+Foo1()$', lines[24])
+ call assert_equal('', lines[25])
+ call assert_equal('FUNCTIONS SORTED ON SELF TIME', lines[26])
+ call assert_equal('count total (s) self (s) function', lines[27])
+ call assert_match('^\s*1\s\+\d\+\.\d\+\s\+Foo2()$', lines[28])
+ call assert_match('^\s*2\s\+\d\+\.\d\+\s\+Foo1()$', lines[29])
+ call assert_equal('', lines[30])
call delete('Xprofile_func.vim')
call delete('Xprofile_func.log')
endfunc
func Test_profile_func_with_ifelse()
- let lines = [
- \ "func! Foo1()",
- \ " if 1",
- \ " let x = 0",
- \ " elseif 1",
- \ " let x = 1",
- \ " else",
- \ " let x = 2",
- \ " endif",
- \ "endfunc",
- \ "func! Foo2()",
- \ " if 0",
- \ " let x = 0",
- \ " elseif 1",
- \ " let x = 1",
- \ " else",
- \ " let x = 2",
- \ " endif",
- \ "endfunc",
- \ "func! Foo3()",
- \ " if 0",
- \ " let x = 0",
- \ " elseif 0",
- \ " let x = 1",
- \ " else",
- \ " let x = 2",
- \ " endif",
- \ "endfunc",
- \ "call Foo1()",
- \ "call Foo2()",
- \ "call Foo3()",
- \ ]
+ let lines =<< trim [CODE]
+ func! Foo1()
+ if 1
+ let x = 0
+ elseif 1
+ let x = 1
+ else
+ let x = 2
+ endif
+ endfunc
+ func! Foo2()
+ if 0
+ let x = 0
+ elseif 1
+ let x = 1
+ else
+ let x = 2
+ endif
+ endfunc
+ func! Foo3()
+ if 0
+ let x = 0
+ elseif 0
+ let x = 1
+ else
+ let x = 2
+ endif
+ endfunc
+ call Foo1()
+ call Foo2()
+ call Foo3()
+ [CODE]
call writefile(lines, 'Xprofile_func.vim')
call system(v:progpath
@@ -198,41 +200,41 @@ func Test_profile_func_with_ifelse()
endfunc
func Test_profile_func_with_trycatch()
- let lines = [
- \ "func! Foo1()",
- \ " try",
- \ " let x = 0",
- \ " catch",
- \ " let x = 1",
- \ " finally",
- \ " let x = 2",
- \ " endtry",
- \ "endfunc",
- \ "func! Foo2()",
- \ " try",
- \ " throw 0",
- \ " catch",
- \ " let x = 1",
- \ " finally",
- \ " let x = 2",
- \ " endtry",
- \ "endfunc",
- \ "func! Foo3()",
- \ " try",
- \ " throw 0",
- \ " catch",
- \ " throw 1",
- \ " finally",
- \ " let x = 2",
- \ " endtry",
- \ "endfunc",
- \ "call Foo1()",
- \ "call Foo2()",
- \ "try",
- \ " call Foo3()",
- \ "catch",
- \ "endtry",
- \ ]
+ let lines =<< trim [CODE]
+ func! Foo1()
+ try
+ let x = 0
+ catch
+ let x = 1
+ finally
+ let x = 2
+ endtry
+ endfunc
+ func! Foo2()
+ try
+ throw 0
+ catch
+ let x = 1
+ finally
+ let x = 2
+ endtry
+ endfunc
+ func! Foo3()
+ try
+ throw 0
+ catch
+ throw 1
+ finally
+ let x = 2
+ endtry
+ endfunc
+ call Foo1()
+ call Foo2()
+ try
+ call Foo3()
+ catch
+ endtry
+ [CODE]
call writefile(lines, 'Xprofile_func.vim')
call system(v:progpath
@@ -311,15 +313,15 @@ func Test_profile_func_with_trycatch()
endfunc
func Test_profile_file()
- let lines = [
- \ 'func! Foo()',
- \ 'endfunc',
- \ 'for i in range(10)',
- \ ' " a comment',
- \ ' call Foo()',
- \ 'endfor',
- \ 'call Foo()',
- \ ]
+ let lines =<< trim [CODE]
+ func! Foo()
+ endfunc
+ for i in range(10)
+ " a comment
+ call Foo()
+ endfor
+ call Foo()
+ [CODE]
call writefile(lines, 'Xprofile_file.vim')
call system(v:progpath
@@ -450,26 +452,27 @@ func Test_profile_truncate_mbyte()
endfunc
func Test_profdel_func()
- let lines = [
- \ 'profile start Xprofile_file.log',
- \ 'func! Foo1()',
- \ 'endfunc',
- \ 'func! Foo2()',
- \ 'endfunc',
- \ 'func! Foo3()',
- \ 'endfunc',
- \ '',
- \ 'profile func Foo1',
- \ 'profile func Foo2',
- \ 'call Foo1()',
- \ 'call Foo2()',
- \ '',
- \ 'profile func Foo3',
- \ 'profdel func Foo2',
- \ 'profdel func Foo3',
- \ 'call Foo1()',
- \ 'call Foo2()',
- \ 'call Foo3()' ]
+ let lines =<< trim [CODE]
+ profile start Xprofile_file.log
+ func! Foo1()
+ endfunc
+ func! Foo2()
+ endfunc
+ func! Foo3()
+ endfunc
+
+ profile func Foo1
+ profile func Foo2
+ call Foo1()
+ call Foo2()
+
+ profile func Foo3
+ profdel func Foo2
+ profdel func Foo3
+ call Foo1()
+ call Foo2()
+ call Foo3()
+ [CODE]
call writefile(lines, 'Xprofile_file.vim')
call system(v:progpath . ' -es --clean -c "so Xprofile_file.vim" -c q')
call assert_equal(0, v:shell_error)
@@ -496,14 +499,15 @@ endfunc
func Test_profdel_star()
" Foo() is invoked once before and once after 'profdel *'.
" So profiling should report it only once.
- let lines = [
- \ 'profile start Xprofile_file.log',
- \ 'func! Foo()',
- \ 'endfunc',
- \ 'profile func Foo',
- \ 'call Foo()',
- \ 'profdel *',
- \ 'call Foo()' ]
+ let lines =<< trim [CODE]
+ profile start Xprofile_file.log
+ func! Foo()
+ endfunc
+ profile func Foo
+ call Foo()
+ profdel *
+ call Foo()
+ [CODE]
call writefile(lines, 'Xprofile_file.vim')
call system(v:progpath . ' -es --clean -c "so Xprofile_file.vim" -c q')
call assert_equal(0, v:shell_error)
diff --git a/src/nvim/testdir/test_python2.vim b/src/nvim/testdir/test_python2.vim
index 8d55b59c31..5895ac85a8 100644
--- a/src/nvim/testdir/test_python2.vim
+++ b/src/nvim/testdir/test_python2.vim
@@ -1,5 +1,5 @@
" Test for python 2 commands.
-" TODO: move tests from test87.in here.
+" TODO: move tests from test86.in here.
if !has('python')
finish
@@ -164,3 +164,11 @@ func Test_Write_To_Current_Buffer_Fixes_Cursor_Str()
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_python3.vim b/src/nvim/testdir/test_python3.vim
index cd07b0883f..637648817c 100644
--- a/src/nvim/testdir/test_python3.vim
+++ b/src/nvim/testdir/test_python3.vim
@@ -1,5 +1,5 @@
" Test for python 3 commands.
-" TODO: move tests from test88.in here.
+" TODO: move tests from test87.in here.
if !has('python3')
finish
@@ -164,3 +164,29 @@ func Test_Write_To_Current_Buffer_Fixes_Cursor_Str()
bwipe!
endfunction
+
+func Test_Catch_Exception_Message()
+ try
+ py3 raise RuntimeError( 'TEST' )
+ catch /.*/
+ call assert_match('^Vim(.*):.*RuntimeError: TEST$', v:exception )
+ endtry
+endfunc
+
+func Test_unicode()
+ " this crashed Vim once
+ throw "Skipped: nvim does not support changing 'encoding'"
+
+ set encoding=utf32
+ py3 print('hello')
+
+ if !has('win32')
+ set encoding=debug
+ py3 print('hello')
+
+ set encoding=euc-tw
+ py3 print('hello')
+ endif
+
+ set encoding=utf8
+endfunc
diff --git a/src/nvim/testdir/test_pyx2.vim b/src/nvim/testdir/test_pyx2.vim
index 50e57c3bfb..10ff3b6e58 100644
--- a/src/nvim/testdir/test_pyx2.vim
+++ b/src/nvim/testdir/test_pyx2.vim
@@ -72,3 +72,11 @@ func Test_pyxfile()
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_pyx3.vim b/src/nvim/testdir/test_pyx3.vim
index 64546b4688..2044af3abe 100644
--- a/src/nvim/testdir/test_pyx3.vim
+++ b/src/nvim/testdir/test_pyx3.vim
@@ -72,3 +72,11 @@ func Test_pyxfile()
call assert_match(s:py2pattern, 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_quickfix.vim b/src/nvim/testdir/test_quickfix.vim
index b7f45aeeb1..926103b69f 100644
--- a/src/nvim/testdir/test_quickfix.vim
+++ b/src/nvim/testdir/test_quickfix.vim
@@ -37,6 +37,8 @@ func s:setup_commands(cchar)
command! -nargs=* Xgrepadd <mods> grepadd <args>
command! -nargs=* Xhelpgrep helpgrep <args>
command! -nargs=0 -count Xcc <count>cc
+ command! -count=1 -nargs=0 Xbelow <mods><count>cbelow
+ command! -count=1 -nargs=0 Xabove <mods><count>cabove
let g:Xgetlist = function('getqflist')
let g:Xsetlist = function('setqflist')
call setqflist([], 'f')
@@ -70,6 +72,8 @@ func s:setup_commands(cchar)
command! -nargs=* Xgrepadd <mods> lgrepadd <args>
command! -nargs=* Xhelpgrep lhelpgrep <args>
command! -nargs=0 -count Xcc <count>ll
+ command! -count=1 -nargs=0 Xbelow <mods><count>lbelow
+ command! -count=1 -nargs=0 Xabove <mods><count>labove
let g:Xgetlist = function('getloclist', [0])
let g:Xsetlist = function('setloclist', [0])
call setloclist(0, [], 'f')
@@ -163,6 +167,12 @@ endfunc
func XageTests(cchar)
call s:setup_commands(a:cchar)
+ if a:cchar == 'l'
+ " No location list for the current window
+ call assert_fails('lolder', 'E776:')
+ call assert_fails('lnewer', 'E776:')
+ endif
+
let list = [{'bufnr': bufnr('%'), 'lnum': 1}]
call g:Xsetlist(list)
@@ -273,6 +283,27 @@ func Test_cwindow()
call XwindowTests('l')
endfunc
+func Test_copenHeight()
+ copen
+ wincmd H
+ let height = winheight(0)
+ copen 10
+ call assert_equal(height, winheight(0))
+ quit
+endfunc
+
+func Test_copenHeight_tabline()
+ set tabline=foo showtabline=2
+ copen
+ wincmd H
+ let height = winheight(0)
+ copen 10
+ call assert_equal(height, winheight(0))
+ quit
+ set tabline& showtabline&
+endfunc
+
+
" Tests for the :cfile, :lfile, :caddfile, :laddfile, :cgetfile and :lgetfile
" commands.
func XfileTests(cchar)
@@ -523,6 +554,33 @@ func s:test_xhelpgrep(cchar)
" This wipes out the buffer, make sure that doesn't cause trouble.
Xclose
+ " When the current window is vertically split, jumping to a help match
+ " should open the help window at the top.
+ only | enew
+ let w1 = win_getid()
+ vert new
+ let w2 = win_getid()
+ Xnext
+ let w3 = win_getid()
+ call assert_true(&buftype == 'help')
+ call assert_true(winnr() == 1)
+ " See jump_to_help_window() for details
+ let w2_width = winwidth(w2)
+ if w2_width != &columns && w2_width < 80
+ call assert_equal(['col', [['leaf', w3],
+ \ ['row', [['leaf', w2], ['leaf', w1]]]]], winlayout())
+ else
+ call assert_equal(['row', [['col', [['leaf', w3], ['leaf', w2]]],
+ \ ['leaf', w1]]] , winlayout())
+ endif
+
+ new | only
+ set buftype=help
+ set modified
+ call assert_fails('Xnext', 'E37:')
+ set nomodified
+ new | only
+
if a:cchar == 'l'
" When a help window is present, running :lhelpgrep should reuse the
" help window and not the current window
@@ -540,6 +598,8 @@ func s:test_xhelpgrep(cchar)
" Search for non existing help string
call assert_fails('Xhelpgrep a1b2c3', 'E480:')
+ " Invalid regular expression
+ call assert_fails('Xhelpgrep \@<!', 'E480:')
endfunc
func Test_helpgrep()
@@ -775,68 +835,68 @@ func Test_efm1()
return
endif
- let l = [
- \ '"Xtestfile", line 4.12: 1506-045 (S) Undeclared identifier fd_set.',
- \ '"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',
- \ 'in file "Xtestfile" linenr 10: there is an error',
- \ '',
- \ '2 returned',
- \ '"Xtestfile", line 11 col 1; this is an error',
- \ '"Xtestfile", line 12 col 2; this is another error',
- \ '"Xtestfile", line 14:10; this is an error in column 10',
- \ '=Xtestfile=, line 15:10; this is another error, but in vcol 10 this time',
- \ '"Xtestfile", linenr 16: yet another problem',
- \ 'Error in "Xtestfile" at line 17:',
- \ 'x should be a dot',
- \ ' xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 17',
- \ ' ^',
- \ 'Error in "Xtestfile" at line 18:',
- \ 'x should be a dot',
- \ ' xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 18',
- \ '.............^',
- \ 'Error in "Xtestfile" at line 19:',
- \ 'x should be a dot',
- \ ' xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 19',
- \ '--------------^',
- \ 'Error in "Xtestfile" at line 20:',
- \ 'x should be a dot',
- \ ' xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 20',
- \ ' ^',
- \ '',
- \ 'Does anyone know what is the problem and how to correction it?',
- \ '"Xtestfile", line 21 col 9: What is the title of the quickfix window?',
- \ '"Xtestfile", line 22 col 9: What is the title of the quickfix window?'
- \ ]
+ let l =<< trim [DATA]
+ "Xtestfile", line 4.12: 1506-045 (S) Undeclared identifier fd_set.
+ "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
+ in file "Xtestfile" linenr 10: there is an error
+
+ 2 returned
+ "Xtestfile", line 11 col 1; this is an error
+ "Xtestfile", line 12 col 2; this is another error
+ "Xtestfile", line 14:10; this is an error in column 10
+ =Xtestfile=, line 15:10; this is another error, but in vcol 10 this time
+ "Xtestfile", linenr 16: yet another problem
+ Error in "Xtestfile" at line 17:
+ x should be a dot
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 17
+ ^
+ Error in "Xtestfile" at line 18:
+ x should be a dot
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 18
+ .............^
+ Error in "Xtestfile" at line 19:
+ x should be a dot
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 19
+ --------------^
+ Error in "Xtestfile" at line 20:
+ x should be a dot
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 20
+ ^
+
+ Does anyone know what is the problem and how to correction it?
+ "Xtestfile", line 21 col 9: What is the title of the quickfix window?
+ "Xtestfile", line 22 col 9: What is the title of the quickfix window?
+ [DATA]
call writefile(l, 'Xerrorfile1')
call writefile(l[:-2], 'Xerrorfile2')
- let m = [
- \ ' xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 2',
- \ ' xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 3',
- \ ' xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 4',
- \ ' xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 5',
- \ ' xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 6',
- \ ' xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 7',
- \ ' xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 8',
- \ ' xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 9',
- \ ' xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 10',
- \ ' xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 11',
- \ ' xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 12',
- \ ' xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 13',
- \ ' xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 14',
- \ ' xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 15',
- \ ' xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 16',
- \ ' xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 17',
- \ ' xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 18',
- \ ' xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 19',
- \ ' xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 20',
- \ ' xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 21',
- \ ' xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 22'
- \ ]
+ let m =<< [DATA]
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 2
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 3
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 4
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 5
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 6
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 7
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 8
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 9
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 10
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 11
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 12
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 13
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 14
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 15
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 16
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 17
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 18
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 19
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 20
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 21
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 22
+[DATA]
call writefile(m, 'Xtestfile')
let save_efm = &efm
@@ -895,20 +955,21 @@ func s:dir_stack_tests(cchar)
let save_efm=&efm
set efm=%DEntering\ dir\ '%f',%f:%l:%m,%XLeaving\ dir\ '%f'
- let lines = ["Entering dir 'dir1/a'",
- \ 'habits2.txt:1:Nine Healthy Habits',
- \ "Entering dir 'b'",
- \ 'habits3.txt:2:0 Hours of television',
- \ 'habits2.txt:7:5 Small meals',
- \ "Entering dir 'dir1/c'",
- \ 'habits4.txt:3:1 Hour of exercise',
- \ "Leaving dir 'dir1/c'",
- \ "Leaving dir 'dir1/a'",
- \ 'habits1.txt:4:2 Liters of water',
- \ "Entering dir 'dir2'",
- \ 'habits5.txt:5:3 Cups of hot green tea',
- \ "Leaving dir 'dir2'"
- \]
+ let lines =<< trim [DATA]
+ Entering dir 'dir1/a'
+ habits2.txt:1:Nine Healthy Habits
+ Entering dir 'b'
+ habits3.txt:2:0 Hours of television
+ habits2.txt:7:5 Small meals
+ Entering dir 'dir1/c'
+ habits4.txt:3:1 Hour of exercise
+ Leaving dir 'dir1/c'
+ Leaving dir 'dir1/a'
+ habits1.txt:4:2 Liters of water
+ Entering dir 'dir2'
+ habits5.txt:5:3 Cups of hot green tea
+ Leaving dir 'dir2
+ [DATA]
Xexpr ""
for l in lines
@@ -942,18 +1003,20 @@ func Test_efm_dirstack()
call mkdir('dir1/c')
call mkdir('dir2')
- let lines = ["Nine Healthy Habits",
- \ "0 Hours of television",
- \ "1 Hour of exercise",
- \ "2 Liters of water",
- \ "3 Cups of hot green tea",
- \ "4 Short mental breaks",
- \ "5 Small meals",
- \ "6 AM wake up time",
- \ "7 Minutes of laughter",
- \ "8 Hours of sleep (at least)",
- \ "9 PM end of the day and off to bed"
- \ ]
+ let lines =<< trim [DATA]
+ Nine Healthy Habits,
+ 0 Hours of television,
+ 1 Hour of exercise,
+ 2 Liters of water,
+ 3 Cups of hot green tea,
+ 4 Short mental breaks,
+ 5 Small meals,
+ 6 AM wake up time,
+ 7 Minutes of laughter,
+ 8 Hours of sleep (at least),
+ 9 PM end of the day and off to bed
+ [DATA]
+
call writefile(lines, 'habits1.txt')
call writefile(lines, 'dir1/a/habits2.txt')
call writefile(lines, 'dir1/a/b/habits3.txt')
@@ -1042,28 +1105,29 @@ func Test_efm2()
set efm=%f:%s
cexpr 'Xtestfile:Line search text'
let l = getqflist()
- call assert_equal(l[0].pattern, '^\VLine search text\$')
- call assert_equal(l[0].lnum, 0)
+ call assert_equal('^\VLine search text\$', l[0].pattern)
+ call assert_equal(0, l[0].lnum)
let l = split(execute('clist', ''), "\n")
call assert_equal([' 1 Xtestfile:^\VLine search text\$: '], l)
" Test for %P, %Q and %t format specifiers
- let lines=["[Xtestfile1]",
- \ "(1,17) error: ';' missing",
- \ "(21,2) warning: variable 'z' not defined",
- \ "(67,3) error: end of file found before string ended",
- \ "--",
- \ "",
- \ "[Xtestfile2]",
- \ "--",
- \ "",
- \ "[Xtestfile3]",
- \ "NEW compiler v1.1",
- \ "(2,2) warning: variable 'x' not defined",
- \ "(67,3) warning: 's' already defined",
- \ "--"
- \]
+ let lines =<< trim [DATA]
+ [Xtestfile1]
+ (1,17) error: ';' missing
+ (21,2) warning: variable 'z' not defined
+ (67,3) error: end of file found before string ended
+ --
+
+ [Xtestfile2]
+ --
+
+ [Xtestfile3]
+ NEW compiler v1.1
+ (2,2) warning: variable 'x' not defined
+ (67,3) warning: 's' already defined
+ --
+ [DATA]
set efm=%+P[%f]%r,(%l\\,%c)%*[\ ]%t%*[^:]:\ %m,%+Q--%r
" To exercise the push/pop file functionality in quickfix, the test files
" need to be created.
@@ -1085,11 +1149,13 @@ func Test_efm2()
call delete('Xtestfile3')
" Tests for %E, %C and %Z format specifiers
- let lines = ["Error 275",
- \ "line 42",
- \ "column 3",
- \ "' ' expected after '--'"
- \]
+ let lines =<< trim [DATA]
+ Error 275
+ line 42
+ column 3
+ ' ' expected after '--'
+ [DATA]
+
set efm=%EError\ %n,%Cline\ %l,%Ccolumn\ %c,%Z%m
cgetexpr lines
let l = getqflist()
@@ -1100,9 +1166,11 @@ func Test_efm2()
call assert_equal("\n' ' expected after '--'", l[0].text)
" Test for %>
- let lines = ["Error in line 147 of foo.c:",
- \"unknown variable 'i'"
- \]
+ let lines =<< trim [DATA]
+ Error in line 147 of foo.c:
+ unknown variable 'i'
+ [DATA]
+
set efm=unknown\ variable\ %m,%E%>Error\ in\ line\ %l\ of\ %f:,%Z%m
cgetexpr lines
let l = getqflist()
@@ -1111,21 +1179,21 @@ func Test_efm2()
call assert_equal("\nunknown variable 'i'", l[0].text)
" Test for %A, %C and other formats
- let lines = [
- \"==============================================================",
- \"FAIL: testGetTypeIdCachesResult (dbfacadeTest.DjsDBFacadeTest)",
- \"--------------------------------------------------------------",
- \"Traceback (most recent call last):",
- \' File "unittests/dbfacadeTest.py", line 89, in testFoo',
- \" self.assertEquals(34, dtid)",
- \' File "/usr/lib/python2.2/unittest.py", line 286, in',
- \" failUnlessEqual",
- \" raise self.failureException, \\",
- \"AssertionError: 34 != 33",
- \"",
- \"--------------------------------------------------------------",
- \"Ran 27 tests in 0.063s"
- \]
+ let lines =<< trim [DATA]
+ ==============================================================
+ FAIL: testGetTypeIdCachesResult (dbfacadeTest.DjsDBFacadeTest)
+ --------------------------------------------------------------
+ Traceback (most recent call last):
+ File "unittests/dbfacadeTest.py", line 89, in testFoo
+ self.assertEquals(34, dtid)
+ File "/usr/lib/python2.2/unittest.py", line 286, in
+ failUnlessEqual
+ raise self.failureException, \\
+ AssertionError: 34 != 33
+
+ --------------------------------------------------------------
+ Ran 27 tests in 0.063s
+ [DATA]
set efm=%C\ %.%#,%A\ \ File\ \"%f\"\\,\ line\ %l%.%#,%Z%[%^\ ]%\\@=%m
cgetexpr lines
let l = getqflist()
@@ -1279,6 +1347,28 @@ func SetXlistTests(cchar, bnum)
let l = g:Xgetlist()
call g:Xsetlist(l)
call assert_equal(0, g:Xgetlist()[0].valid)
+ " Adding a non-valid entry should not mark the list as having valid entries
+ call g:Xsetlist([{'bufnr':a:bnum, 'lnum':5, 'valid':0}], 'a')
+ Xwindow
+ call assert_equal(1, winnr('$'))
+
+ " :cnext/:cprev should still work even with invalid entries in the list
+ let l = [{'bufnr' : a:bnum, 'lnum' : 1, 'text' : '1', 'valid' : 0},
+ \ {'bufnr' : a:bnum, 'lnum' : 2, 'text' : '2', 'valid' : 0}]
+ call g:Xsetlist(l)
+ Xnext
+ call assert_equal(2, g:Xgetlist({'idx' : 0}).idx)
+ Xprev
+ call assert_equal(1, g:Xgetlist({'idx' : 0}).idx)
+ " :cnext/:cprev should still work after appending invalid entries to an
+ " empty list
+ call g:Xsetlist([])
+ call g:Xsetlist(l, 'a')
+ Xnext
+ call assert_equal(2, g:Xgetlist({'idx' : 0}).idx)
+ Xprev
+ call assert_equal(1, g:Xgetlist({'idx' : 0}).idx)
+
call g:Xsetlist([{'text':'Text1', 'valid':1}])
Xwindow
call assert_equal(2, winnr('$'))
@@ -1447,6 +1537,13 @@ func Test_setqflist_invalid_nr()
call setqflist([], ' ', {'nr' : $XXX_DOES_NOT_EXIST})
endfunc
+func Test_setqflist_user_sets_buftype()
+ call setqflist([{'text': 'foo'}, {'text': 'bar'}])
+ set buftype=quickfix
+ call setqflist([], 'a')
+ enew
+endfunc
+
func Test_quickfix_set_list_with_act()
call XquickfixSetListWithAct('c')
call XquickfixSetListWithAct('l')
@@ -1579,6 +1676,14 @@ func Test_switchbuf()
call assert_equal(3, tabpagenr('$'))
tabfirst | enew | tabonly | only
+ set switchbuf=uselast
+ split
+ let last_winid = win_getid()
+ copen
+ exe "normal 1G\<CR>"
+ call assert_equal(last_winid, win_getid())
+ enew | only
+
set switchbuf=
edit Xqftestfile1
let file1_winid = win_getid()
@@ -1955,6 +2060,18 @@ func Xproperty_tests(cchar)
call g:Xsetlist([], 'r', {'items' : [{'filename' : 'F1', 'lnum' : 10, 'text' : 'L10'}]})
call assert_equal('TestTitle', g:Xgetlist({'title' : 1}).title)
+ " Test for getting id of window associated with a location list window
+ if a:cchar == 'l'
+ only
+ call assert_equal(0, g:Xgetlist({'all' : 1}).filewinid)
+ let wid = win_getid()
+ Xopen
+ call assert_equal(wid, g:Xgetlist({'filewinid' : 1}).filewinid)
+ wincmd w
+ call assert_equal(0, g:Xgetlist({'filewinid' : 1}).filewinid)
+ only
+ endif
+
" The following used to crash Vim with address sanitizer
call g:Xsetlist([], 'f')
call g:Xsetlist([], 'a', {'items' : [{'filename':'F1', 'lnum':10}]})
@@ -2317,6 +2434,12 @@ func XvimgrepTests(cchar)
call assert_equal('Xtestfile2', bufname(''))
call assert_equal('Editor:Emacs EmAcS', l[0].text)
+ " Test for unloading a buffer after vimgrep searched the buffer
+ %bwipe
+ Xvimgrep /Editor/j Xtestfile*
+ call assert_equal(0, getbufinfo('Xtestfile1')[0].loaded)
+ call assert_equal([], getbufinfo('Xtestfile2'))
+
call delete('Xtestfile1')
call delete('Xtestfile2')
endfunc
@@ -2489,7 +2612,7 @@ func Test_file_from_copen()
cclose
augroup! QF_Test
-endfunction
+endfunc
func Test_resize_from_copen()
augroup QF_Test
@@ -2864,185 +2987,101 @@ func Test_qf_id()
call Xqfid_tests('l')
endfunc
-func Test_getqflist_invalid_nr()
- " The following commands used to crash Vim
- cexpr ""
- call getqflist({'nr' : $XXX_DOES_NOT_EXIST_XXX})
-
- " Cleanup
- call setqflist([], 'r')
-endfunc
-
-" Test for shortening/simplifying the file name when opening the
-" quickfix window or when displaying the quickfix list
-func Test_shorten_fname()
- if !has('unix')
- return
- endif
- %bwipe
- " Create a quickfix list with a absolute path filename
- let fname = getcwd() . '/test_quickfix.vim'
- call setqflist([], ' ', {'lines':[fname . ":20:Line20"], 'efm':'%f:%l:%m'})
- call assert_equal(fname, bufname('test_quickfix.vim'))
- " Opening the quickfix window should simplify the file path
- cwindow
- call assert_equal('test_quickfix.vim', bufname('test_quickfix.vim'))
- cclose
- %bwipe
- " Create a quickfix list with a absolute path filename
- call setqflist([], ' ', {'lines':[fname . ":20:Line20"], 'efm':'%f:%l:%m'})
- call assert_equal(fname, bufname('test_quickfix.vim'))
- " Displaying the quickfix list should simplify the file path
- silent! clist
- call assert_equal('test_quickfix.vim', bufname('test_quickfix.vim'))
-endfunc
-
-" Quickfix title tests
-" In the below tests, 'exe "cmd"' is used to invoke the quickfix commands.
-" Otherwise due to indentation, the title is set with spaces at the beginning
-" of the command.
-func Test_qftitle()
- call writefile(["F1:1:Line1"], 'Xerr')
-
- " :cexpr
- exe "cexpr readfile('Xerr')"
- call assert_equal(":cexpr readfile('Xerr')", getqflist({'title' : 1}).title)
-
- " :cgetexpr
- exe "cgetexpr readfile('Xerr')"
- call assert_equal(":cgetexpr readfile('Xerr')",
- \ getqflist({'title' : 1}).title)
-
- " :caddexpr
- call setqflist([], 'f')
- exe "caddexpr readfile('Xerr')"
- call assert_equal(":caddexpr readfile('Xerr')",
- \ getqflist({'title' : 1}).title)
-
- " :cbuffer
- new Xerr
- exe "cbuffer"
- call assert_equal(':cbuffer (Xerr)', getqflist({'title' : 1}).title)
-
- " :cgetbuffer
- edit Xerr
- exe "cgetbuffer"
- call assert_equal(':cgetbuffer (Xerr)', getqflist({'title' : 1}).title)
-
- " :caddbuffer
- call setqflist([], 'f')
- edit Xerr
- exe "caddbuffer"
- call assert_equal(':caddbuffer (Xerr)', getqflist({'title' : 1}).title)
-
- " :cfile
- exe "cfile Xerr"
- call assert_equal(':cfile Xerr', getqflist({'title' : 1}).title)
-
- " :cgetfile
- exe "cgetfile Xerr"
- call assert_equal(':cgetfile Xerr', getqflist({'title' : 1}).title)
-
- " :caddfile
- call setqflist([], 'f')
- exe "caddfile Xerr"
- call assert_equal(':caddfile Xerr', getqflist({'title' : 1}).title)
-
- " :grep
- set grepprg=internal
- exe "grep F1 Xerr"
- call assert_equal(':grep F1 Xerr', getqflist({'title' : 1}).title)
-
- " :grepadd
- call setqflist([], 'f')
- exe "grepadd F1 Xerr"
- call assert_equal(':grepadd F1 Xerr', getqflist({'title' : 1}).title)
- set grepprg&vim
-
- " :vimgrep
- exe "vimgrep F1 Xerr"
- call assert_equal(':vimgrep F1 Xerr', getqflist({'title' : 1}).title)
+func Xqfjump_tests(cchar)
+ call s:setup_commands(a:cchar)
- " :vimgrepadd
- call setqflist([], 'f')
- exe "vimgrepadd F1 Xerr"
- call assert_equal(':vimgrepadd F1 Xerr', getqflist({'title' : 1}).title)
+ call writefile(["Line1\tFoo", "Line2"], 'F1')
+ call writefile(["Line1\tBar", "Line2"], 'F2')
+ call writefile(["Line1\tBaz", "Line2"], 'F3')
- call setqflist(['F1:10:L10'], ' ')
- call assert_equal(':setqflist()', getqflist({'title' : 1}).title)
+ call g:Xsetlist([], 'f')
- call setqflist([], 'f')
- call setqflist(['F1:10:L10'], 'a')
- call assert_equal(':setqflist()', getqflist({'title' : 1}).title)
+ " Tests for
+ " Jumping to a line using a pattern
+ " Jumping to a column greater than the last column in a line
+ " Jumping to a line greater than the last line in the file
+ let l = []
+ for i in range(1, 7)
+ call add(l, {})
+ endfor
+ let l[0].filename='F1'
+ let l[0].pattern='Line1'
+ let l[1].filename='F2'
+ let l[1].pattern='Line1'
+ let l[2].filename='F3'
+ let l[2].pattern='Line1'
+ let l[3].filename='F3'
+ let l[3].lnum=1
+ let l[3].col=9
+ let l[3].vcol=1
+ let l[4].filename='F3'
+ let l[4].lnum=99
+ let l[5].filename='F3'
+ let l[5].lnum=1
+ let l[5].col=99
+ let l[5].vcol=1
+ let l[6].filename='F3'
+ let l[6].pattern='abcxyz'
- call setqflist([], 'f')
- call setqflist(['F1:10:L10'], 'r')
- call assert_equal(':setqflist()', getqflist({'title' : 1}).title)
+ call g:Xsetlist([], ' ', {'items' : l})
+ Xopen | only
+ 2Xnext
+ call assert_equal(3, g:Xgetlist({'idx' : 0}).idx)
+ call assert_equal('F3', bufname('%'))
+ Xnext
+ call assert_equal(7, col('.'))
+ Xnext
+ call assert_equal(2, line('.'))
+ Xnext
+ call assert_equal(9, col('.'))
+ 2
+ Xnext
+ call assert_equal(2, line('.'))
- close
- call delete('Xerr')
+ if a:cchar == 'l'
+ " When jumping to a location list entry in the location list window and
+ " no usable windows are available, then a new window should be opened.
+ enew! | new | only
+ call g:Xsetlist([], 'f')
+ setlocal buftype=nofile
+ new
+ call g:Xsetlist([], ' ', {'lines' : ['F1:1:1:Line1', 'F1:2:2:Line2', 'F2:1:1:Line1', 'F2:2:2:Line2', 'F3:1:1:Line1', 'F3:2:2:Line2']})
+ Xopen
+ let winid = win_getid()
+ wincmd p
+ close
+ call win_gotoid(winid)
+ Xnext
+ call assert_equal(3, winnr('$'))
+ call assert_equal(1, winnr())
+ call assert_equal(2, line('.'))
- call setqflist([], ' ', {'title' : 'Errors'})
- copen
- call assert_equal('Errors', w:quickfix_title)
- call setqflist([], 'r', {'items' : [{'filename' : 'a.c', 'lnum' : 10}]})
- call assert_equal('Errors', w:quickfix_title)
- cclose
-endfunc
+ " When jumping to an entry in the location list window and the window
+ " associated with the location list is not present and a window containing
+ " the file is already present, then that window should be used.
+ close
+ belowright new
+ call g:Xsetlist([], 'f')
+ edit F3
+ call win_gotoid(winid)
+ Xlast
+ call assert_equal(3, winnr())
+ call assert_equal(6, g:Xgetlist({'size' : 1}).size)
+ call assert_equal(winid, g:Xgetlist({'winid' : 1}).winid)
+ endif
-" Test for the position of the quickfix and location list window
-func Test_qfwin_pos()
- " Open two windows
+ " Cleanup
+ enew!
new | only
- new
- cexpr ['F1:10:L10']
- copen
- " Quickfix window should be the bottom most window
- call assert_equal(3, winnr())
- close
- " Open at the very top
- wincmd t
- topleft copen
- call assert_equal(1, winnr())
- close
- " open left of the current window
- wincmd t
- below new
- leftabove copen
- call assert_equal(2, winnr())
- close
- " open right of the current window
- rightbelow copen
- call assert_equal(3, winnr())
- close
-endfunc
-" The following test used to crash Vim
-func Test_lhelpgrep_autocmd()
- lhelpgrep quickfix
- autocmd QuickFixCmdPost * call setloclist(0, [], 'f')
- lhelpgrep buffer
- call assert_equal('help', &filetype)
- call assert_equal(0, getloclist(0, {'nr' : '$'}).nr)
- lhelpgrep tabpage
- call assert_equal('help', &filetype)
- call assert_equal(1, getloclist(0, {'nr' : '$'}).nr)
- au! QuickFixCmdPost
- new | only
+ call delete('F1')
+ call delete('F2')
+ call delete('F3')
endfunc
-" Test to make sure that an empty quickfix buffer is not reused for loading
-" a normal buffer.
-func Test_empty_qfbuf()
- enew | only
- call writefile(["Test"], 'Xfile1')
- call setqflist([], 'f')
- copen | only
- let qfbuf = bufnr('')
- edit Xfile1
- call assert_notequal(qfbuf, bufnr(''))
- enew
- call delete('Xfile1')
+func Test_qfjump()
+ call Xqfjump_tests('c')
+ call Xqfjump_tests('l')
endfunc
" Tests for the getqflist() and getloclist() functions when the list is not
@@ -3061,7 +3100,17 @@ func Xgetlist_empty_tests(cchar)
call assert_equal('', g:Xgetlist({'title' : 0}).title)
call assert_equal(0, g:Xgetlist({'winid' : 0}).winid)
call assert_equal(0, g:Xgetlist({'changedtick' : 0}).changedtick)
- call assert_equal({'context' : '', 'id' : 0, 'idx' : 0, 'items' : [], 'nr' : 0, 'size' : 0, 'title' : '', 'winid' : 0, 'changedtick': 0}, g:Xgetlist({'all' : 0}))
+ if a:cchar == 'c'
+ call assert_equal({'context' : '', 'id' : 0, 'idx' : 0,
+ \ 'items' : [], 'nr' : 0, 'size' : 0,
+ \ 'title' : '', 'winid' : 0, 'changedtick': 0},
+ \ g:Xgetlist({'all' : 0}))
+ else
+ call assert_equal({'context' : '', 'id' : 0, 'idx' : 0,
+ \ 'items' : [], 'nr' : 0, 'size' : 0, 'title' : '',
+ \ 'winid' : 0, 'changedtick': 0, 'filewinid' : 0},
+ \ g:Xgetlist({'all' : 0}))
+ endif
" Quickfix window with empty stack
silent! Xopen
@@ -3094,7 +3143,16 @@ func Xgetlist_empty_tests(cchar)
call assert_equal('', g:Xgetlist({'id' : qfid, 'title' : 0}).title)
call assert_equal(0, g:Xgetlist({'id' : qfid, 'winid' : 0}).winid)
call assert_equal(0, g:Xgetlist({'id' : qfid, 'changedtick' : 0}).changedtick)
- call assert_equal({'context' : '', 'id' : 0, 'idx' : 0, 'items' : [], 'nr' : 0, 'size' : 0, 'title' : '', 'winid' : 0, 'changedtick' : 0}, g:Xgetlist({'id' : qfid, 'all' : 0}))
+ if a:cchar == 'c'
+ call assert_equal({'context' : '', 'id' : 0, 'idx' : 0, 'items' : [],
+ \ 'nr' : 0, 'size' : 0, 'title' : '', 'winid' : 0,
+ \ 'changedtick' : 0}, g:Xgetlist({'id' : qfid, 'all' : 0}))
+ else
+ call assert_equal({'context' : '', 'id' : 0, 'idx' : 0, 'items' : [],
+ \ 'nr' : 0, 'size' : 0, 'title' : '', 'winid' : 0,
+ \ 'changedtick' : 0, 'filewinid' : 0},
+ \ g:Xgetlist({'id' : qfid, 'all' : 0}))
+ endif
" Non-existing quickfix list number
call assert_equal('', g:Xgetlist({'nr' : 5, 'context' : 0}).context)
@@ -3106,7 +3164,16 @@ func Xgetlist_empty_tests(cchar)
call assert_equal('', g:Xgetlist({'nr' : 5, 'title' : 0}).title)
call assert_equal(0, g:Xgetlist({'nr' : 5, 'winid' : 0}).winid)
call assert_equal(0, g:Xgetlist({'nr' : 5, 'changedtick' : 0}).changedtick)
- call assert_equal({'context' : '', 'id' : 0, 'idx' : 0, 'items' : [], 'nr' : 0, 'size' : 0, 'title' : '', 'winid' : 0, 'changedtick' : 0}, g:Xgetlist({'nr' : 5, 'all' : 0}))
+ if a:cchar == 'c'
+ call assert_equal({'context' : '', 'id' : 0, 'idx' : 0, 'items' : [],
+ \ 'nr' : 0, 'size' : 0, 'title' : '', 'winid' : 0,
+ \ 'changedtick' : 0}, g:Xgetlist({'nr' : 5, 'all' : 0}))
+ else
+ call assert_equal({'context' : '', 'id' : 0, 'idx' : 0, 'items' : [],
+ \ 'nr' : 0, 'size' : 0, 'title' : '', 'winid' : 0,
+ \ 'changedtick' : 0, 'filewinid' : 0},
+ \ g:Xgetlist({'nr' : 5, 'all' : 0}))
+ endif
endfunc
func Test_getqflist()
@@ -3114,6 +3181,16 @@ func Test_getqflist()
call Xgetlist_empty_tests('l')
endfunc
+
+func Test_getqflist_invalid_nr()
+ " The following commands used to crash Vim
+ cexpr ""
+ call getqflist({'nr' : $XXX_DOES_NOT_EXIST_XXX})
+
+ " Cleanup
+ call setqflist([], 'r')
+endfunc
+
" Tests for the quickfix/location list changedtick
func Xqftick_tests(cchar)
call s:setup_commands(a:cchar)
@@ -3172,6 +3249,41 @@ func Test_qf_tick()
call Xqftick_tests('l')
endfunc
+" Test helpgrep with lang specifier
+func Xtest_helpgrep_with_lang_specifier(cchar)
+ call s:setup_commands(a:cchar)
+ Xhelpgrep Vim@en
+ call assert_equal('help', &filetype)
+ call assert_notequal(0, g:Xgetlist({'nr' : '$'}).nr)
+ new | only
+endfunc
+
+func Test_helpgrep_with_lang_specifier()
+ call Xtest_helpgrep_with_lang_specifier('c')
+ call Xtest_helpgrep_with_lang_specifier('l')
+endfunc
+
+" The following test used to crash Vim.
+" Open the location list window and close the regular window associated with
+" the location list. When the garbage collection runs now, it incorrectly
+" marks the location list context as not in use and frees the context.
+func Test_ll_window_ctx()
+ call setloclist(0, [], 'f')
+ call setloclist(0, [], 'a', {'context' : []})
+ lopen | only
+ call test_garbagecollect_now()
+ echo getloclist(0, {'context' : 1}).context
+ enew | only
+endfunc
+
+" The following test used to crash vim
+func Test_lfile_crash()
+ sp Xtest
+ au QuickFixCmdPre * bw
+ call assert_fails('lfile', 'E40')
+ au! QuickFixCmdPre
+endfunc
+
" The following test used to crash vim
func Test_lbuffer_crash()
sv Xtest
@@ -3195,7 +3307,28 @@ func Test_lexpr_crash()
augroup QF_Test
au!
augroup END
+
+ enew | only
+ augroup QF_Test
+ au!
+ au BufNew * call setloclist(0, [], 'f')
+ augroup END
+ lexpr 'x:1:x'
+ augroup QF_Test
+ au!
+ augroup END
+
enew | only
+ lexpr ''
+ lopen
+ augroup QF_Test
+ au!
+ au FileType * call setloclist(0, [], 'f')
+ augroup END
+ lexpr ''
+ augroup QF_Test
+ au!
+ augroup END
endfunc
" The following test used to crash Vim
@@ -3212,122 +3345,39 @@ func Test_lvimgrep_crash()
enew | only
endfunc
-func Xqfjump_tests(cchar)
- call s:setup_commands(a:cchar)
-
- call writefile(["Line1\tFoo", "Line2"], 'F1')
- call writefile(["Line1\tBar", "Line2"], 'F2')
- call writefile(["Line1\tBaz", "Line2"], 'F3')
-
- call g:Xsetlist([], 'f')
+func Test_lvimgrep_crash2()
+ au BufNewFile x sfind
+ call assert_fails('lvimgrep x x', 'E480:')
+ call assert_fails('lvimgrep x x x', 'E480:')
- " Tests for
- " Jumping to a line using a pattern
- " Jumping to a column greater than the last column in a line
- " Jumping to a line greater than the last line in the file
- let l = []
- for i in range(1, 7)
- call add(l, {})
- endfor
- let l[0].filename='F1'
- let l[0].pattern='Line1'
- let l[1].filename='F2'
- let l[1].pattern='Line1'
- let l[2].filename='F3'
- let l[2].pattern='Line1'
- let l[3].filename='F3'
- let l[3].lnum=1
- let l[3].col=9
- let l[3].vcol=1
- let l[4].filename='F3'
- let l[4].lnum=99
- let l[5].filename='F3'
- let l[5].lnum=1
- let l[5].col=99
- let l[5].vcol=1
- let l[6].filename='F3'
- let l[6].pattern='abcxyz'
-
- call g:Xsetlist([], ' ', {'items' : l})
- Xopen | only
- 2Xnext
- call assert_equal(3, g:Xgetlist({'idx' : 0}).idx)
- call assert_equal('F3', bufname('%'))
- Xnext
- call assert_equal(7, col('.'))
- Xnext
- call assert_equal(2, line('.'))
- Xnext
- call assert_equal(9, col('.'))
- 2
- Xnext
- call assert_equal(2, line('.'))
-
- if a:cchar == 'l'
- " When jumping to a location list entry in the location list window and
- " no usable windows are available, then a new window should be opened.
- enew! | new | only
- call g:Xsetlist([], 'f')
- setlocal buftype=nofile
- new
- call g:Xsetlist([], ' ', {'lines' : ['F1:1:1:Line1', 'F1:2:2:Line2', 'F2:1:1:Line1', 'F2:2:2:Line2', 'F3:1:1:Line1', 'F3:2:2:Line2']})
- Xopen
- let winid = win_getid()
- wincmd p
- close
- call win_gotoid(winid)
- Xnext
- call assert_equal(3, winnr('$'))
- call assert_equal(1, winnr())
- call assert_equal(2, line('.'))
-
- " When jumping to an entry in the location list window and the window
- " associated with the location list is not present and a window containing
- " the file is already present, then that window should be used.
- close
- belowright new
- call g:Xsetlist([], 'f')
- edit F3
- call win_gotoid(winid)
- Xlast
- call assert_equal(3, winnr())
- call assert_equal(6, g:Xgetlist({'size' : 1}).size)
- call assert_equal(winid, g:Xgetlist({'winid' : 1}).winid)
- endif
-
- " Cleanup
- enew!
- new | only
-
- call delete('F1')
- call delete('F2')
- call delete('F3')
-endfunc
-
-func Test_qfjump()
- call Xqfjump_tests('c')
- call Xqfjump_tests('l')
-endfunc
-
-" The following test used to crash Vim.
-" Open the location list window and close the regular window associated with
-" the location list. When the garbage collection runs now, it incorrectly
-" marks the location list context as not in use and frees the context.
-func Test_ll_window_ctx()
- call setloclist(0, [], 'f')
- call setloclist(0, [], 'a', {'context' : []})
- lopen | only
- call test_garbagecollect_now()
- echo getloclist(0, {'context' : 1}).context
- enew | only
+ au! BufNewFile
endfunc
-" The following test used to crash vim
-func Test_lfile_crash()
- sp Xtest
- au QuickFixCmdPre * bw
- call assert_fails('lfile', 'E40')
- au! QuickFixCmdPre
+" Test for the position of the quickfix and location list window
+func Test_qfwin_pos()
+ " Open two windows
+ new | only
+ new
+ cexpr ['F1:10:L10']
+ copen
+ " Quickfix window should be the bottom most window
+ call assert_equal(3, winnr())
+ close
+ " Open at the very top
+ wincmd t
+ topleft copen
+ call assert_equal(1, winnr())
+ close
+ " open left of the current window
+ wincmd t
+ below new
+ leftabove copen
+ call assert_equal(2, winnr())
+ close
+ " open right of the current window
+ rightbelow copen
+ call assert_equal(3, winnr())
+ close
endfunc
" Tests for quickfix/location lists changed by autocommands when
@@ -3371,6 +3421,137 @@ func Test_vimgrep_autocmd()
call setqflist([], 'f')
endfunc
+" The following test used to crash Vim
+func Test_lhelpgrep_autocmd()
+ lhelpgrep quickfix
+ autocmd QuickFixCmdPost * call setloclist(0, [], 'f')
+ lhelpgrep buffer
+ call assert_equal('help', &filetype)
+ call assert_equal(0, getloclist(0, {'nr' : '$'}).nr)
+ lhelpgrep tabpage
+ call assert_equal('help', &filetype)
+ call assert_equal(1, getloclist(0, {'nr' : '$'}).nr)
+ au! QuickFixCmdPost
+ new | only
+endfunc
+
+" Test for shortening/simplifying the file name when opening the
+" quickfix window or when displaying the quickfix list
+func Test_shorten_fname()
+ if !has('unix')
+ return
+ endif
+ %bwipe
+ " Create a quickfix list with a absolute path filename
+ let fname = getcwd() . '/test_quickfix.vim'
+ call setqflist([], ' ', {'lines':[fname . ":20:Line20"], 'efm':'%f:%l:%m'})
+ call assert_equal(fname, bufname('test_quickfix.vim'))
+ " Opening the quickfix window should simplify the file path
+ cwindow
+ call assert_equal('test_quickfix.vim', bufname('test_quickfix.vim'))
+ cclose
+ %bwipe
+ " Create a quickfix list with a absolute path filename
+ call setqflist([], ' ', {'lines':[fname . ":20:Line20"], 'efm':'%f:%l:%m'})
+ call assert_equal(fname, bufname('test_quickfix.vim'))
+ " Displaying the quickfix list should simplify the file path
+ silent! clist
+ call assert_equal('test_quickfix.vim', bufname('test_quickfix.vim'))
+endfunc
+
+" Quickfix title tests
+" In the below tests, 'exe "cmd"' is used to invoke the quickfix commands.
+" Otherwise due to indentation, the title is set with spaces at the beginning
+" of the command.
+func Test_qftitle()
+ call writefile(["F1:1:Line1"], 'Xerr')
+
+ " :cexpr
+ exe "cexpr readfile('Xerr')"
+ call assert_equal(":cexpr readfile('Xerr')", getqflist({'title' : 1}).title)
+
+ " :cgetexpr
+ exe "cgetexpr readfile('Xerr')"
+ call assert_equal(":cgetexpr readfile('Xerr')",
+ \ getqflist({'title' : 1}).title)
+
+ " :caddexpr
+ call setqflist([], 'f')
+ exe "caddexpr readfile('Xerr')"
+ call assert_equal(":caddexpr readfile('Xerr')",
+ \ getqflist({'title' : 1}).title)
+
+ " :cbuffer
+ new Xerr
+ exe "cbuffer"
+ call assert_equal(':cbuffer (Xerr)', getqflist({'title' : 1}).title)
+
+ " :cgetbuffer
+ edit Xerr
+ exe "cgetbuffer"
+ call assert_equal(':cgetbuffer (Xerr)', getqflist({'title' : 1}).title)
+
+ " :caddbuffer
+ call setqflist([], 'f')
+ edit Xerr
+ exe "caddbuffer"
+ call assert_equal(':caddbuffer (Xerr)', getqflist({'title' : 1}).title)
+
+ " :cfile
+ exe "cfile Xerr"
+ call assert_equal(':cfile Xerr', getqflist({'title' : 1}).title)
+
+ " :cgetfile
+ exe "cgetfile Xerr"
+ call assert_equal(':cgetfile Xerr', getqflist({'title' : 1}).title)
+
+ " :caddfile
+ call setqflist([], 'f')
+ exe "caddfile Xerr"
+ call assert_equal(':caddfile Xerr', getqflist({'title' : 1}).title)
+
+ " :grep
+ set grepprg=internal
+ exe "grep F1 Xerr"
+ call assert_equal(':grep F1 Xerr', getqflist({'title' : 1}).title)
+
+ " :grepadd
+ call setqflist([], 'f')
+ exe "grepadd F1 Xerr"
+ call assert_equal(':grepadd F1 Xerr', getqflist({'title' : 1}).title)
+ set grepprg&vim
+
+ " :vimgrep
+ exe "vimgrep F1 Xerr"
+ call assert_equal(':vimgrep F1 Xerr', getqflist({'title' : 1}).title)
+
+ " :vimgrepadd
+ call setqflist([], 'f')
+ exe "vimgrepadd F1 Xerr"
+ call assert_equal(':vimgrepadd F1 Xerr', getqflist({'title' : 1}).title)
+
+ call setqflist(['F1:10:L10'], ' ')
+ call assert_equal(':setqflist()', getqflist({'title' : 1}).title)
+
+ call setqflist([], 'f')
+ call setqflist(['F1:10:L10'], 'a')
+ call assert_equal(':setqflist()', getqflist({'title' : 1}).title)
+
+ call setqflist([], 'f')
+ call setqflist(['F1:10:L10'], 'r')
+ call assert_equal(':setqflist()', getqflist({'title' : 1}).title)
+
+ close
+ call delete('Xerr')
+
+ call setqflist([], ' ', {'title' : 'Errors'})
+ copen
+ call assert_equal('Errors', w:quickfix_title)
+ call setqflist([], 'r', {'items' : [{'filename' : 'a.c', 'lnum' : 10}]})
+ call assert_equal('Errors', w:quickfix_title)
+ cclose
+endfunc
+
func Test_lbuffer_with_bwipe()
new
new
@@ -3383,23 +3564,6 @@ func Test_lbuffer_with_bwipe()
augroup END
endfunc
-" Tests for the ':filter /pat/ clist' command
-func Test_filter_clist()
- cexpr ['Xfile1:10:10:Line 10', 'Xfile2:15:15:Line 15']
- call assert_equal([' 2 Xfile2:15 col 15: Line 15'],
- \ split(execute('filter /Line 15/ clist'), "\n"))
- call assert_equal([' 1 Xfile1:10 col 10: Line 10'],
- \ split(execute('filter /Xfile1/ clist'), "\n"))
- call assert_equal([], split(execute('filter /abc/ clist'), "\n"))
-
- call setqflist([{'module' : 'abc', 'pattern' : 'pat1'},
- \ {'module' : 'pqr', 'pattern' : 'pat2'}], ' ')
- call assert_equal([' 2 pqr:pat2: '],
- \ split(execute('filter /pqr/ clist'), "\n"))
- call assert_equal([' 1 abc:pat1: '],
- \ split(execute('filter /pat1/ clist'), "\n"))
-endfunc
-
" Test for an autocmd freeing the quickfix/location list when cexpr/lexpr is
" running
func Xexpr_acmd_freelist(cchar)
@@ -3549,6 +3713,23 @@ func Test_autocmd_changelist()
call Xautocmd_changelist('l')
endfunc
+" Tests for the ':filter /pat/ clist' command
+func Test_filter_clist()
+ cexpr ['Xfile1:10:10:Line 10', 'Xfile2:15:15:Line 15']
+ call assert_equal([' 2 Xfile2:15 col 15: Line 15'],
+ \ split(execute('filter /Line 15/ clist'), "\n"))
+ call assert_equal([' 1 Xfile1:10 col 10: Line 10'],
+ \ split(execute('filter /Xfile1/ clist'), "\n"))
+ call assert_equal([], split(execute('filter /abc/ clist'), "\n"))
+
+ call setqflist([{'module' : 'abc', 'pattern' : 'pat1'},
+ \ {'module' : 'pqr', 'pattern' : 'pat2'}], ' ')
+ call assert_equal([' 2 pqr:pat2: '],
+ \ split(execute('filter /pqr/ clist'), "\n"))
+ call assert_equal([' 1 abc:pat1: '],
+ \ split(execute('filter /pat1/ clist'), "\n"))
+endfunc
+
" Tests for the "CTRL-W <CR>" command.
func Xview_result_split_tests(cchar)
call s:setup_commands(a:cchar)
@@ -3575,3 +3756,269 @@ func Test_curswant()
call assert_equal(getcurpos()[4], virtcol('.'))
cclose | helpclose
endfunc
+
+" Test for parsing entries using visual screen column
+func Test_viscol()
+ enew
+ call writefile(["Col1\tCol2\tCol3"], 'Xfile1')
+ edit Xfile1
+
+ " Use byte offset for column number
+ set efm&
+ cexpr "Xfile1:1:5:XX\nXfile1:1:9:YY\nXfile1:1:20:ZZ"
+ call assert_equal([5, 8], [col('.'), virtcol('.')])
+ cnext
+ call assert_equal([9, 12], [col('.'), virtcol('.')])
+ cnext
+ call assert_equal([14, 20], [col('.'), virtcol('.')])
+
+ " Use screen column offset for column number
+ set efm=%f:%l:%v:%m
+ cexpr "Xfile1:1:8:XX\nXfile1:1:12:YY\nXfile1:1:20:ZZ"
+ call assert_equal([5, 8], [col('.'), virtcol('.')])
+ cnext
+ call assert_equal([9, 12], [col('.'), virtcol('.')])
+ cnext
+ call assert_equal([14, 20], [col('.'), virtcol('.')])
+ cexpr "Xfile1:1:6:XX\nXfile1:1:15:YY\nXfile1:1:24:ZZ"
+ call assert_equal([5, 8], [col('.'), virtcol('.')])
+ cnext
+ call assert_equal([10, 16], [col('.'), virtcol('.')])
+ cnext
+ call assert_equal([14, 20], [col('.'), virtcol('.')])
+
+ enew
+ call writefile(["Col1\täü\töß\tCol4"], 'Xfile1')
+
+ " Use byte offset for column number
+ set efm&
+ cexpr "Xfile1:1:8:XX\nXfile1:1:11:YY\nXfile1:1:16:ZZ"
+ call assert_equal([8, 10], [col('.'), virtcol('.')])
+ cnext
+ call assert_equal([11, 17], [col('.'), virtcol('.')])
+ cnext
+ call assert_equal([16, 25], [col('.'), virtcol('.')])
+
+ " Use screen column offset for column number
+ set efm=%f:%l:%v:%m
+ cexpr "Xfile1:1:10:XX\nXfile1:1:17:YY\nXfile1:1:25:ZZ"
+ call assert_equal([8, 10], [col('.'), virtcol('.')])
+ cnext
+ call assert_equal([11, 17], [col('.'), virtcol('.')])
+ cnext
+ call assert_equal([16, 25], [col('.'), virtcol('.')])
+
+ enew | only
+ set efm&
+ call delete('Xfile1')
+endfunc
+
+" Test to make sure that an empty quickfix buffer is not reused for loading
+" a normal buffer.
+func Test_empty_qfbuf()
+ enew | only
+ call writefile(["Test"], 'Xfile1')
+ call setqflist([], 'f')
+ copen | only
+ let qfbuf = bufnr('')
+ edit Xfile1
+ call assert_notequal(qfbuf, bufnr(''))
+ enew
+ call delete('Xfile1')
+endfunc
+
+" Test for the :cbelow, :cabove, :lbelow and :labove commands.
+func Xtest_below(cchar)
+ call s:setup_commands(a:cchar)
+
+ " No quickfix/location list
+ call assert_fails('Xbelow', 'E42:')
+ call assert_fails('Xabove', 'E42:')
+
+ " Empty quickfix/location list
+ call g:Xsetlist([])
+ call assert_fails('Xbelow', 'E42:')
+ call assert_fails('Xabove', 'E42:')
+
+ call s:create_test_file('X1')
+ call s:create_test_file('X2')
+ call s:create_test_file('X3')
+ call s:create_test_file('X4')
+
+ " Invalid entries
+ edit X1
+ call g:Xsetlist(["E1", "E2"])
+ call assert_fails('Xbelow', 'E42:')
+ call assert_fails('Xabove', 'E42:')
+ call assert_fails('3Xbelow', 'E42:')
+ call assert_fails('4Xabove', 'E42:')
+
+ " Test the commands with various arguments
+ Xexpr ["X1:5:L5", "X2:5:L5", "X2:10:L10", "X2:15:L15", "X3:3:L3"]
+ edit +7 X2
+ Xabove
+ call assert_equal(['X2', 5], [bufname(''), line('.')])
+ call assert_fails('Xabove', 'E553:')
+ normal 2j
+ Xbelow
+ call assert_equal(['X2', 10], [bufname(''), line('.')])
+ " Last error in this file
+ Xbelow 99
+ call assert_equal(['X2', 15], [bufname(''), line('.')])
+ call assert_fails('Xbelow', 'E553:')
+ " First error in this file
+ Xabove 99
+ call assert_equal(['X2', 5], [bufname(''), line('.')])
+ call assert_fails('Xabove', 'E553:')
+ normal gg
+ Xbelow 2
+ call assert_equal(['X2', 10], [bufname(''), line('.')])
+ normal G
+ Xabove 2
+ call assert_equal(['X2', 10], [bufname(''), line('.')])
+ edit X4
+ call assert_fails('Xabove', 'E42:')
+ call assert_fails('Xbelow', 'E42:')
+ if a:cchar == 'l'
+ " If a buffer has location list entries from some other window but not
+ " from the current window, then the commands should fail.
+ edit X1 | split | call setloclist(0, [], 'f')
+ call assert_fails('Xabove', 'E776:')
+ call assert_fails('Xbelow', 'E776:')
+ close
+ endif
+
+ " Test for lines with multiple quickfix entries
+ Xexpr ["X1:5:L5", "X2:5:1:L5_1", "X2:5:2:L5_2", "X2:5:3:L5_3",
+ \ "X2:10:1:L10_1", "X2:10:2:L10_2", "X2:10:3:L10_3",
+ \ "X2:15:1:L15_1", "X2:15:2:L15_2", "X2:15:3:L15_3", "X3:3:L3"]
+ edit +1 X2
+ Xbelow 2
+ call assert_equal(['X2', 10, 1], [bufname(''), line('.'), col('.')])
+ normal gg
+ Xbelow 99
+ call assert_equal(['X2', 15, 1], [bufname(''), line('.'), col('.')])
+ normal G
+ Xabove 2
+ call assert_equal(['X2', 10, 1], [bufname(''), line('.'), col('.')])
+ normal G
+ Xabove 99
+ call assert_equal(['X2', 5, 1], [bufname(''), line('.'), col('.')])
+ normal 10G
+ Xabove
+ call assert_equal(['X2', 5, 1], [bufname(''), line('.'), col('.')])
+ normal 10G
+ Xbelow
+ call assert_equal(['X2', 15, 1], [bufname(''), line('.'), col('.')])
+
+ " Invalid range
+ if a:cchar == 'c'
+ call assert_fails('-2cbelow', 'E553:')
+ " TODO: should go to first error in the current line?
+ 0cabove
+ else
+ call assert_fails('-2lbelow', 'E553:')
+ " TODO: should go to first error in the current line?
+ 0labove
+ endif
+
+ call delete('X1')
+ call delete('X2')
+ call delete('X3')
+ call delete('X4')
+endfunc
+
+func Test_cbelow()
+ call Xtest_below('c')
+ call Xtest_below('l')
+endfunc
+
+" Test for aborting quickfix commands using QuickFixCmdPre
+func Xtest_qfcmd_abort(cchar)
+ call s:setup_commands(a:cchar)
+
+ call g:Xsetlist([], 'f')
+
+ " cexpr/lexpr
+ let e = ''
+ try
+ Xexpr ["F1:10:Line10", "F2:20:Line20"]
+ catch /.*/
+ let e = v:exception
+ endtry
+ call assert_equal('AbortCmd', e)
+ call assert_equal(0, g:Xgetlist({'nr' : '$'}).nr)
+
+ " cfile/lfile
+ call writefile(["F1:10:Line10", "F2:20:Line20"], 'Xfile1')
+ let e = ''
+ try
+ Xfile Xfile1
+ catch /.*/
+ let e = v:exception
+ endtry
+ call assert_equal('AbortCmd', e)
+ call assert_equal(0, g:Xgetlist({'nr' : '$'}).nr)
+ call delete('Xfile1')
+
+ " cgetbuffer/lgetbuffer
+ enew!
+ call append(0, ["F1:10:Line10", "F2:20:Line20"])
+ let e = ''
+ try
+ Xgetbuffer
+ catch /.*/
+ let e = v:exception
+ endtry
+ call assert_equal('AbortCmd', e)
+ call assert_equal(0, g:Xgetlist({'nr' : '$'}).nr)
+ enew!
+
+ " vimgrep/lvimgrep
+ let e = ''
+ try
+ Xvimgrep /func/ test_quickfix.vim
+ catch /.*/
+ let e = v:exception
+ endtry
+ call assert_equal('AbortCmd', e)
+ call assert_equal(0, g:Xgetlist({'nr' : '$'}).nr)
+
+ " helpgrep/lhelpgrep
+ let e = ''
+ try
+ Xhelpgrep quickfix
+ catch /.*/
+ let e = v:exception
+ endtry
+ call assert_equal('AbortCmd', e)
+ call assert_equal(0, g:Xgetlist({'nr' : '$'}).nr)
+
+ " grep/lgrep
+ if has('unix')
+ let e = ''
+ try
+ silent Xgrep func test_quickfix.vim
+ catch /.*/
+ let e = v:exception
+ endtry
+ call assert_equal('AbortCmd', e)
+ call assert_equal(0, g:Xgetlist({'nr' : '$'}).nr)
+ endif
+endfunc
+
+func Test_qfcmd_abort()
+ augroup QF_Test
+ au!
+ autocmd QuickFixCmdPre * throw "AbortCmd"
+ augroup END
+
+ call Xtest_qfcmd_abort('c')
+ call Xtest_qfcmd_abort('l')
+
+ augroup QF_Test
+ au!
+ augroup END
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_quotestar.vim b/src/nvim/testdir/test_quotestar.vim
index b83fbe40e8..77a5153a81 100644
--- a/src/nvim/testdir/test_quotestar.vim
+++ b/src/nvim/testdir/test_quotestar.vim
@@ -54,34 +54,33 @@ func Do_test_quotestar_for_x11()
" Make sure a previous server has exited
try
call remote_send(name, ":qa!\<CR>")
- call WaitFor('serverlist() !~ "' . name . '"')
catch /E241:/
endtry
- call assert_notmatch(name, serverlist())
+ call WaitForAssert({-> assert_notmatch(name, serverlist())})
let cmd .= ' --servername ' . name
let job = job_start(cmd, {'stoponexit': 'kill', 'out_io': 'null'})
- call WaitFor({-> job_status(job) == "run"})
+ call WaitForAssert({-> assert_equal("run", job_status(job))})
" Takes a short while for the server to be active.
- call WaitFor('serverlist() =~ "' . name . '"')
+ call WaitForAssert({-> assert_match(name, serverlist())})
" Wait for the server to be up and answering requests. One second is not
" always sufficient.
- call WaitFor('remote_expr("' . name . '", "v:version", "", 2) != ""')
+ call WaitForAssert({-> assert_notequal('', remote_expr(name, "v:version", "", 2))})
" Clear the *-register of this vim instance and wait for it to be picked up
" by the server.
let @* = 'no'
call remote_foreground(name)
- call WaitFor('remote_expr("' . name . '", "@*", "", 1) == "no"')
+ call WaitForAssert({-> assert_equal("no", remote_expr(name, "@*", "", 1))})
" Set the * register on the server.
call remote_send(name, ":let @* = 'yes'\<CR>")
- call WaitFor('remote_expr("' . name . '", "@*", "", 1) == "yes"')
+ call WaitForAssert({-> assert_equal("yes", remote_expr(name, "@*", "", 1))})
" Check that the *-register of this vim instance is changed as expected.
- call WaitFor('@* == "yes"')
+ call WaitForAssert({-> assert_equal("yes", @*)})
" Handle the large selection over 262040 byte.
let length = 262044
@@ -109,18 +108,18 @@ func Do_test_quotestar_for_x11()
call remote_send(name, ":gui -f\<CR>")
endif
" Wait for the server in the GUI to be up and answering requests.
- call WaitFor('remote_expr("' . name . '", "has(\"gui_running\")", "", 1) =~ "1"')
+ " On some systems and with valgrind this can be very slow.
+ call WaitForAssert({-> assert_match("1", remote_expr(name, "has('gui_running')", "", 1))}, 10000)
call remote_send(name, ":let @* = 'maybe'\<CR>")
- call WaitFor('remote_expr("' . name . '", "@*", "", 1) == "maybe"')
- call assert_equal('maybe', remote_expr(name, "@*", "", 2))
+ call WaitForAssert({-> assert_equal("maybe", remote_expr(name, "@*", "", 2))})
call assert_equal('maybe', @*)
endif
call remote_send(name, ":qa!\<CR>")
try
- call WaitFor({-> job_status(job) == "dead"})
+ call WaitForAssert({-> assert_equal("dead", job_status(job))})
finally
if job_status(job) != 'dead'
call assert_report('Server did not exit')
diff --git a/src/nvim/testdir/test_recover.vim b/src/nvim/testdir/test_recover.vim
index 09c8d1cda6..fc073cacd2 100644
--- a/src/nvim/testdir/test_recover.vim
+++ b/src/nvim/testdir/test_recover.vim
@@ -14,6 +14,12 @@ func Test_recover_root_dir()
set dir=/notexist/
endif
call assert_fails('split Xtest', 'E303:')
+
+ " No error with empty 'directory' setting.
+ set directory=
+ split XtestOK
+ close!
+
set dir&
endfunc
diff --git a/src/nvim/testdir/test_regexp_latin.vim b/src/nvim/testdir/test_regexp_latin.vim
index b5e99b0ed3..2ee0ee1c0c 100644
--- a/src/nvim/testdir/test_regexp_latin.vim
+++ b/src/nvim/testdir/test_regexp_latin.vim
@@ -3,7 +3,7 @@ set encoding=latin1
scriptencoding latin1
func s:equivalence_test()
- let str = "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àáâãäå 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"
+ let str = "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àáâãäå 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"
let groups = split(str)
for group1 in groups
for c in split(group1, '\zs')
@@ -98,3 +98,669 @@ func Test_out_of_memory()
" This will be slow...
call assert_fails('call search("\\v((n||<)+);")', 'E363:')
endfunc
+
+" Tests for regexp patterns without multi-byte support.
+func Test_regexp_single_line_pat()
+ " tl is a List of Lists with:
+ " regexp engine
+ " regexp pattern
+ " text to test the pattern on
+ " expected match (optional)
+ " expected submatch 1 (optional)
+ " expected submatch 2 (optional)
+ " etc.
+ " When there is no match use only the first two items.
+ let tl = []
+
+ call add(tl, [2, 'ab', 'aab', 'ab'])
+ call add(tl, [2, 'b', 'abcdef', 'b'])
+ call add(tl, [2, 'bc*', 'abccccdef', 'bcccc'])
+ call add(tl, [2, 'bc\{-}', 'abccccdef', 'b'])
+ call add(tl, [2, 'bc\{-}\(d\)', 'abccccdef', 'bccccd', 'd'])
+ call add(tl, [2, 'bc*', 'abbdef', 'b'])
+ call add(tl, [2, 'c*', 'ccc', 'ccc'])
+ call add(tl, [2, 'bc*', 'abdef', 'b'])
+ call add(tl, [2, 'c*', 'abdef', ''])
+ call add(tl, [2, 'bc\+', 'abccccdef', 'bcccc'])
+ call add(tl, [2, 'bc\+', 'abdef']) " no match
+
+ " operator \|
+ call add(tl, [2, 'a\|ab', 'cabd', 'a']) " alternation is ordered
+
+ call add(tl, [2, 'c\?', 'ccb', 'c'])
+ call add(tl, [2, 'bc\?', 'abd', 'b'])
+ call add(tl, [2, 'bc\?', 'abccd', 'bc'])
+
+ call add(tl, [2, '\va{1}', 'ab', 'a'])
+
+ call add(tl, [2, '\va{2}', 'aa', 'aa'])
+ call add(tl, [2, '\va{2}', 'caad', 'aa'])
+ call add(tl, [2, '\va{2}', 'aba'])
+ call add(tl, [2, '\va{2}', 'ab'])
+ call add(tl, [2, '\va{2}', 'abaa', 'aa'])
+ call add(tl, [2, '\va{2}', 'aaa', 'aa'])
+
+ call add(tl, [2, '\vb{1}', 'abca', 'b'])
+ call add(tl, [2, '\vba{2}', 'abaa', 'baa'])
+ call add(tl, [2, '\vba{3}', 'aabaac'])
+
+ call add(tl, [2, '\v(ab){1}', 'ab', 'ab', 'ab'])
+ call add(tl, [2, '\v(ab){1}', 'dabc', 'ab', 'ab'])
+ call add(tl, [2, '\v(ab){1}', 'acb'])
+
+ call add(tl, [2, '\v(ab){0,2}', 'acb', "", ""])
+ call add(tl, [2, '\v(ab){0,2}', 'ab', 'ab', 'ab'])
+ call add(tl, [2, '\v(ab){1,2}', 'ab', 'ab', 'ab'])
+ call add(tl, [2, '\v(ab){1,2}', 'ababc', 'abab', 'ab'])
+ call add(tl, [2, '\v(ab){2,4}', 'ababcab', 'abab', 'ab'])
+ call add(tl, [2, '\v(ab){2,4}', 'abcababa', 'abab', 'ab'])
+
+ call add(tl, [2, '\v(ab){2}', 'abab', 'abab', 'ab'])
+ call add(tl, [2, '\v(ab){2}', 'cdababe', 'abab', 'ab'])
+ call add(tl, [2, '\v(ab){2}', 'abac'])
+ call add(tl, [2, '\v(ab){2}', 'abacabab', 'abab', 'ab'])
+ call add(tl, [2, '\v((ab){2}){2}', 'abababab', 'abababab', 'abab', 'ab'])
+ call add(tl, [2, '\v((ab){2}){2}', 'abacabababab', 'abababab', 'abab', 'ab'])
+
+ call add(tl, [2, '\v(a{1}){1}', 'a', 'a', 'a'])
+ call add(tl, [2, '\v(a{2}){1}', 'aa', 'aa', 'aa'])
+ call add(tl, [2, '\v(a{2}){1}', 'aaac', 'aa', 'aa'])
+ call add(tl, [2, '\v(a{2}){1}', 'daaac', 'aa', 'aa'])
+ call add(tl, [2, '\v(a{1}){2}', 'daaac', 'aa', 'a'])
+ call add(tl, [2, '\v(a{1}){2}', 'aaa', 'aa', 'a'])
+ call add(tl, [2, '\v(a{2})+', 'adaac', 'aa', 'aa'])
+ call add(tl, [2, '\v(a{2})+', 'aa', 'aa', 'aa'])
+ call add(tl, [2, '\v(a{2}){1}', 'aa', 'aa', 'aa'])
+ call add(tl, [2, '\v(a{1}){2}', 'aa', 'aa', 'a'])
+ call add(tl, [2, '\v(a{1}){1}', 'a', 'a', 'a'])
+ call add(tl, [2, '\v(a{2}){2}', 'aaaa', 'aaaa', 'aa'])
+ call add(tl, [2, '\v(a{2}){2}', 'aaabaaaa', 'aaaa', 'aa'])
+
+ call add(tl, [2, '\v(a+){2}', 'dadaac', 'aa', 'a'])
+ call add(tl, [2, '\v(a{3}){2}', 'aaaaaaa', 'aaaaaa', 'aaa'])
+
+ call add(tl, [2, '\v(a{1,2}){2}', 'daaac', 'aaa', 'a'])
+ call add(tl, [2, '\v(a{1,3}){2}', 'daaaac', 'aaaa', 'a'])
+ call add(tl, [2, '\v(a{1,3}){2}', 'daaaaac', 'aaaaa', 'aa'])
+ call add(tl, [2, '\v(a{1,3}){3}', 'daac'])
+ call add(tl, [2, '\v(a{1,2}){2}', 'dac'])
+ call add(tl, [2, '\v(a+)+', 'daac', 'aa', 'aa'])
+ call add(tl, [2, '\v(a+)+', 'aaa', 'aaa', 'aaa'])
+ call add(tl, [2, '\v(a+){1,2}', 'aaa', 'aaa', 'aaa'])
+ call add(tl, [2, '\v(a+)(a+)', 'aaa', 'aaa', 'aa', 'a'])
+ call add(tl, [2, '\v(a{3})+', 'daaaac', 'aaa', 'aaa'])
+ call add(tl, [2, '\v(a|b|c)+', 'aacb', 'aacb', 'b'])
+ call add(tl, [2, '\v(a|b|c){2}', 'abcb', 'ab', 'b'])
+ call add(tl, [2, '\v(abc){2}', 'abcabd', ])
+ call add(tl, [2, '\v(abc){2}', 'abdabcabc','abcabc', 'abc'])
+
+ call add(tl, [2, 'a*', 'cc', ''])
+ call add(tl, [2, '\v(a*)+', 'cc', ''])
+ call add(tl, [2, '\v((ab)+)+', 'ab', 'ab', 'ab', 'ab'])
+ call add(tl, [2, '\v(((ab)+)+)+', 'ab', 'ab', 'ab', 'ab', 'ab'])
+ call add(tl, [2, '\v(((ab)+)+)+', 'dababc', 'abab', 'abab', 'abab', 'ab'])
+ call add(tl, [2, '\v(a{0,2})+', 'cc', ''])
+ call add(tl, [2, '\v(a*)+', '', ''])
+ call add(tl, [2, '\v((a*)+)+', '', ''])
+ call add(tl, [2, '\v((ab)*)+', '', ''])
+ call add(tl, [2, '\va{1,3}', 'aab', 'aa'])
+ call add(tl, [2, '\va{2,3}', 'abaa', 'aa'])
+
+ call add(tl, [2, '\v((ab)+|c*)+', 'abcccaba', 'abcccab', '', 'ab'])
+ call add(tl, [2, '\v(a{2})|(b{3})', 'bbabbbb', 'bbb', '', 'bbb'])
+ call add(tl, [2, '\va{2}|b{2}', 'abab'])
+ call add(tl, [2, '\v(a)+|(c)+', 'bbacbaacbbb', 'a', 'a'])
+ call add(tl, [2, '\vab{2,3}c', 'aabbccccccccccccc', 'abbc'])
+ call add(tl, [2, '\vab{2,3}c', 'aabbbccccccccccccc', 'abbbc'])
+ call add(tl, [2, '\vab{2,3}cd{2,3}e', 'aabbbcddee', 'abbbcdde'])
+ call add(tl, [2, '\va(bc){2}d', 'aabcbfbc' ])
+ call add(tl, [2, '\va*a{2}', 'a', ])
+ call add(tl, [2, '\va*a{2}', 'aa', 'aa' ])
+ call add(tl, [2, '\va*a{2}', 'aaa', 'aaa' ])
+ call add(tl, [2, '\va*a{2}', 'bbbabcc', ])
+ call add(tl, [2, '\va*b*|a*c*', 'a', 'a'])
+ call add(tl, [2, '\va{1}b{1}|a{1}b{1}', ''])
+
+ " submatches
+ call add(tl, [2, '\v(a)', 'ab', 'a', 'a'])
+ call add(tl, [2, '\v(a)(b)', 'ab', 'ab', 'a', 'b'])
+ call add(tl, [2, '\v(ab)(b)(c)', 'abbc', 'abbc', 'ab', 'b', 'c'])
+ call add(tl, [2, '\v((a)(b))', 'ab', 'ab', 'ab', 'a', 'b'])
+ call add(tl, [2, '\v(a)|(b)', 'ab', 'a', 'a'])
+
+ call add(tl, [2, '\v(a*)+', 'aaaa', 'aaaa', ''])
+ call add(tl, [2, 'x', 'abcdef'])
+
+ "
+ " Simple tests
+ "
+
+ " Search single groups
+ call add(tl, [2, 'ab', 'aab', 'ab'])
+ call add(tl, [2, 'ab', 'baced'])
+ call add(tl, [2, 'ab', ' ab ', 'ab'])
+
+ " Search multi-modifiers
+ call add(tl, [2, 'x*', 'xcd', 'x'])
+ call add(tl, [2, 'x*', 'xxxxxxxxxxxxxxxxsofijiojgf', 'xxxxxxxxxxxxxxxx'])
+ " empty match is good
+ call add(tl, [2, 'x*', 'abcdoij', ''])
+ " no match here
+ call add(tl, [2, 'x\+', 'abcdoin'])
+ call add(tl, [2, 'x\+', 'abcdeoijdfxxiuhfij', 'xx'])
+ call add(tl, [2, 'x\+', 'xxxxx', 'xxxxx'])
+ call add(tl, [2, 'x\+', 'abc x siufhiush xxxxxxxxx', 'x'])
+ call add(tl, [2, 'x\=', 'x sdfoij', 'x'])
+ call add(tl, [2, 'x\=', 'abc sfoij', '']) " empty match is good
+ call add(tl, [2, 'x\=', 'xxxxxxxxx c', 'x'])
+ call add(tl, [2, 'x\?', 'x sdfoij', 'x'])
+ " empty match is good
+ call add(tl, [2, 'x\?', 'abc sfoij', ''])
+ call add(tl, [2, 'x\?', 'xxxxxxxxxx c', 'x'])
+
+ call add(tl, [2, 'a\{0,0}', 'abcdfdoij', ''])
+ " same thing as 'a?'
+ call add(tl, [2, 'a\{0,1}', 'asiubid axxxaaa', 'a'])
+ " same thing as 'a\{0,1}'
+ call add(tl, [2, 'a\{1,0}', 'asiubid axxxaaa', 'a'])
+ call add(tl, [2, 'a\{3,6}', 'aa siofuh'])
+ call add(tl, [2, 'a\{3,6}', 'aaaaa asfoij afaa', 'aaaaa'])
+ call add(tl, [2, 'a\{3,6}', 'aaaaaaaa', 'aaaaaa'])
+ call add(tl, [2, 'a\{0}', 'asoiuj', ''])
+ call add(tl, [2, 'a\{2}', 'aaaa', 'aa'])
+ call add(tl, [2, 'a\{2}', 'iuash fiusahfliusah fiushfilushfi uhsaifuh askfj nasfvius afg aaaa sfiuhuhiushf', 'aa'])
+ call add(tl, [2, 'a\{2}', 'abcdefghijklmnopqrestuvwxyz1234567890'])
+ " same thing as 'a*'
+ call add(tl, [2, 'a\{0,}', 'oij sdigfusnf', ''])
+ call add(tl, [2, 'a\{0,}', 'aaaaa aa', 'aaaaa'])
+ call add(tl, [2, 'a\{2,}', 'sdfiougjdsafg'])
+ call add(tl, [2, 'a\{2,}', 'aaaaasfoij ', 'aaaaa'])
+ call add(tl, [2, 'a\{5,}', 'xxaaaaxxx '])
+ call add(tl, [2, 'a\{5,}', 'xxaaaaaxxx ', 'aaaaa'])
+ call add(tl, [2, 'a\{,0}', 'oidfguih iuhi hiu aaaa', ''])
+ call add(tl, [2, 'a\{,5}', 'abcd', 'a'])
+ call add(tl, [2, 'a\{,5}', 'aaaaaaaaaa', 'aaaaa'])
+ " leading star as normal char when \{} follows
+ call add(tl, [2, '^*\{4,}$', '***'])
+ call add(tl, [2, '^*\{4,}$', '****', '****'])
+ call add(tl, [2, '^*\{4,}$', '*****', '*****'])
+ " same thing as 'a*'
+ call add(tl, [2, 'a\{}', 'bbbcddiuhfcd', ''])
+ call add(tl, [2, 'a\{}', 'aaaaioudfh coisf jda', 'aaaa'])
+
+ call add(tl, [2, 'a\{-0,0}', 'abcdfdoij', ''])
+ " anti-greedy version of 'a?'
+ call add(tl, [2, 'a\{-0,1}', 'asiubid axxxaaa', ''])
+ call add(tl, [2, 'a\{-3,6}', 'aa siofuh'])
+ call add(tl, [2, 'a\{-3,6}', 'aaaaa asfoij afaa', 'aaa'])
+ call add(tl, [2, 'a\{-3,6}', 'aaaaaaaa', 'aaa'])
+ call add(tl, [2, 'a\{-0}', 'asoiuj', ''])
+ call add(tl, [2, 'a\{-2}', 'aaaa', 'aa'])
+ call add(tl, [2, 'a\{-2}', 'abcdefghijklmnopqrestuvwxyz1234567890'])
+ call add(tl, [2, 'a\{-0,}', 'oij sdigfusnf', ''])
+ call add(tl, [2, 'a\{-0,}', 'aaaaa aa', ''])
+ call add(tl, [2, 'a\{-2,}', 'sdfiougjdsafg'])
+ call add(tl, [2, 'a\{-2,}', 'aaaaasfoij ', 'aa'])
+ call add(tl, [2, 'a\{-,0}', 'oidfguih iuhi hiu aaaa', ''])
+ call add(tl, [2, 'a\{-,5}', 'abcd', ''])
+ call add(tl, [2, 'a\{-,5}', 'aaaaaaaaaa', ''])
+ " anti-greedy version of 'a*'
+ call add(tl, [2, 'a\{-}', 'bbbcddiuhfcd', ''])
+ call add(tl, [2, 'a\{-}', 'aaaaioudfh coisf jda', ''])
+
+ " Test groups of characters and submatches
+ call add(tl, [2, '\(abc\)*', 'abcabcabc', 'abcabcabc', 'abc'])
+ call add(tl, [2, '\(ab\)\+', 'abababaaaaa', 'ababab', 'ab'])
+ call add(tl, [2, '\(abaaaaa\)*cd', 'cd', 'cd', ''])
+ call add(tl, [2, '\(test1\)\? \(test2\)\?', 'test1 test3', 'test1 ', 'test1', ''])
+ call add(tl, [2, '\(test1\)\= \(test2\) \(test4443\)\=', ' test2 test4443 yupiiiiiiiiiii', ' test2 test4443', '', 'test2', 'test4443'])
+ call add(tl, [2, '\(\(sub1\) hello \(sub 2\)\)', 'asterix sub1 hello sub 2 obelix', 'sub1 hello sub 2', 'sub1 hello sub 2', 'sub1', 'sub 2'])
+ call add(tl, [2, '\(\(\(yyxxzz\)\)\)', 'abcdddsfiusfyyzzxxyyxxzz', 'yyxxzz', 'yyxxzz', 'yyxxzz', 'yyxxzz'])
+ call add(tl, [2, '\v((ab)+|c+)+', 'abcccaba', 'abcccab', 'ab', 'ab'])
+ call add(tl, [2, '\v((ab)|c*)+', 'abcccaba', 'abcccab', '', 'ab'])
+ call add(tl, [2, '\v(a(c*)+b)+', 'acbababaaa', 'acbabab', 'ab', ''])
+ call add(tl, [2, '\v(a|b*)+', 'aaaa', 'aaaa', ''])
+ call add(tl, [2, '\p*', 'aá ', 'aá '])
+
+ " Test greedy-ness and lazy-ness
+ call add(tl, [2, 'a\{-2,7}','aaaaaaaaaaaaa', 'aa'])
+ call add(tl, [2, 'a\{-2,7}x','aaaaaaaaax', 'aaaaaaax'])
+ call add(tl, [2, 'a\{2,7}','aaaaaaaaaaaaaaaaaaaa', 'aaaaaaa'])
+ call add(tl, [2, 'a\{2,7}x','aaaaaaaaax', 'aaaaaaax'])
+ call add(tl, [2, '\vx(.{-,8})yz(.*)','xayxayzxayzxayz','xayxayzxayzxayz','ayxa','xayzxayz'])
+ call add(tl, [2, '\vx(.*)yz(.*)','xayxayzxayzxayz','xayxayzxayzxayz', 'ayxayzxayzxa',''])
+ call add(tl, [2, '\v(a{1,2}){-2,3}','aaaaaaa','aaaa','aa'])
+ call add(tl, [2, '\v(a{-1,3})+', 'aa', 'aa', 'a'])
+ call add(tl, [2, '^\s\{-}\zs\( x\|x$\)', ' x', ' x', ' x'])
+ call add(tl, [2, '^\s\{-}\zs\(x\| x$\)', ' x', ' x', ' x'])
+ call add(tl, [2, '^\s\{-}\ze\(x\| x$\)', ' x', '', ' x'])
+ call add(tl, [2, '^\(\s\{-}\)\(x\| x$\)', ' x', ' x', '', ' x'])
+
+ " Test Character classes
+ call add(tl, [2, '\d\+e\d\d','test 10e23 fd','10e23'])
+
+ " Test collections and character range []
+ call add(tl, [2, '\v[a]', 'abcd', 'a'])
+ call add(tl, [2, 'a[bcd]', 'abcd', 'ab'])
+ call add(tl, [2, 'a[b-d]', 'acbd', 'ac'])
+ call add(tl, [2, '[a-d][e-f][x-x]d', 'cexdxx', 'cexd'])
+ call add(tl, [2, '\v[[:alpha:]]+', 'abcdefghijklmnopqrstuvwxyz6','abcdefghijklmnopqrstuvwxyz'])
+ call add(tl, [2, '[[:alpha:]\+]', '6x8','x'])
+ call add(tl, [2, '[^abc]\+','abcabcabc'])
+ call add(tl, [2, '[^abc]','defghiasijvoinasoiunbvb','d'])
+ call add(tl, [2, '[^abc]\+','ddddddda','ddddddd'])
+ call add(tl, [2, '[^a-d]\+','aaaAAAZIHFNCddd','AAAZIHFNC'])
+ call add(tl, [2, '[a-f]*','iiiiiiii',''])
+ call add(tl, [2, '[a-f]*','abcdefgh','abcdef'])
+ call add(tl, [2, '[^a-f]\+','abcdefgh','gh'])
+ call add(tl, [2, '[a-c]\{-3,6}','abcabc','abc'])
+ call add(tl, [2, '[^[:alpha:]]\+','abcccadfoij7787ysf287yrnccdu','7787'])
+ call add(tl, [2, '[-a]', '-', '-'])
+ call add(tl, [2, '[a-]', '-', '-'])
+ call add(tl, [2, '[a-f]*\c','ABCDEFGH','ABCDEF'])
+ call add(tl, [2, '[abc][xyz]\c','-af-AF-BY--','BY'])
+ " filename regexp
+ call add(tl, [2, '[-./[:alnum:]_~]\+', 'log13.file', 'log13.file'])
+ " special chars
+ call add(tl, [2, '[\]\^\-\\]\+', '\^\\\-\---^', '\^\\\-\---^'])
+ " collation elem
+ call add(tl, [2, '[[.a.]]\+', 'aa', 'aa'])
+ " middle of regexp
+ call add(tl, [2, 'abc[0-9]*ddd', 'siuhabc ii'])
+ call add(tl, [2, 'abc[0-9]*ddd', 'adf abc44482ddd oijs', 'abc44482ddd'])
+ call add(tl, [2, '\_[0-9]\+', 'asfi9888u', '9888'])
+ call add(tl, [2, '[0-9\n]\+', 'asfi9888u', '9888'])
+ call add(tl, [2, '\_[0-9]\+', "asfi\n9888u", "\n9888"])
+ call add(tl, [2, '\_f', " \na ", "\n"])
+ call add(tl, [2, '\_f\+', " \na ", "\na"])
+ call add(tl, [2, '[0-9A-Za-z-_.]\+', " @0_a.A-{ ", "0_a.A-"])
+
+ " Test start/end of line, start/end of file
+ call add(tl, [2, '^a.', "a_\nb ", "a_"])
+ call add(tl, [2, '^a.', "b a \na_"])
+ call add(tl, [2, '.a$', " a\n "])
+ call add(tl, [2, '.a$', " a b\n_a", "_a"])
+ call add(tl, [2, '\%^a.', "a a\na", "a "])
+ call add(tl, [2, '\%^a', " a \na "])
+ call add(tl, [2, '.a\%$', " a\n "])
+ call add(tl, [2, '.a\%$', " a\n_a", "_a"])
+
+ " Test recognition of character classes
+ call add(tl, [2, '[0-7]\+', 'x0123456789x', '01234567'])
+ call add(tl, [2, '[^0-7]\+', '0a;X+% 897', 'a;X+% 89'])
+ call add(tl, [2, '[0-9]\+', 'x0123456789x', '0123456789'])
+ call add(tl, [2, '[^0-9]\+', '0a;X+% 9', 'a;X+% '])
+ call add(tl, [2, '[0-9a-fA-F]\+', 'x0189abcdefg', '0189abcdef'])
+ call add(tl, [2, '[^0-9A-Fa-f]\+', '0189g;X+% ab', 'g;X+% '])
+ call add(tl, [2, '[a-z_A-Z0-9]\+', ';+aso_SfOij ', 'aso_SfOij'])
+ call add(tl, [2, '[^a-z_A-Z0-9]\+', 'aSo_;+% sfOij', ';+% '])
+ call add(tl, [2, '[a-z_A-Z]\+', '0abyz_ABYZ;', 'abyz_ABYZ'])
+ call add(tl, [2, '[^a-z_A-Z]\+', 'abAB_09;+% yzYZ', '09;+% '])
+ call add(tl, [2, '[a-z]\+', '0abcxyz1', 'abcxyz'])
+ call add(tl, [2, '[a-z]\+', 'AabxyzZ', 'abxyz'])
+ call add(tl, [2, '[^a-z]\+', 'a;X09+% x', ';X09+% '])
+ call add(tl, [2, '[^a-z]\+', 'abX0;%yz', 'X0;%'])
+ call add(tl, [2, '[a-zA-Z]\+', '0abABxzXZ9', 'abABxzXZ'])
+ call add(tl, [2, '[^a-zA-Z]\+', 'ab09_;+ XZ', '09_;+ '])
+ call add(tl, [2, '[A-Z]\+', 'aABXYZz', 'ABXYZ'])
+ call add(tl, [2, '[^A-Z]\+', 'ABx0;%YZ', 'x0;%'])
+ call add(tl, [2, '[a-z]\+\c', '0abxyzABXYZ;', 'abxyzABXYZ'])
+ call add(tl, [2, '[A-Z]\+\c', '0abABxzXZ9', 'abABxzXZ'])
+ call add(tl, [2, '\c[^a-z]\+', 'ab09_;+ XZ', '09_;+ '])
+ call add(tl, [2, '\c[^A-Z]\+', 'ab09_;+ XZ', '09_;+ '])
+ call add(tl, [2, '\C[^A-Z]\+', 'ABCOIJDEOIFNSD jsfoij sa', ' jsfoij sa'])
+
+ " Tests for \z features
+ " match ends at \ze
+ call add(tl, [2, 'xx \ze test', 'xx '])
+ call add(tl, [2, 'abc\zeend', 'oij abcend', 'abc'])
+ call add(tl, [2, 'aa\zebb\|aaxx', ' aabb ', 'aa'])
+ call add(tl, [2, 'aa\zebb\|aaxx', ' aaxx ', 'aaxx'])
+ call add(tl, [2, 'aabb\|aa\zebb', ' aabb ', 'aabb'])
+ call add(tl, [2, 'aa\zebb\|aaebb', ' aabb ', 'aa'])
+ " match starts at \zs
+ call add(tl, [2, 'abc\zsdd', 'ddabcddxyzt', 'dd'])
+ call add(tl, [2, 'aa \zsax', ' ax'])
+ call add(tl, [2, 'abc \zsmatch\ze abc', 'abc abc abc match abc abc', 'match'])
+ call add(tl, [2, '\v(a \zsif .*){2}', 'a if then a if last', 'if last', 'a if last'])
+ call add(tl, [2, '\>\zs.', 'aword. ', '.'])
+ call add(tl, [2, '\s\+\ze\[/\|\s\zs\s\+', 'is [a t', ' '])
+
+ " Tests for \@= and \& features
+ call add(tl, [2, 'abc\@=', 'abc', 'ab'])
+ call add(tl, [2, 'abc\@=cd', 'abcd', 'abcd'])
+ call add(tl, [2, 'abc\@=', 'ababc', 'ab'])
+ " will never match, no matter the input text
+ call add(tl, [2, 'abcd\@=e', 'abcd'])
+ " will never match
+ call add(tl, [2, 'abcd\@=e', 'any text in here ... '])
+ call add(tl, [2, '\v(abc)@=..', 'xabcd', 'ab', 'abc'])
+ call add(tl, [2, '\(.*John\)\@=.*Bob', 'here is John, and here is B'])
+ call add(tl, [2, '\(John.*\)\@=.*Bob', 'John is Bobs friend', 'John is Bob', 'John is Bobs friend'])
+ call add(tl, [2, '\<\S\+\())\)\@=', '$((i=i+1))', 'i=i+1', '))'])
+ call add(tl, [2, '.*John\&.*Bob', 'here is John, and here is B'])
+ call add(tl, [2, '.*John\&.*Bob', 'John is Bobs friend', 'John is Bob'])
+ call add(tl, [2, '\v(test1)@=.*yep', 'this is a test1, yep it is', 'test1, yep', 'test1'])
+ call add(tl, [2, 'foo\(bar\)\@!', 'foobar'])
+ call add(tl, [2, 'foo\(bar\)\@!', 'foo bar', 'foo'])
+ call add(tl, [2, 'if \(\(then\)\@!.\)*$', ' if then else'])
+ call add(tl, [2, 'if \(\(then\)\@!.\)*$', ' if else ', 'if else ', ' '])
+ call add(tl, [2, '\(foo\)\@!bar', 'foobar', 'bar'])
+ call add(tl, [2, '\(foo\)\@!...bar', 'foobar'])
+ call add(tl, [2, '^\%(.*bar\)\@!.*\zsfoo', ' bar foo '])
+ call add(tl, [2, '^\%(.*bar\)\@!.*\zsfoo', ' foo bar '])
+ call add(tl, [2, '^\%(.*bar\)\@!.*\zsfoo', ' foo xxx ', 'foo'])
+ call add(tl, [2, '[ ]\@!\p\%([ ]\@!\p\)*:', 'implicit mappings:', 'mappings:'])
+ call add(tl, [2, '[ ]\@!\p\([ ]\@!\p\)*:', 'implicit mappings:', 'mappings:', 's'])
+ call add(tl, [2, 'm\k\+_\@=\%(_\@!\k\)\@<=\k\+e', 'mx__xe', 'mx__xe'])
+ call add(tl, [2, '\%(\U\@<=S\k*\|S\l\)R', 'SuR', 'SuR'])
+
+ " Combining different tests and features
+ call add(tl, [2, '[[:alpha:]]\{-2,6}', '787abcdiuhsasiuhb4', 'ab'])
+ call add(tl, [2, '', 'abcd', ''])
+ call add(tl, [2, '\v(())', 'any possible text', ''])
+ call add(tl, [2, '\v%(ab(xyz)c)', ' abxyzc ', 'abxyzc', 'xyz'])
+ call add(tl, [2, '\v(test|)empty', 'tesempty', 'empty', ''])
+ call add(tl, [2, '\v(a|aa)(a|aa)', 'aaa', 'aa', 'a', 'a'])
+
+ " \%u and friends
+ call add(tl, [2, '\%d32', 'yes no', ' '])
+ call add(tl, [2, '\%o40', 'yes no', ' '])
+ call add(tl, [2, '\%x20', 'yes no', ' '])
+ call add(tl, [2, '\%u0020', 'yes no', ' '])
+ call add(tl, [2, '\%U00000020', 'yes no', ' '])
+ call add(tl, [2, '\%d0', "yes\x0ano", "\x0a"])
+
+ "" \%[abc]
+ call add(tl, [2, 'foo\%[bar]', 'fobar'])
+ call add(tl, [2, 'foo\%[bar]', 'foobar', 'foobar'])
+ call add(tl, [2, 'foo\%[bar]', 'fooxx', 'foo'])
+ call add(tl, [2, 'foo\%[bar]', 'foobxx', 'foob'])
+ call add(tl, [2, 'foo\%[bar]', 'foobaxx', 'fooba'])
+ call add(tl, [2, 'foo\%[bar]', 'foobarxx', 'foobar'])
+ call add(tl, [2, 'foo\%[bar]x', 'foobxx', 'foobx'])
+ call add(tl, [2, 'foo\%[bar]x', 'foobarxx', 'foobarx'])
+ call add(tl, [2, '\%[bar]x', 'barxx', 'barx'])
+ call add(tl, [2, '\%[bar]x', 'bxx', 'bx'])
+ call add(tl, [2, '\%[bar]x', 'xxx', 'x'])
+ call add(tl, [2, 'b\%[[ao]r]', 'bar bor', 'bar'])
+ call add(tl, [2, 'b\%[[]]r]', 'b]r bor', 'b]r'])
+ call add(tl, [2, '@\%[\w\-]*', '<http://john.net/pandoc/>[@pandoc]', '@pandoc'])
+
+ " Alternatives, must use first longest match
+ call add(tl, [2, 'goo\|go', 'google', 'goo'])
+ call add(tl, [2, '\<goo\|\<go', 'google', 'goo'])
+ call add(tl, [2, '\<goo\|go', 'google', 'goo'])
+
+ " Back references
+ call add(tl, [2, '\(\i\+\) \1', ' abc abc', 'abc abc', 'abc'])
+ call add(tl, [2, '\(\i\+\) \1', 'xgoo goox', 'goo goo', 'goo'])
+ call add(tl, [2, '\(a\)\(b\)\(c\)\(dd\)\(e\)\(f\)\(g\)\(h\)\(i\)\1\2\3\4\5\6\7\8\9', 'xabcddefghiabcddefghix', 'abcddefghiabcddefghi', 'a', 'b', 'c', 'dd', 'e', 'f', 'g', 'h', 'i'])
+ call add(tl, [2, '\(\d*\)a \1b', ' a b ', 'a b', ''])
+ call add(tl, [2, '^.\(.\).\_..\1.', "aaa\naaa\nb", "aaa\naaa", 'a'])
+ call add(tl, [2, '^.*\.\(.*\)/.\+\(\1\)\@<!$', 'foo.bat/foo.com', 'foo.bat/foo.com', 'bat'])
+ call add(tl, [2, '^.*\.\(.*\)/.\+\(\1\)\@<!$', 'foo.bat/foo.bat'])
+ call add(tl, [2, '^.*\.\(.*\)/.\+\(\1\)\@<=$', 'foo.bat/foo.bat', 'foo.bat/foo.bat', 'bat', 'bat'])
+ call add(tl, [2, '\\\@<!\${\(\d\+\%(:.\{-}\)\?\\\@<!\)}', '2013-06-27${0}', '${0}', '0'])
+ call add(tl, [2, '^\(a*\)\1$', 'aaaaaaaa', 'aaaaaaaa', 'aaaa'])
+ call add(tl, [2, '^\(a\{-2,}\)\1\+$', 'aaaaaaaaa', 'aaaaaaaaa', 'aaa'])
+
+ " Look-behind with limit
+ call add(tl, [2, '<\@<=span.', 'xxspanxx<spanyyy', 'spany'])
+ call add(tl, [2, '<\@1<=span.', 'xxspanxx<spanyyy', 'spany'])
+ call add(tl, [2, '<\@2<=span.', 'xxspanxx<spanyyy', 'spany'])
+ call add(tl, [2, '\(<<\)\@<=span.', 'xxspanxxxx<spanxx<<spanyyy', 'spany', '<<'])
+ call add(tl, [2, '\(<<\)\@1<=span.', 'xxspanxxxx<spanxx<<spanyyy'])
+ call add(tl, [2, '\(<<\)\@2<=span.', 'xxspanxxxx<spanxx<<spanyyy', 'spany', '<<'])
+ call add(tl, [2, '\(foo\)\@<!bar.', 'xx foobar1 xbar2 xx', 'bar2'])
+
+ " look-behind match in front of a zero-width item
+ call add(tl, [2, '\v\C%(<Last Changed:\s+)@<=.*$', '" test header'])
+ call add(tl, [2, '\v\C%(<Last Changed:\s+)@<=.*$', '" Last Changed: 1970', '1970'])
+ call add(tl, [2, '\(foo\)\@<=\>', 'foobar'])
+ call add(tl, [2, '\(foo\)\@<=\>', 'barfoo', '', 'foo'])
+ call add(tl, [2, '\(foo\)\@<=.*', 'foobar', 'bar', 'foo'])
+
+ " complicated look-behind match
+ call add(tl, [2, '\(r\@<=\|\w\@<!\)\/', 'x = /word/;', '/'])
+ call add(tl, [2, '^[a-z]\+\ze \&\(asdf\)\@<!', 'foo bar', 'foo'])
+
+ "" \@>
+ call add(tl, [2, '\(a*\)\@>a', 'aaaa'])
+ call add(tl, [2, '\(a*\)\@>b', 'aaab', 'aaab', 'aaa'])
+ call add(tl, [2, '^\(.\{-}b\)\@>.', ' abcbd', ' abc', ' ab'])
+ call add(tl, [2, '\(.\{-}\)\(\)\@>$', 'abc', 'abc', 'abc', ''])
+ " TODO: BT engine does not restore submatch after failure
+ call add(tl, [1, '\(a*\)\@>a\|a\+', 'aaaa', 'aaaa'])
+
+ " "\_" prepended negated collection matches EOL
+ call add(tl, [2, '\_[^8-9]\+', "asfi\n9888", "asfi\n"])
+ call add(tl, [2, '\_[^a]\+', "asfi\n9888", "sfi\n9888"])
+
+ " Requiring lots of states.
+ call add(tl, [2, '[0-9a-zA-Z]\{8}-\([0-9a-zA-Z]\{4}-\)\{3}[0-9a-zA-Z]\{12}', " 12345678-1234-1234-1234-123456789012 ", "12345678-1234-1234-1234-123456789012", "1234-"])
+
+ " Skip adding state twice
+ call add(tl, [2, '^\%(\%(^\s*#\s*if\>\|#\s*if\)\)\(\%>1c.*$\)\@=', "#if FOO", "#if", ' FOO'])
+
+ " Test \%V atom
+ call add(tl, [2, '\%>70vGesamt', 'Jean-Michel Charlier & Victor Hubinon\Gesamtausgabe [Salleck] Buck Danny {Jean-Michel Charlier & Victor Hubinon}\Gesamtausgabe', 'Gesamt'])
+
+ " Run the tests
+ for t in tl
+ let re = t[0]
+ let pat = t[1]
+ let text = t[2]
+ let matchidx = 3
+ for engine in [0, 1, 2]
+ if engine == 2 && re == 0 || engine == 1 && re == 1
+ continue
+ endif
+ let &regexpengine = engine
+ try
+ let l = matchlist(text, pat)
+ catch
+ call assert_report('Error ' . engine . ': pat: \"' . pat
+ \ . '\", text: \"' . text . '\", caused an exception: \"'
+ \ . v:exception . '\"')
+ endtry
+ " check the match itself
+ if len(l) == 0 && len(t) > matchidx
+ call assert_report('Error ' . engine . ': pat: \"' . pat
+ \ . '\", text: \"' . text . '\", did not match, expected: \"'
+ \ . t[matchidx] . '\"')
+ elseif len(l) > 0 && len(t) == matchidx
+ call assert_report('Error ' . engine . ': pat: \"' . pat
+ \ . '\", text: \"' . text . '\", match: \"' . l[0]
+ \ . '\", expected no match')
+ elseif len(t) > matchidx && l[0] != t[matchidx]
+ call assert_report('Error ' . engine . ': pat: \"' . pat
+ \ . '\", text: \"' . text . '\", match: \"' . l[0]
+ \ . '\", expected: \"' . t[matchidx] . '\"')
+ else
+ " Test passed
+ endif
+
+ " check all the nine submatches
+ if len(l) > 0
+ for i in range(1, 9)
+ if len(t) <= matchidx + i
+ let e = ''
+ else
+ let e = t[matchidx + i]
+ endif
+ if l[i] != e
+ call assert_report('Error ' . engine . ': pat: \"' . pat
+ \ . '\", text: \"' . text . '\", submatch ' . i . ': \"'
+ \ . l[i] . '\", expected: \"' . e . '\"')
+ endif
+ endfor
+ unlet i
+ endif
+ endfor
+ endfor
+
+ unlet t tl e l
+endfunc
+
+" Tests for multi-line regexp patterns without multi-byte support.
+func Test_regexp_multiline_pat()
+ let tl = []
+
+ " back references
+ call add(tl, [2, '^.\(.\).\_..\1.', ['aaa', 'aaa', 'b'], ['XX', 'b']])
+ call add(tl, [2, '\v.*\/(.*)\n.*\/\1$', ['./Dir1/Dir2/zyxwvuts.txt', './Dir1/Dir2/abcdefgh.bat', '', './Dir1/Dir2/file1.txt', './OtherDir1/OtherDir2/file1.txt'], ['./Dir1/Dir2/zyxwvuts.txt', './Dir1/Dir2/abcdefgh.bat', '', 'XX']])
+
+ " line breaks
+ call add(tl, [2, '\S.*\nx', ['abc', 'def', 'ghi', 'xjk', 'lmn'], ['abc', 'def', 'XXjk', 'lmn']])
+
+ " Check that \_[0-9] matching EOL does not break a following \>
+ call add(tl, [2, '\<\(\(25\_[0-5]\|2\_[0-4]\_[0-9]\|\_[01]\?\_[0-9]\_[0-9]\?\)\.\)\{3\}\(25\_[0-5]\|2\_[0-4]\_[0-9]\|\_[01]\?\_[0-9]\_[0-9]\?\)\>', ['', 'localnet/192.168.0.1', ''], ['', 'localnet/XX', '']])
+
+ " Check a pattern with a line break and ^ and $
+ call add(tl, [2, 'a\n^b$\n^c', ['a', 'b', 'c'], ['XX']])
+
+ call add(tl, [2, '\(^.\+\n\)\1', [' dog', ' dog', 'asdf'], ['XXasdf']])
+
+ " Run the multi-line tests
+ for t in tl
+ let re = t[0]
+ let pat = t[1]
+ let before = t[2]
+ let after = t[3]
+ for engine in [0, 1, 2]
+ if engine == 2 && re == 0 || engine == 1 && re ==1
+ continue
+ endif
+ let &regexpengine = engine
+ new
+ call setline(1, before)
+ exe '%s/' . pat . '/XX/'
+ let result = getline(1, '$')
+ q!
+ if result != after
+ call assert_report('Error: pat: \"' . pat . '\", text: \"'
+ \ . string(before) . '\", expected: \"' . string(after)
+ \ . '\", got: \"' . string(result) . '\"')
+ else
+ " Test passed
+ endif
+ endfor
+ endfor
+ unlet t tl
+endfunc
+
+" Check that using a pattern on two lines doesn't get messed up by using
+" matchstr() with \ze in between.
+func Test_matchstr_with_ze()
+ new
+ call append(0, ['Substitute here:', '<T="">Ta 5</Title>',
+ \ '<T="">Ac 7</Title>'])
+ call cursor(1, 1)
+ set re=0
+
+ .+1,.+2s/""/\='"' . matchstr(getline("."), '\d\+\ze<') . '"'
+ call assert_equal(['Substitute here:', '<T="5">Ta 5</Title>',
+ \ '<T="7">Ac 7</Title>', ''], getline(1, '$'))
+
+ bwipe!
+endfunc
+
+" Check a pattern with a look beind crossing a line boundary
+func Test_lookbehind_across_line()
+ new
+ call append(0, ['Behind:', 'asdfasd<yyy', 'xxstart1', 'asdfasd<yy',
+ \ 'xxxstart2', 'asdfasd<yy', 'xxstart3'])
+ call cursor(1, 1)
+ call search('\(<\_[xy]\+\)\@3<=start')
+ call assert_equal([0, 7, 3, 0], getpos('.'))
+ bwipe!
+endfunc
+
+" Check matching Visual area
+func Test_matching_visual_area()
+ new
+ call append(0, ['Visual:', 'thexe the thexethe', 'andaxand andaxand',
+ \ 'oooxofor foroxooo', 'oooxofor foroxooo'])
+ call cursor(1, 1)
+ exe "normal jfxvfx:s/\\%Ve/E/g\<CR>"
+ exe "normal jV:s/\\%Va/A/g\<CR>"
+ exe "normal jfx\<C-V>fxj:s/\\%Vo/O/g\<CR>"
+ call assert_equal(['Visual:', 'thexE thE thExethe', 'AndAxAnd AndAxAnd',
+ \ 'oooxOfOr fOrOxooo', 'oooxOfOr fOrOxooo', ''], getline(1, '$'))
+ bwipe!
+endfunc
+
+" Check matching marks
+func Test_matching_marks()
+ new
+ call append(0, ['', '', '', 'Marks:', 'asdfSasdfsadfEasdf', 'asdfSas',
+ \ 'dfsadfEasdf', '', '', '', '', ''])
+ call cursor(4, 1)
+ exe "normal jfSmsfEme:.-4,.+6s/.\\%>'s.*\\%<'e../here/\<CR>"
+ exe "normal jfSmsj0fEme:.-4,.+6s/.\\%>'s\\_.*\\%<'e../again/\<CR>"
+ call assert_equal(['', '', '', 'Marks:', 'asdfhereasdf', 'asdfagainasdf',
+ \ '', '', '', '', '', ''], getline(1, '$'))
+ bwipe!
+endfunc
+
+" Check patterns matching cursor position.
+func s:curpos_test()
+ new
+ call setline(1, ['ffooooo', 'boboooo', 'zoooooo', 'koooooo', 'moooooo',
+ \ "\t\t\tfoo", 'abababababababfoo', 'bababababababafoo', '********_',
+ \ ' xxxxxxxxxxxx xxxx xxxxxx xxxxxxx x xxxxxxxxx xx xxxxxx xxxxxx xxxxx xxxxxxx xx xxxx xxxxxxxx xxxx xxxxxxxxxxx xxx xxxxxxx xxxxxxxxx xx xxxxxx xx xxxxxxx xxxxxxxxxxxxxxxx xxxxxxxxx xxx xxxxxxxx xxxxxxxxx xxxx xxx xxxx xxx xxx xxxxx xxxxxxxxxxxx xxxx xxxxxxxxx xxxxxxxxxxx xx xxxxx xxx xxxxxxxx xxxxxx xxx xxx xxxxxxxxx xxxxxxx x xxxxxxxxx xx xxxxxx xxxxxxx xxxxxxxxxxxxxxxxxx xxxxxxx xxxxxxx xxx xxx xxxxxxxx xxxxxxx xxxx xxx xxxxxx xxxxx xxxxx xx xxxxxx xxxxxxx xxx xxxxxxxxxxxx xxxx xxxxxxxxx xxxxxx xxxxxx xxxxx xxx xxxxxxx xxxxxxxxxxxxxxxx xxxxxxxxx xxxxxxxxxx xxxx xx xxxxxxxx xxx xxxxxxxxxxx xxxxx'])
+ call setpos('.', [0, 1, 0, 0])
+ s/\%>3c.//g
+ call setpos('.', [0, 2, 4, 0])
+ s/\%#.*$//g
+ call setpos('.', [0, 3, 0, 0])
+ s/\%<3c./_/g
+ %s/\%4l\%>5c./_/g
+ %s/\%6l\%>25v./_/g
+ %s/\%>6l\%3c./!/g
+ %s/\%>7l\%12c./?/g
+ %s/\%>7l\%<9l\%>5v\%<8v./#/g
+ $s/\%(|\u.*\)\@<=[^|\t]\+$//ge
+ call assert_equal(['ffo', 'bob', '__ooooo', 'koooo__', 'moooooo',
+ \ ' f__', 'ab!babababababfoo',
+ \ 'ba!ab##abab?bafoo', '**!*****_',
+ \ ' ! xxx?xxxxxxxx xxxx xxxxxx xxxxxxx x xxxxxxxxx xx xxxxxx xxxxxx xxxxx xxxxxxx xx xxxx xxxxxxxx xxxx xxxxxxxxxxx xxx xxxxxxx xxxxxxxxx xx xxxxxx xx xxxxxxx xxxxxxxxxxxxxxxx xxxxxxxxx xxx xxxxxxxx xxxxxxxxx xxxx xxx xxxx xxx xxx xxxxx xxxxxxxxxxxx xxxx xxxxxxxxx xxxxxxxxxxx xx xxxxx xxx xxxxxxxx xxxxxx xxx xxx xxxxxxxxx xxxxxxx x xxxxxxxxx xx xxxxxx xxxxxxx xxxxxxxxxxxxxxxxxx xxxxxxx xxxxxxx xxx xxx xxxxxxxx xxxxxxx xxxx xxx xxxxxx xxxxx xxxxx xx xxxxxx xxxxxxx xxx xxxxxxxxxxxx xxxx xxxxxxxxx xxxxxx xxxxxx xxxxx xxx xxxxxxx xxxxxxxxxxxxxxxx xxxxxxxxx xxxxxxxxxx xxxx xx xxxxxxxx xxx xxxxxxxxxxx xxxxx'],
+ \ getline(1, '$'))
+ bwipe!
+endfunc
+
+func Test_matching_curpos()
+ set re=0
+ call s:curpos_test()
+ set re=1
+ call s:curpos_test()
+ set re=2
+ call s:curpos_test()
+ set re&
+endfunc
+
+" Test for matching the start and end of a buffer
+func Test_start_end_of_buffer_match()
+ new
+ call setline(1, repeat(['vim edit'], 20))
+ /\%^
+ call assert_equal([0, 1, 1, 0], getpos('.'))
+ exe "normal 50%/\\%^..\<CR>"
+ call assert_equal([0, 1, 1, 0], getpos('.'))
+ exe "normal 50%/\\%$\<CR>"
+ call assert_equal([0, 20, 8, 0], getpos('.'))
+ exe "normal 6gg/..\\%$\<CR>"
+ call assert_equal([0, 20, 7, 0], getpos('.'))
+ bwipe!
+endfunc
+
+" Check for detecting error
+func Test_regexp_error()
+ set regexpengine=2
+ call assert_fails("call matchlist('x x', ' \\ze*')", 'E888:')
+ call assert_fails("call matchlist('x x', ' \\zs*')", 'E888:')
+ set re&
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_regexp_utf8.vim b/src/nvim/testdir/test_regexp_utf8.vim
index e06c7d6368..4466ad436a 100644
--- a/src/nvim/testdir/test_regexp_utf8.vim
+++ b/src/nvim/testdir/test_regexp_utf8.vim
@@ -32,6 +32,9 @@ func Test_equivalence_re2()
endfunc
func s:classes_test()
+ if has('win32')
+ set iskeyword=@,48-57,_,192-255
+ endif
set isprint=@,161-255
call assert_equal('Motörhead', matchstr('Motörhead', '[[:print:]]\+'))
@@ -51,6 +54,12 @@ func s:classes_test()
let tabchar = ''
let upperchars = ''
let xdigitchars = ''
+ let identchars = ''
+ let identchars1 = ''
+ let kwordchars = ''
+ let kwordchars1 = ''
+ let fnamechars = ''
+ let fnamechars1 = ''
let i = 1
while i <= 255
let c = nr2char(i)
@@ -102,6 +111,24 @@ func s:classes_test()
if c =~ '[[:xdigit:]]'
let xdigitchars .= c
endif
+ if c =~ '[[:ident:]]'
+ let identchars .= c
+ endif
+ if c =~ '\i'
+ let identchars1 .= c
+ endif
+ if c =~ '[[:keyword:]]'
+ let kwordchars .= c
+ endif
+ if c =~ '\k'
+ let kwordchars1 .= c
+ endif
+ if c =~ '[[:fname:]]'
+ let fnamechars .= c
+ endif
+ if c =~ '\f'
+ let fnamechars1 .= c
+ endif
let i += 1
endwhile
@@ -121,6 +148,37 @@ func s:classes_test()
call assert_equal("\t\n\x0b\f\r ", spacechars)
call assert_equal("\t", tabchar)
call assert_equal('0123456789ABCDEFabcdef', xdigitchars)
+
+ if has('win32')
+ let identchars_ok = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz€Â‚ƒ„…†‡ˆ‰Š‹ŒÂŽ‘’“”•–—˜™š›œÂžŸ ¡¢£¤¥¦§µÀÃÂÃÄÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖØÙÚÛÜÃÞßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿ'
+ let kwordchars_ok = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyzµÀÃÂÃÄÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖרÙÚÛÜÃÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ'
+ elseif has('ebcdic')
+ let identchars_ok = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz€ŒÂŽœž¬®µº¿ÀÃÂÃÄÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖØÙÚÛÜÃÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ'
+ let kwordchars_ok = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz€ŒÂŽœž¬®µº¿ÀÃÂÃÄÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖØÙÚÛÜÃÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ'
+ else
+ let identchars_ok = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyzµÀÃÂÃÄÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖרÙÚÛÜÃÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ'
+ let kwordchars_ok = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyzµÀÃÂÃÄÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖרÙÚÛÜÃÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ'
+ endif
+
+ if has('win32')
+ let fnamechars_ok = '!#$%+,-./0123456789:=@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]_abcdefghijklmnopqrstuvwxyz{}~ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÃÂÃÄÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖרÙÚÛÜÃÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ'
+ elseif has('amiga')
+ let fnamechars_ok = '$+,-./0123456789:ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÃÂÃÄÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖרÙÚÛÜÃÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ'
+ elseif has('vms')
+ let fnamechars_ok = '#$%+,-./0123456789:;<>ABCDEFGHIJKLMNOPQRSTUVWXYZ[]_abcdefghijklmnopqrstuvwxyz~ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÃÂÃÄÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖרÙÚÛÜÃÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ'
+ elseif has('ebcdic')
+ let fnamechars_ok = '#$%+,-./=ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÃÂÃÄÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖרÙÚÛÜÃÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ'
+ else
+ let fnamechars_ok = '#$%+,-./0123456789=ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÃÂÃÄÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖרÙÚÛÜÃÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ'
+ endif
+
+ call assert_equal(identchars_ok, identchars)
+ call assert_equal(kwordchars_ok, kwordchars)
+ call assert_equal(fnamechars_ok, fnamechars)
+
+ call assert_equal(identchars1, identchars)
+ call assert_equal(kwordchars1, kwordchars)
+ call assert_equal(fnamechars1, fnamechars)
endfunc
func Test_classes_re1()
@@ -192,3 +250,287 @@ func Test_optmatch_toolong()
set re=0
endfunc
+" Test for regexp patterns with multi-byte support, using utf-8.
+func Test_multibyte_chars()
+ " tl is a List of Lists with:
+ " 2: test auto/old/new 0: test auto/old 1: test auto/new
+ " regexp pattern
+ " text to test the pattern on
+ " expected match (optional)
+ " expected submatch 1 (optional)
+ " expected submatch 2 (optional)
+ " etc.
+ " When there is no match use only the first two items.
+ let tl = []
+
+ " Multi-byte character tests. These will fail unless vim is compiled
+ " with Multibyte (FEAT_MBYTE) or BIG/HUGE features.
+ call add(tl, [2, '[[:alpha:][=a=]]\+', '879 aiaãâaiuvna ', 'aiaãâaiuvna'])
+ call add(tl, [2, '[[=a=]]\+', 'ddaãâbcd', 'aãâ']) " equivalence classes
+ call add(tl, [2, '[^ม ]\+', 'มม oijasoifjos ifjoisj f osij j มมมมม abcd', 'oijasoifjos'])
+ call add(tl, [2, ' [^ ]\+', 'start มabcdม ', ' มabcdม'])
+ call add(tl, [2, '[ม[:alpha:][=a=]]\+', '879 aiaãมâมaiuvna ', 'aiaãมâมaiuvna'])
+
+ " this is not a normal "i" but 0xec
+ call add(tl, [2, '\p\+', 'ìa', 'ìa'])
+ call add(tl, [2, '\p*', 'aã‚', 'aã‚'])
+
+ " Test recognition of some character classes
+ call add(tl, [2, '\i\+', '&*¨xx ', 'xx'])
+ call add(tl, [2, '\f\+', '&*Ÿfname ', 'fname'])
+
+ " Test composing character matching
+ call add(tl, [2, '.ม', 'xม่x yมy', 'yม'])
+ call add(tl, [2, '.ม่', 'xม่x yมy', 'xม่'])
+ call add(tl, [2, "\u05b9", " x\u05b9 ", "x\u05b9"])
+ call add(tl, [2, ".\u05b9", " x\u05b9 ", "x\u05b9"])
+ call add(tl, [2, "\u05b9\u05bb", " x\u05b9\u05bb ", "x\u05b9\u05bb"])
+ call add(tl, [2, ".\u05b9\u05bb", " x\u05b9\u05bb ", "x\u05b9\u05bb"])
+ call add(tl, [2, "\u05bb\u05b9", " x\u05b9\u05bb ", "x\u05b9\u05bb"])
+ call add(tl, [2, ".\u05bb\u05b9", " x\u05b9\u05bb ", "x\u05b9\u05bb"])
+ call add(tl, [2, "\u05b9", " y\u05bb x\u05b9 ", "x\u05b9"])
+ call add(tl, [2, ".\u05b9", " y\u05bb x\u05b9 ", "x\u05b9"])
+ call add(tl, [2, "\u05b9", " y\u05bb\u05b9 x\u05b9 ", "y\u05bb\u05b9"])
+ call add(tl, [2, ".\u05b9", " y\u05bb\u05b9 x\u05b9 ", "y\u05bb\u05b9"])
+ call add(tl, [1, "\u05b9\u05bb", " y\u05b9 x\u05b9\u05bb ", "x\u05b9\u05bb"])
+ call add(tl, [2, ".\u05b9\u05bb", " y\u05bb x\u05b9\u05bb ", "x\u05b9\u05bb"])
+ call add(tl, [2, "a", "ca\u0300t"])
+ call add(tl, [2, "ca", "ca\u0300t"])
+ call add(tl, [2, "a\u0300", "ca\u0300t", "a\u0300"])
+ call add(tl, [2, 'a\%C', "ca\u0300t", "a\u0300"])
+ call add(tl, [2, 'ca\%C', "ca\u0300t", "ca\u0300"])
+ call add(tl, [2, 'ca\%Ct', "ca\u0300t", "ca\u0300t"])
+
+ " Test \Z
+ call add(tl, [2, 'ú\Z', 'x'])
+ call add(tl, [2, 'יהוה\Z', 'יהוה', 'יהוה'])
+ call add(tl, [2, 'יְהוָה\Z', 'יהוה', 'יהוה'])
+ call add(tl, [2, 'יהוה\Z', 'יְהוָה', 'יְהוָה'])
+ call add(tl, [2, 'יְהוָה\Z', 'יְהוָה', 'יְהוָה'])
+ call add(tl, [2, 'יְ\Z', 'וְיַ', 'יַ'])
+ call add(tl, [2, "×§\u200d\u05b9x\\Z", "x×§\u200d\u05b9xy", "×§\u200d\u05b9x"])
+ call add(tl, [2, "×§\u200d\u05b9x\\Z", "x×§\u200dxy", "×§\u200dx"])
+ call add(tl, [2, "×§\u200dx\\Z", "x×§\u200d\u05b9xy", "×§\u200d\u05b9x"])
+ call add(tl, [2, "×§\u200dx\\Z", "x×§\u200dxy", "×§\u200dx"])
+ call add(tl, [2, "\u05b9\\Z", "xyz"])
+ call add(tl, [2, "\\Z\u05b9", "xyz"])
+ call add(tl, [2, "\u05b9\\Z", "xy\u05b9z", "y\u05b9"])
+ call add(tl, [2, "\\Z\u05b9", "xy\u05b9z", "y\u05b9"])
+ call add(tl, [1, "\u05b9\\+\\Z", "xy\u05b9z\u05b9 ", "y\u05b9z\u05b9"])
+ call add(tl, [1, "\\Z\u05b9\\+", "xy\u05b9z\u05b9 ", "y\u05b9z\u05b9"])
+
+ " Combining different tests and features
+ call add(tl, [2, '[^[=a=]]\+', 'ddaãâbcd', 'dd'])
+
+ " Run the tests
+ for t in tl
+ let re = t[0]
+ let pat = t[1]
+ let text = t[2]
+ let matchidx = 3
+ for engine in [0, 1, 2]
+ if engine == 2 && re == 0 || engine == 1 && re == 1
+ continue
+ endif
+ let &regexpengine = engine
+ try
+ let l = matchlist(text, pat)
+ catch
+ call assert_report('Error ' . engine . ': pat: \"' . pat .
+ \ '\", text: \"' . text .
+ \ '\", caused an exception: \"' . v:exception . '\"')
+ endtry
+ " check the match itself
+ if len(l) == 0 && len(t) > matchidx
+ call assert_report('Error ' . engine . ': pat: \"' . pat .
+ \ '\", text: \"' . text .
+ \ '\", did not match, expected: \"' . t[matchidx] . '\"')
+ elseif len(l) > 0 && len(t) == matchidx
+ call assert_report('Error ' . engine . ': pat: \"' . pat .
+ \ '\", text: \"' . text . '\", match: \"' . l[0] .
+ \ '\", expected no match')
+ elseif len(t) > matchidx && l[0] != t[matchidx]
+ call assert_report('Error ' . engine . ': pat: \"' . pat .
+ \ '\", text: \"' . text . '\", match: \"' . l[0] .
+ \ '\", expected: \"' . t[matchidx] . '\"')
+ else
+ " Test passed
+ endif
+ if len(l) > 0
+ " check all the nine submatches
+ for i in range(1, 9)
+ if len(t) <= matchidx + i
+ let e = ''
+ else
+ let e = t[matchidx + i]
+ endif
+ if l[i] != e
+ call assert_report('Error ' . engine . ': pat: \"' . pat .
+ \ '\", text: \"' . text . '\", submatch ' . i .
+ \ ': \"' . l[i] . '\", expected: \"' . e . '\"')
+ endif
+ endfor
+ unlet i
+ endif
+ endfor
+ endfor
+ set regexpengine&
+endfunc
+
+" check that 'ambiwidth' does not change the meaning of \p
+func Test_ambiwidth()
+ set regexpengine=1 ambiwidth=single
+ call assert_equal(0, match("\u00EC", '\p'))
+ set regexpengine=1 ambiwidth=double
+ call assert_equal(0, match("\u00EC", '\p'))
+ set regexpengine=2 ambiwidth=single
+ call assert_equal(0, match("\u00EC", '\p'))
+ set regexpengine=2 ambiwidth=double
+ call assert_equal(0, match("\u00EC", '\p'))
+ set regexpengine& ambiwidth&
+endfunc
+
+func Run_regexp_ignore_case()
+ call assert_equal('iIİ', substitute('iIİ', '\([iIİ]\)', '\1', 'g'))
+
+ call assert_equal('iIx', substitute('iIİ', '\c\([İ]\)', 'x', 'g'))
+ call assert_equal('xxİ', substitute('iIİ', '\(i\c\)', 'x', 'g'))
+ call assert_equal('iIx', substitute('iIİ', '\(İ\c\)', 'x', 'g'))
+ call assert_equal('iIx', substitute('iIİ', '\c\(\%u0130\)', 'x', 'g'))
+ call assert_equal('iIx', substitute('iIİ', '\c\([\u0130]\)', 'x', 'g'))
+ call assert_equal('iIx', substitute('iIİ', '\c\([\u012f-\u0131]\)', 'x', 'g'))
+endfunc
+
+func Test_regexp_ignore_case()
+ set regexpengine=1
+ call Run_regexp_ignore_case()
+ set regexpengine=2
+ call Run_regexp_ignore_case()
+ set regexpengine&
+endfunc
+
+" Tests for regexp with multi-byte encoding and various magic settings
+func Run_regexp_multibyte_magic()
+ let text =<< trim END
+ 1 a aa abb abbccc
+ 2 d dd dee deefff
+ 3 g gg ghh ghhiii
+ 4 j jj jkk jkklll
+ 5 m mm mnn mnnooo
+ 6 x ^aa$ x
+ 7 (a)(b) abbaa
+ 8 axx [ab]xx
+ 9 หม่x อมx
+ a อมx หม่x
+ b ã¡ã‚«ãƒ¨ã¯
+ c x ¬€x
+ d 天使x
+ e ü’…™¸y
+ f ü’Нz
+ g aå•·bb
+ j 0123â¤x
+ k combinations
+ l äö üᾱ̆Ì
+ END
+
+ new
+ call setline(1, text)
+ exe 'normal /a*b\{2}c\+/e' .. "\<CR>x"
+ call assert_equal('1 a aa abb abbcc', getline('.'))
+ exe 'normal /\Md\*e\{2}f\+/e' .. "\<CR>x"
+ call assert_equal('2 d dd dee deeff', getline('.'))
+ set nomagic
+ exe 'normal /g\*h\{2}i\+/e' .. "\<CR>x"
+ call assert_equal('3 g gg ghh ghhii', getline('.'))
+ exe 'normal /\mj*k\{2}l\+/e' .. "\<CR>x"
+ call assert_equal('4 j jj jkk jkkll', getline('.'))
+ exe 'normal /\vm*n{2}o+/e' .. "\<CR>x"
+ call assert_equal('5 m mm mnn mnnoo', getline('.'))
+ exe 'normal /\V^aa$/' .. "\<CR>x"
+ call assert_equal('6 x aa$ x', getline('.'))
+ set magic
+ exe 'normal /\v(a)(b)\2\1\1/e' .. "\<CR>x"
+ call assert_equal('7 (a)(b) abba', getline('.'))
+ exe 'normal /\V[ab]\(\[xy]\)\1' .. "\<CR>x"
+ call assert_equal('8 axx ab]xx', getline('.'))
+
+ " search for multi-byte without composing char
+ exe 'normal /ม' .. "\<CR>x"
+ call assert_equal('9 หม่x อx', getline('.'))
+
+ " search for multi-byte with composing char
+ exe 'normal /ม่' .. "\<CR>x"
+ call assert_equal('a อมx หx', getline('.'))
+
+ " find word by change of word class
+ exe 'normal /ã¡\<カヨ\>ã¯' .. "\<CR>x"
+ call assert_equal('b カヨã¯', getline('.'))
+
+ " Test \%u, [\u] and friends
+ " c
+ exe 'normal /\%u20ac' .. "\<CR>x"
+ call assert_equal('c x ¬x', getline('.'))
+ " d
+ exe 'normal /[\u4f7f\u5929]\+' .. "\<CR>x"
+ call assert_equal('d 使x', getline('.'))
+ " e
+ exe 'normal /\%U12345678' .. "\<CR>x"
+ call assert_equal('e y', getline('.'))
+ " f
+ exe 'normal /[\U1234abcd\u1234\uabcd]' .. "\<CR>x"
+ call assert_equal('f z', getline('.'))
+ " g
+ exe 'normal /\%d21879b' .. "\<CR>x"
+ call assert_equal('g abb', getline('.'))
+
+ " j Test backwards search from a multi-byte char
+ exe "normal /x\<CR>x?.\<CR>x"
+ call assert_equal('j 012â¤', getline('.'))
+ " k
+ let @w=':%s#comb[i]nations#œ̄ṣÌm̥̄ᾱ̆Ì#g'
+ @w
+ call assert_equal('k œ̄ṣÌm̥̄ᾱ̆Ì', getline(18))
+
+ close!
+endfunc
+
+func Test_regexp_multibyte_magic()
+ set regexpengine=1
+ call Run_regexp_multibyte_magic()
+ set regexpengine=2
+ call Run_regexp_multibyte_magic()
+ set regexpengine&
+endfunc
+
+" Test for 7.3.192
+" command ":s/ \?/ /g" splits multi-byte characters into bytes
+func Test_split_multibyte_to_bytes()
+ new
+ call setline(1, 'l äö üᾱ̆Ì')
+ s/ \?/ /g
+ call assert_equal(' l ä ö ü ᾱ̆Ì', getline(1))
+ close!
+endfunc
+
+" Test for matchstr() with multibyte characters
+func Test_matchstr_multibyte()
+ new
+ call assert_equal('ב', matchstr("×בגד", ".", 0, 2))
+ call assert_equal('בג', matchstr("×בגד", "..", 0, 2))
+ call assert_equal('×', matchstr("×בגד", ".", 0, 0))
+ call assert_equal('×’', matchstr("×בגד", ".", 4, -1))
+ close!
+endfunc
+
+" Test for 7.4.636
+" A search with end offset gets stuck at end of file.
+func Test_search_with_end_offset()
+ new
+ call setline(1, ['', 'dog(a', 'cat('])
+ exe "normal /(/e+" .. "\<CR>"
+ normal "ayn
+ call assert_equal("a\ncat(", @a)
+ close!
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_registers.vim b/src/nvim/testdir/test_registers.vim
index 298268a994..d20f8d1eef 100644
--- a/src/nvim/testdir/test_registers.vim
+++ b/src/nvim/testdir/test_registers.vim
@@ -1,3 +1,16 @@
+"
+" Tests for register operations
+"
+
+" This test must be executed first to check for empty and unset registers.
+func Test_aaa_empty_reg_test()
+ call assert_fails('normal @@', 'E748:')
+ call assert_fails('normal @%', 'E354:')
+ call assert_fails('normal @#', 'E354:')
+ call assert_fails('normal @!', 'E354:')
+ call assert_fails('normal @:', 'E30:')
+ call assert_fails('normal @.', 'E29:')
+endfunc
func Test_yank_shows_register()
enew
@@ -82,3 +95,94 @@ func Test_recording_esc_sequence()
let &t_F2 = save_F2
endif
endfunc
+
+" Test for executing the last used register (@)
+func Test_last_used_exec_reg()
+ " Test for the @: command
+ let a = ''
+ call feedkeys(":let a ..= 'Vim'\<CR>", 'xt')
+ normal @:
+ call assert_equal('VimVim', a)
+
+ " Test for the @= command
+ let x = ''
+ let a = ":let x ..= 'Vim'\<CR>"
+ exe "normal @=a\<CR>"
+ normal @@
+ call assert_equal('VimVim', x)
+
+ " Test for the @. command
+ let a = ''
+ call feedkeys("i:let a ..= 'Edit'\<CR>", 'xt')
+ normal @.
+ normal @@
+ call assert_equal('EditEdit', a)
+
+ enew!
+endfunc
+
+func Test_get_register()
+ enew
+ edit Xfile1
+ edit Xfile2
+ call assert_equal('Xfile2', getreg('%'))
+ call assert_equal('Xfile1', getreg('#'))
+
+ call feedkeys("iTwo\<Esc>", 'xt')
+ call assert_equal('Two', getreg('.'))
+ call assert_equal('', getreg('_'))
+ call assert_beeps('normal ":yy')
+ call assert_beeps('normal "%yy')
+ call assert_beeps('normal ".yy')
+
+ call assert_equal('', getreg("\<C-F>"))
+ call assert_equal('', getreg("\<C-W>"))
+ call assert_equal('', getreg("\<C-L>"))
+
+ call assert_equal('', getregtype('!'))
+
+ enew!
+endfunc
+
+func Test_set_register()
+ call assert_fails("call setreg('#', 200)", 'E86:')
+
+ edit Xfile_alt_1
+ let b1 = bufnr('')
+ edit Xfile_alt_2
+ let b2 = bufnr('')
+ edit Xfile_alt_3
+ let b3 = bufnr('')
+ call setreg('#', 'alt_1')
+ call assert_equal('Xfile_alt_1', getreg('#'))
+ call setreg('#', b2)
+ call assert_equal('Xfile_alt_2', getreg('#'))
+
+ let ab = 'regwrite'
+ call setreg('=', '')
+ call setreg('=', 'a', 'a')
+ call setreg('=', 'b', 'a')
+ call assert_equal('regwrite', getreg('='))
+
+ enew!
+endfunc
+
+func Test_ve_blockpaste()
+ new
+ set ve=all
+ 0put =['QWERTZ','ASDFGH']
+ call cursor(1,1)
+ exe ":norm! \<C-V>3ljdP"
+ call assert_equal(1, col('.'))
+ call assert_equal(getline(1, 2), ['QWERTZ', 'ASDFGH'])
+ call cursor(1,1)
+ exe ":norm! \<C-V>3ljd"
+ call cursor(1,1)
+ norm! $3lP
+ call assert_equal(5, col('.'))
+ call assert_equal(getline(1, 2), ['TZ QWER', 'GH ASDF'])
+ set ve&vim
+ bwipe!
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_restricted.vim b/src/nvim/testdir/test_restricted.vim
new file mode 100644
index 0000000000..a29f7c33d3
--- /dev/null
+++ b/src/nvim/testdir/test_restricted.vim
@@ -0,0 +1,103 @@
+" Test for "rvim" or "vim -Z"
+
+source shared.vim
+
+"if has('win32') && has('gui')
+" " Win32 GUI shows a dialog instead of displaying the error in the last line.
+" finish
+"endif
+
+func Test_restricted()
+ call Run_restricted_test('!ls', 'E145:')
+endfunc
+
+func Run_restricted_test(ex_cmd, error)
+ let cmd = GetVimCommand('Xrestricted')
+ if cmd == ''
+ return
+ endif
+
+ " Use a VimEnter autocommand to avoid that the error message is displayed in
+ " a dialog with an OK button.
+ call writefile([
+ \ "func Init()",
+ \ " silent! " . a:ex_cmd,
+ \ " call writefile([v:errmsg], 'Xrestrout')",
+ \ " qa!",
+ \ "endfunc",
+ \ "au VimEnter * call Init()",
+ \ ], 'Xrestricted')
+ call system(cmd . ' -Z')
+ call assert_match(a:error, join(readfile('Xrestrout')))
+
+ call delete('Xrestricted')
+ call delete('Xrestrout')
+endfunc
+
+func Test_restricted_lua()
+ if !has('lua')
+ throw 'Skipped: Lua is not supported'
+ endif
+ call Run_restricted_test('lua print("Hello, Vim!")', 'E981:')
+ call Run_restricted_test('luado return "hello"', 'E981:')
+ call Run_restricted_test('luafile somefile', 'E981:')
+ call Run_restricted_test('call luaeval("expression")', 'E145:')
+endfunc
+
+func Test_restricted_mzscheme()
+ if !has('mzscheme')
+ throw 'Skipped: MzScheme is not supported'
+ endif
+ call Run_restricted_test('mzscheme statement', 'E981:')
+ call Run_restricted_test('mzfile somefile', 'E981:')
+ call Run_restricted_test('call mzeval("expression")', 'E145:')
+endfunc
+
+func Test_restricted_perl()
+ if !has('perl')
+ throw 'Skipped: Perl is not supported'
+ endif
+ " TODO: how to make Safe mode fail?
+ " call Run_restricted_test('perl system("ls")', 'E981:')
+ " call Run_restricted_test('perldo system("hello")', 'E981:')
+ " call Run_restricted_test('perlfile somefile', 'E981:')
+ " call Run_restricted_test('call perleval("system(\"ls\")")', 'E145:')
+endfunc
+
+func Test_restricted_python()
+ if !has('python')
+ throw 'Skipped: Python is not supported'
+ endif
+ call Run_restricted_test('python print "hello"', 'E981:')
+ call Run_restricted_test('pydo return "hello"', 'E981:')
+ call Run_restricted_test('pyfile somefile', 'E981:')
+ call Run_restricted_test('call pyeval("expression")', 'E145:')
+endfunc
+
+func Test_restricted_python3()
+ if !has('python3')
+ throw 'Skipped: Python3 is not supported'
+ endif
+ call Run_restricted_test('py3 print "hello"', 'E981:')
+ call Run_restricted_test('py3do return "hello"', 'E981:')
+ call Run_restricted_test('py3file somefile', 'E981:')
+ call Run_restricted_test('call py3eval("expression")', 'E145:')
+endfunc
+
+func Test_restricted_ruby()
+ if !has('ruby')
+ throw 'Skipped: Ruby is not supported'
+ endif
+ call Run_restricted_test('ruby print "Hello"', 'E981:')
+ call Run_restricted_test('rubydo print "Hello"', 'E981:')
+ call Run_restricted_test('rubyfile somefile', 'E981:')
+endfunc
+
+func Test_restricted_tcl()
+ if !has('tcl')
+ throw 'Skipped: Tcl is not supported'
+ endif
+ call Run_restricted_test('tcl puts "Hello"', 'E981:')
+ call Run_restricted_test('tcldo puts "Hello"', 'E981:')
+ call Run_restricted_test('tclfile somefile', 'E981:')
+endfunc
diff --git a/src/nvim/testdir/test_search.vim b/src/nvim/testdir/test_search.vim
index 5d4c2a015f..8036dea29f 100644
--- a/src/nvim/testdir/test_search.vim
+++ b/src/nvim/testdir/test_search.vim
@@ -1,6 +1,7 @@
" Test for the search command
source shared.vim
+source screendump.vim
func Test_search_cmdline()
" See test/functional/legacy/search_spec.lua
@@ -57,7 +58,7 @@ func Test_search_cmdline()
call feedkeys("/the".repeat("\<C-G>", 6)."\<cr>", 'tx')
call assert_equal(' 8 them', getline('.'))
:1
- " eigth match
+ " eighth match
call feedkeys("/the".repeat("\<C-G>", 7)."\<cr>", 'tx')
call assert_equal(' 9 these', getline('.'))
:1
@@ -99,7 +100,7 @@ func Test_search_cmdline()
call feedkeys("/the".repeat("\<C-G>", 6)."\<cr>", 'tx')
call assert_equal(' 8 them', getline('.'))
:1
- " eigth match
+ " eighth match
call feedkeys("/the".repeat("\<C-G>", 7)."\<cr>", 'tx')
call assert_equal(' 9 these', getline('.'))
:1
@@ -549,6 +550,36 @@ func Test_incsearch_with_change()
call delete('Xis_change_script')
endfunc
+func Test_incsearch_scrolling()
+ if !CanRunVimInTerminal()
+ return
+ endif
+ call assert_equal(0, &scrolloff)
+ call writefile([
+ \ 'let dots = repeat(".", 120)',
+ \ 'set incsearch cmdheight=2 scrolloff=0',
+ \ 'call setline(1, [dots, dots, dots, "", "target", dots, dots])',
+ \ 'normal gg',
+ \ 'redraw',
+ \ ], 'Xscript')
+ let buf = RunVimInTerminal('-S Xscript', {'rows': 9, 'cols': 70})
+ " Need to send one key at a time to force a redraw
+ call term_sendkeys(buf, '/')
+ sleep 100m
+ call term_sendkeys(buf, 't')
+ sleep 100m
+ call term_sendkeys(buf, 'a')
+ sleep 100m
+ call term_sendkeys(buf, 'r')
+ sleep 100m
+ call term_sendkeys(buf, 'g')
+ call VerifyScreenDump(buf, 'Test_incsearch_scrolling_01', {})
+
+ call term_sendkeys(buf, "\<Esc>")
+ call StopVimInTerminal(buf)
+ call delete('Xscript')
+endfunc
+
func Test_search_undefined_behaviour()
if !has("terminal")
return
@@ -667,3 +698,9 @@ func Test_search_display_pattern()
set norl
endif
endfunc
+
+func Test_search_special()
+ " this was causing illegal memory access and an endless loop
+ set t_PE=
+ exe "norm /\x80PS"
+endfunc
diff --git a/src/nvim/testdir/test_signs.vim b/src/nvim/testdir/test_signs.vim
index ef4b227215..8b1927e4f0 100644
--- a/src/nvim/testdir/test_signs.vim
+++ b/src/nvim/testdir/test_signs.vim
@@ -4,6 +4,8 @@ if !has('signs')
finish
endif
+source screendump.vim
+
func Test_sign()
new
call setline(1, ['a', 'b', 'c', 'd'])
@@ -210,13 +212,16 @@ func Test_sign_completion()
call assert_equal('"sign define Sign linehl=SpellBad SpellCap ' .
\ 'SpellLocal SpellRare', @:)
- call writefile(['foo'], 'XsignOne')
- call writefile(['bar'], 'XsignTwo')
+ call feedkeys(":sign define Sign texthl=Spell\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"sign define Sign texthl=SpellBad SpellCap ' .
+ \ 'SpellLocal SpellRare', @:)
+
+ call writefile(repeat(["Sun is shining"], 30), "XsignOne")
+ call writefile(repeat(["Sky is blue"], 30), "XsignTwo")
call feedkeys(":sign define Sign icon=Xsig\<C-A>\<C-B>\"\<CR>", 'tx')
call assert_equal('"sign define Sign icon=XsignOne XsignTwo', @:)
- call delete('XsignOne')
- call delete('XsignTwo')
+ " Test for completion of arguments to ':sign undefine'
call feedkeys(":sign undefine \<C-A>\<C-B>\"\<CR>", 'tx')
call assert_equal('"sign undefine Sign1 Sign2', @:)
@@ -227,17 +232,70 @@ func Test_sign_completion()
call feedkeys(":sign place 1 name=\<C-A>\<C-B>\"\<CR>", 'tx')
call assert_equal('"sign place 1 name=Sign1 Sign2', @:)
+ edit XsignOne
+ sign place 1 name=Sign1 line=5
+ sign place 1 name=Sign1 group=g1 line=10
+ edit XsignTwo
+ sign place 1 name=Sign2 group=g2 line=15
+
+ " Test for completion of group= and file= arguments to ':sign place'
+ call feedkeys(":sign place 1 name=Sign1 file=Xsign\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"sign place 1 name=Sign1 file=XsignOne XsignTwo', @:)
+ call feedkeys(":sign place 1 name=Sign1 group=\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"sign place 1 name=Sign1 group=g1 g2', @:)
+
+ " Test for completion of arguments to 'sign place' without sign identifier
+ call feedkeys(":sign place \<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"sign place buffer= file= group=', @:)
+ call feedkeys(":sign place file=Xsign\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"sign place file=XsignOne XsignTwo', @:)
+ call feedkeys(":sign place group=\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"sign place group=g1 g2', @:)
+ call feedkeys(":sign place group=g1 file=\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"sign place group=g1 file=XsignOne XsignTwo', @:)
+
+ " Test for completion of arguments to ':sign unplace'
call feedkeys(":sign unplace 1 \<C-A>\<C-B>\"\<CR>", 'tx')
call assert_equal('"sign unplace 1 buffer= file= group=', @:)
-
+ call feedkeys(":sign unplace 1 file=Xsign\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"sign unplace 1 file=XsignOne XsignTwo', @:)
+ call feedkeys(":sign unplace 1 group=\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"sign unplace 1 group=g1 g2', @:)
+ call feedkeys(":sign unplace 1 group=g2 file=Xsign\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"sign unplace 1 group=g2 file=XsignOne XsignTwo', @:)
+
+ " Test for completion of arguments to ':sign list'
call feedkeys(":sign list \<C-A>\<C-B>\"\<CR>", 'tx')
call assert_equal('"sign list Sign1 Sign2', @:)
+ " Test for completion of arguments to ':sign jump'
call feedkeys(":sign jump 1 \<C-A>\<C-B>\"\<CR>", 'tx')
call assert_equal('"sign jump 1 buffer= file= group=', @:)
+ call feedkeys(":sign jump 1 file=Xsign\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"sign jump 1 file=XsignOne XsignTwo', @:)
+ call feedkeys(":sign jump 1 group=\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"sign jump 1 group=g1 g2', @:)
+
+ " Error cases
+ call feedkeys(":sign here\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"sign here', @:)
+ call feedkeys(":sign define Sign here=\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"sign define Sign here=\<C-A>", @:)
+ call feedkeys(":sign place 1 here=\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"sign place 1 here=\<C-A>", @:)
+ call feedkeys(":sign jump 1 here=\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"sign jump 1 here=\<C-A>", @:)
+ call feedkeys(":sign here there\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"sign here there\<C-A>", @:)
+ call feedkeys(":sign here there=\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"sign here there=\<C-A>", @:)
+ sign unplace * group=*
sign undefine Sign1
sign undefine Sign2
+ enew
+ call delete('XsignOne')
+ call delete('XsignTwo')
endfunc
func Test_sign_invalid_commands()
@@ -1127,6 +1185,319 @@ func Test_sign_priority()
\ 'priority' : 10}],
\ s[0].signs)
+ call sign_unplace('*')
+
+ " Three signs on different lines with changing priorities
+ call sign_place(1, '', 'sign1', 'Xsign',
+ \ {'lnum' : 11, 'priority' : 50})
+ call sign_place(2, '', 'sign2', 'Xsign',
+ \ {'lnum' : 12, 'priority' : 60})
+ call sign_place(3, '', 'sign3', 'Xsign',
+ \ {'lnum' : 13, 'priority' : 70})
+ call sign_place(2, '', 'sign2', 'Xsign',
+ \ {'lnum' : 12, 'priority' : 40})
+ call sign_place(3, '', 'sign3', 'Xsign',
+ \ {'lnum' : 13, 'priority' : 30})
+ call sign_place(1, '', 'sign1', 'Xsign',
+ \ {'lnum' : 11, 'priority' : 50})
+ let s = sign_getplaced('Xsign', {'group' : '*'})
+ call assert_equal([
+ \ {'id' : 1, 'name' : 'sign1', 'lnum' : 11, 'group' : '',
+ \ 'priority' : 50},
+ \ {'id' : 2, 'name' : 'sign2', 'lnum' : 12, 'group' : '',
+ \ 'priority' : 40},
+ \ {'id' : 3, 'name' : 'sign3', 'lnum' : 13, 'group' : '',
+ \ 'priority' : 30}],
+ \ s[0].signs)
+
+ call sign_unplace('*')
+
+ " Two signs on the same line with changing priorities
+ call sign_place(1, '', 'sign1', 'Xsign',
+ \ {'lnum' : 4, 'priority' : 20})
+ call sign_place(2, '', 'sign2', 'Xsign',
+ \ {'lnum' : 4, 'priority' : 30})
+ let s = sign_getplaced('Xsign', {'group' : '*'})
+ call assert_equal([
+ \ {'id' : 2, 'name' : 'sign2', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 30},
+ \ {'id' : 1, 'name' : 'sign1', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 20}],
+ \ s[0].signs)
+ " Change the priority of the last sign to highest
+ call sign_place(1, '', 'sign1', 'Xsign',
+ \ {'lnum' : 4, 'priority' : 40})
+ let s = sign_getplaced('Xsign', {'group' : '*'})
+ call assert_equal([
+ \ {'id' : 1, 'name' : 'sign1', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 40},
+ \ {'id' : 2, 'name' : 'sign2', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 30}],
+ \ s[0].signs)
+ " Change the priority of the first sign to lowest
+ call sign_place(1, '', 'sign1', 'Xsign',
+ \ {'lnum' : 4, 'priority' : 25})
+ let s = sign_getplaced('Xsign', {'group' : '*'})
+ call assert_equal([
+ \ {'id' : 2, 'name' : 'sign2', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 30},
+ \ {'id' : 1, 'name' : 'sign1', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 25}],
+ \ s[0].signs)
+ call sign_place(1, '', 'sign1', 'Xsign',
+ \ {'lnum' : 4, 'priority' : 45})
+ call sign_place(2, '', 'sign2', 'Xsign',
+ \ {'lnum' : 4, 'priority' : 55})
+ let s = sign_getplaced('Xsign', {'group' : '*'})
+ call assert_equal([
+ \ {'id' : 2, 'name' : 'sign2', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 55},
+ \ {'id' : 1, 'name' : 'sign1', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 45}],
+ \ s[0].signs)
+
+ call sign_unplace('*')
+
+ " Three signs on the same line with changing priorities
+ call sign_place(1, '', 'sign1', 'Xsign',
+ \ {'lnum' : 4, 'priority' : 40})
+ call sign_place(2, '', 'sign2', 'Xsign',
+ \ {'lnum' : 4, 'priority' : 30})
+ call sign_place(3, '', 'sign3', 'Xsign',
+ \ {'lnum' : 4, 'priority' : 20})
+ let s = sign_getplaced('Xsign', {'group' : '*'})
+ call assert_equal([
+ \ {'id' : 1, 'name' : 'sign1', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 40},
+ \ {'id' : 2, 'name' : 'sign2', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 30},
+ \ {'id' : 3, 'name' : 'sign3', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 20}],
+ \ s[0].signs)
+
+ " Change the priority of the middle sign to the highest
+ call sign_place(2, '', 'sign2', 'Xsign',
+ \ {'lnum' : 4, 'priority' : 50})
+ let s = sign_getplaced('Xsign', {'group' : '*'})
+ call assert_equal([
+ \ {'id' : 2, 'name' : 'sign2', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 50},
+ \ {'id' : 1, 'name' : 'sign1', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 40},
+ \ {'id' : 3, 'name' : 'sign3', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 20}],
+ \ s[0].signs)
+
+ " Change the priority of the middle sign to the lowest
+ call sign_place(1, '', 'sign1', 'Xsign',
+ \ {'lnum' : 4, 'priority' : 15})
+ let s = sign_getplaced('Xsign', {'group' : '*'})
+ call assert_equal([
+ \ {'id' : 2, 'name' : 'sign2', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 50},
+ \ {'id' : 3, 'name' : 'sign3', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 20},
+ \ {'id' : 1, 'name' : 'sign1', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 15}],
+ \ s[0].signs)
+
+ " Change the priority of the last sign to the highest
+ call sign_place(1, '', 'sign1', 'Xsign',
+ \ {'lnum' : 4, 'priority' : 55})
+ let s = sign_getplaced('Xsign', {'group' : '*'})
+ call assert_equal([
+ \ {'id' : 1, 'name' : 'sign1', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 55},
+ \ {'id' : 2, 'name' : 'sign2', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 50},
+ \ {'id' : 3, 'name' : 'sign3', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 20}],
+ \ s[0].signs)
+
+ " Change the priority of the first sign to the lowest
+ call sign_place(1, '', 'sign1', 'Xsign',
+ \ {'lnum' : 4, 'priority' : 15})
+ let s = sign_getplaced('Xsign', {'group' : '*'})
+ call assert_equal([
+ \ {'id' : 2, 'name' : 'sign2', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 50},
+ \ {'id' : 3, 'name' : 'sign3', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 20},
+ \ {'id' : 1, 'name' : 'sign1', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 15}],
+ \ s[0].signs)
+
+ call sign_unplace('*')
+
+ " Three signs on the same line with changing priorities along with other
+ " signs
+ call sign_place(1, '', 'sign1', 'Xsign',
+ \ {'lnum' : 2, 'priority' : 10})
+ call sign_place(2, '', 'sign1', 'Xsign',
+ \ {'lnum' : 4, 'priority' : 30})
+ call sign_place(3, '', 'sign2', 'Xsign',
+ \ {'lnum' : 4, 'priority' : 20})
+ call sign_place(4, '', 'sign3', 'Xsign',
+ \ {'lnum' : 4, 'priority' : 25})
+ call sign_place(5, '', 'sign2', 'Xsign',
+ \ {'lnum' : 6, 'priority' : 80})
+ let s = sign_getplaced('Xsign', {'group' : '*'})
+ call assert_equal([
+ \ {'id' : 1, 'name' : 'sign1', 'lnum' : 2, 'group' : '',
+ \ 'priority' : 10},
+ \ {'id' : 2, 'name' : 'sign1', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 30},
+ \ {'id' : 4, 'name' : 'sign3', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 25},
+ \ {'id' : 3, 'name' : 'sign2', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 20},
+ \ {'id' : 5, 'name' : 'sign2', 'lnum' : 6, 'group' : '',
+ \ 'priority' : 80}],
+ \ s[0].signs)
+
+ " Change the priority of the first sign to lowest
+ call sign_place(2, '', 'sign1', 'Xsign',
+ \ {'lnum' : 4, 'priority' : 15})
+ let s = sign_getplaced('Xsign', {'group' : '*'})
+ call assert_equal([
+ \ {'id' : 1, 'name' : 'sign1', 'lnum' : 2, 'group' : '',
+ \ 'priority' : 10},
+ \ {'id' : 4, 'name' : 'sign3', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 25},
+ \ {'id' : 3, 'name' : 'sign2', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 20},
+ \ {'id' : 2, 'name' : 'sign1', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 15},
+ \ {'id' : 5, 'name' : 'sign2', 'lnum' : 6, 'group' : '',
+ \ 'priority' : 80}],
+ \ s[0].signs)
+
+ " Change the priority of the last sign to highest
+ call sign_place(2, '', 'sign1', 'Xsign',
+ \ {'lnum' : 4, 'priority' : 30})
+ let s = sign_getplaced('Xsign', {'group' : '*'})
+ call assert_equal([
+ \ {'id' : 1, 'name' : 'sign1', 'lnum' : 2, 'group' : '',
+ \ 'priority' : 10},
+ \ {'id' : 2, 'name' : 'sign1', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 30},
+ \ {'id' : 4, 'name' : 'sign3', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 25},
+ \ {'id' : 3, 'name' : 'sign2', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 20},
+ \ {'id' : 5, 'name' : 'sign2', 'lnum' : 6, 'group' : '',
+ \ 'priority' : 80}],
+ \ s[0].signs)
+
+ " Change the priority of the middle sign to lowest
+ call sign_place(4, '', 'sign3', 'Xsign',
+ \ {'lnum' : 4, 'priority' : 15})
+ let s = sign_getplaced('Xsign', {'group' : '*'})
+ call assert_equal([
+ \ {'id' : 1, 'name' : 'sign1', 'lnum' : 2, 'group' : '',
+ \ 'priority' : 10},
+ \ {'id' : 2, 'name' : 'sign1', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 30},
+ \ {'id' : 3, 'name' : 'sign2', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 20},
+ \ {'id' : 4, 'name' : 'sign3', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 15},
+ \ {'id' : 5, 'name' : 'sign2', 'lnum' : 6, 'group' : '',
+ \ 'priority' : 80}],
+ \ s[0].signs)
+
+ " Change the priority of the middle sign to highest
+ call sign_place(3, '', 'sign2', 'Xsign',
+ \ {'lnum' : 4, 'priority' : 35})
+ let s = sign_getplaced('Xsign', {'group' : '*'})
+ call assert_equal([
+ \ {'id' : 1, 'name' : 'sign1', 'lnum' : 2, 'group' : '',
+ \ 'priority' : 10},
+ \ {'id' : 3, 'name' : 'sign2', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 35},
+ \ {'id' : 2, 'name' : 'sign1', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 30},
+ \ {'id' : 4, 'name' : 'sign3', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 15},
+ \ {'id' : 5, 'name' : 'sign2', 'lnum' : 6, 'group' : '',
+ \ 'priority' : 80}],
+ \ s[0].signs)
+
+ call sign_unplace('*')
+
+ " Multiple signs with the same priority on the same line
+ call sign_place(1, '', 'sign1', 'Xsign',
+ \ {'lnum' : 4, 'priority' : 20})
+ call sign_place(2, '', 'sign2', 'Xsign',
+ \ {'lnum' : 4, 'priority' : 20})
+ call sign_place(3, '', 'sign3', 'Xsign',
+ \ {'lnum' : 4, 'priority' : 20})
+ let s = sign_getplaced('Xsign', {'group' : '*'})
+ call assert_equal([
+ \ {'id' : 3, 'name' : 'sign3', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 20},
+ \ {'id' : 2, 'name' : 'sign2', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 20},
+ \ {'id' : 1, 'name' : 'sign1', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 20}],
+ \ s[0].signs)
+ " Place the last sign again with the same priority
+ call sign_place(1, '', 'sign1', 'Xsign',
+ \ {'lnum' : 4, 'priority' : 20})
+ let s = sign_getplaced('Xsign', {'group' : '*'})
+ call assert_equal([
+ \ {'id' : 1, 'name' : 'sign1', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 20},
+ \ {'id' : 3, 'name' : 'sign3', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 20},
+ \ {'id' : 2, 'name' : 'sign2', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 20}],
+ \ s[0].signs)
+ " Place the first sign again with the same priority
+ call sign_place(1, '', 'sign1', 'Xsign',
+ \ {'lnum' : 4, 'priority' : 20})
+ let s = sign_getplaced('Xsign', {'group' : '*'})
+ call assert_equal([
+ \ {'id' : 1, 'name' : 'sign1', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 20},
+ \ {'id' : 3, 'name' : 'sign3', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 20},
+ \ {'id' : 2, 'name' : 'sign2', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 20}],
+ \ s[0].signs)
+ " Place the middle sign again with the same priority
+ call sign_place(3, '', 'sign3', 'Xsign',
+ \ {'lnum' : 4, 'priority' : 20})
+ let s = sign_getplaced('Xsign', {'group' : '*'})
+ call assert_equal([
+ \ {'id' : 3, 'name' : 'sign3', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 20},
+ \ {'id' : 1, 'name' : 'sign1', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 20},
+ \ {'id' : 2, 'name' : 'sign2', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 20}],
+ \ s[0].signs)
+
+ call sign_unplace('*')
+
+ " Place multiple signs with same id on a line with different priority
+ call sign_place(1, '', 'sign1', 'Xsign',
+ \ {'lnum' : 5, 'priority' : 20})
+ call sign_place(1, '', 'sign2', 'Xsign',
+ \ {'lnum' : 5, 'priority' : 10})
+ let s = sign_getplaced('Xsign', {'lnum' : 5})
+ call assert_equal([
+ \ {'id' : 1, 'name' : 'sign2', 'lnum' : 5, 'group' : '',
+ \ 'priority' : 10}],
+ \ s[0].signs)
+ call sign_place(1, '', 'sign2', 'Xsign',
+ \ {'lnum' : 5, 'priority' : 5})
+ let s = sign_getplaced('Xsign', {'lnum' : 5})
+ call assert_equal([
+ \ {'id' : 1, 'name' : 'sign2', 'lnum' : 5, 'group' : '',
+ \ 'priority' : 5}],
+ \ s[0].signs)
+
" Error case
call assert_fails("call sign_place(1, 'g1', 'sign1', 'Xsign',
\ [])", 'E715:')
@@ -1339,3 +1710,35 @@ func Test_sign_jump_func()
sign undefine sign1
enew! | only!
endfunc
+
+" Test for correct cursor position after the sign column appears or disappears.
+func Test_sign_cursor_position()
+ if !CanRunVimInTerminal()
+ throw 'Skipped: cannot make screendumps'
+ endif
+
+ let lines =<< trim END
+ call setline(1, [repeat('x', 75), 'mmmm', 'yyyy'])
+ call cursor(2,1)
+ sign define s1 texthl=Search text==>
+ redraw
+ sign place 10 line=2 name=s1
+ END
+ call writefile(lines, 'XtestSigncolumn')
+ let buf = RunVimInTerminal('-S XtestSigncolumn', {'rows': 6})
+ call VerifyScreenDump(buf, 'Test_sign_cursor_1', {})
+
+ " Change the sign text
+ call term_sendkeys(buf, ":sign define s1 text=-)\<CR>")
+ call VerifyScreenDump(buf, 'Test_sign_cursor_2', {})
+
+ " update cursor position calculation
+ call term_sendkeys(buf, "lh")
+ call term_sendkeys(buf, ":sign unplace 10\<CR>")
+ call VerifyScreenDump(buf, 'Test_sign_cursor_3', {})
+
+
+ " clean up
+ call StopVimInTerminal(buf)
+ call delete('XtestSigncolumn')
+endfunc
diff --git a/src/nvim/testdir/test_spell.vim b/src/nvim/testdir/test_spell.vim
index e49b5542fa..414c7278eb 100644
--- a/src/nvim/testdir/test_spell.vim
+++ b/src/nvim/testdir/test_spell.vim
@@ -1,10 +1,13 @@
" Test spell checking
" Note: this file uses latin1 encoding, but is used with utf-8 encoding.
+source check.vim
if !has('spell')
finish
endif
+source screendump.vim
+
func TearDown()
set nospell
call delete('Xtest.aff')
@@ -130,20 +133,21 @@ endfunc
func Test_spellinfo()
throw 'skipped: Nvim does not support enc=latin1'
new
+ let runtime = substitute($VIMRUNTIME, '\\', '/', 'g')
set enc=latin1 spell spelllang=en
- call assert_match("^\nfile: .*/runtime/spell/en.latin1.spl\n$", execute('spellinfo'))
+ call assert_match("^\nfile: " .. runtime .. "/spell/en.latin1.spl\n$", execute('spellinfo'))
set enc=cp1250 spell spelllang=en
- call assert_match("^\nfile: .*/runtime/spell/en.ascii.spl\n$", execute('spellinfo'))
+ call assert_match("^\nfile: " .. runtime .. "/spell/en.ascii.spl\n$", execute('spellinfo'))
set enc=utf-8 spell spelllang=en
- call assert_match("^\nfile: .*/runtime/spell/en.utf-8.spl\n$", execute('spellinfo'))
+ call assert_match("^\nfile: " .. runtime .. "/spell/en.utf-8.spl\n$", execute('spellinfo'))
set enc=latin1 spell spelllang=en_us,en_nz
call assert_match("^\n" .
- \ "file: .*/runtime/spell/en.latin1.spl\n" .
- \ "file: .*/runtime/spell/en.latin1.spl\n$", execute('spellinfo'))
+ \ "file: " .. runtime .. "/spell/en.latin1.spl\n" .
+ \ "file: " .. runtime .. "/spell/en.latin1.spl\n$", execute('spellinfo'))
set spell spelllang=
call assert_fails('spellinfo', 'E756:')
@@ -151,6 +155,12 @@ func Test_spellinfo()
set nospell spelllang=en
call assert_fails('spellinfo', 'E756:')
+ call assert_fails('set spelllang=foo/bar', 'E474:')
+ call assert_fails('set spelllang=foo\ bar', 'E474:')
+ call assert_fails("set spelllang=foo\\\nbar", 'E474:')
+ call assert_fails("set spelllang=foo\\\rbar", 'E474:')
+ call assert_fails("set spelllang=foo+bar", 'E474:')
+
set enc& spell& spelllang&
bwipe
endfunc
@@ -283,9 +293,9 @@ func Test_zz_affix()
\ ])
call LoadAffAndDic(g:test_data_aff7, g:test_data_dic7)
- call RunGoodBad("meea1 meea\xE9 bar prebar barmeat prebarmeat leadprebar lead tail leadtail leadmiddletail",
+ call RunGoodBad("meea1 meezero meea\xE9 bar prebar barmeat prebarmeat leadprebar lead tail leadtail leadmiddletail",
\ "bad: mee meea2 prabar probarmaat middle leadmiddle middletail taillead leadprobar",
- \ ["bar", "barmeat", "lead", "meea1", "meea\xE9", "prebar", "prebarmeat", "tail"],
+ \ ["bar", "barmeat", "lead", "meea1", "meea\xE9", "meezero", "prebar", "prebarmeat", "tail"],
\ [
\ ["bad", ["bar", "lead", "tail"]],
\ ["mee", ["meea1", "meea\xE9", "bar"]],
@@ -320,6 +330,19 @@ func Test_zz_Numbers()
\ ])
endfunc
+" Affix flags
+func Test_zz_affix_flags()
+ call LoadAffAndDic(g:test_data_aff10, g:test_data_dic10)
+ call RunGoodBad("drink drinkable drinkables drinktable drinkabletable",
+ \ "bad: drinks drinkstable drinkablestable",
+ \ ["drink", "drinkable", "drinkables", "table"],
+ \ [['bad', []],
+ \ ['drinks', ['drink']],
+ \ ['drinkstable', ['drinktable', 'drinkable', 'drink table']],
+ \ ['drinkablestable', ['drinkabletable', 'drinkables table', 'drinkable table']],
+ \ ])
+endfunc
+
function FirstSpellWord()
call feedkeys("/^start:\n", 'tx')
normal ]smm
@@ -373,6 +396,11 @@ func Test_zz_sal_and_addition()
call assert_equal("elekwint", SecondSpellWord())
endfunc
+func Test_spellfile_value()
+ set spellfile=Xdir/Xtest.latin1.add
+ set spellfile=Xdir/Xtest.utf-8.add,Xtest_other.add
+endfunc
+
func Test_region_error()
messages clear
call writefile(["/regions=usgbnz", "elequint/0"], "Xtest.latin1.add")
@@ -452,6 +480,44 @@ func RunGoodBad(good, bad, expected_words, expected_bad_words)
bwipe!
endfunc
+func Test_spell_screendump()
+ CheckScreendump
+
+ let lines =<< trim END
+ 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
+ END
+ call writefile(lines, 'XtestSpell')
+ let buf = RunVimInTerminal('-S XtestSpell', {'rows': 8})
+ call VerifyScreenDump(buf, 'Test_spell_1', {})
+
+ let lines =<< trim END
+ 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
+ END
+ call writefile(lines, 'XtestSpell')
+ let buf = RunVimInTerminal('-S XtestSpell', {'rows': 8})
+ call VerifyScreenDump(buf, 'Test_spell_1', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+ call delete('XtestSpell')
+endfunc
+
let g:test_data_aff1 = [
\"SET ISO8859-1",
\"TRY esianrtolcdugmphbyfvkwjkqxz-\xEB\xE9\xE8\xEA\xEF\xEE\xE4\xE0\xE2\xF6\xFC\xFB'ESIANRTOLCDUGMPHBYFVKWJKQXZ",
@@ -713,6 +779,9 @@ let g:test_data_aff7 = [
\"SFX 61003 Y 1",
\"SFX 61003 0 meat .",
\"",
+ \"SFX 0 Y 1",
+ \"SFX 0 0 zero .",
+ \"",
\"SFX 391 Y 1",
\"SFX 391 0 a1 .",
\"",
@@ -724,7 +793,7 @@ let g:test_data_aff7 = [
\ ]
let g:test_data_dic7 = [
\"1234",
- \"mee/391,111,9999",
+ \"mee/0,391,111,9999",
\"bar/17,61003,123",
\"lead/2",
\"tail/123",
@@ -748,6 +817,21 @@ let g:test_data_dic9 = [
\"foo",
\"bar",
\ ]
+let g:test_data_aff10 = [
+ \"COMPOUNDRULE se",
+ \"COMPOUNDPERMITFLAG p",
+ \"",
+ \"SFX A Y 1",
+ \"SFX A 0 able/Mp .",
+ \"",
+ \"SFX M Y 1",
+ \"SFX M 0 s .",
+ \ ]
+let g:test_data_dic10 = [
+ \"1234",
+ \"drink/As",
+ \"table/e",
+ \ ]
let g:test_data_aff_sal = [
\"SET ISO8859-1",
\"TRY esianrtolcdugmphbyfvkwjkqxz-\xEB\xE9\xE8\xEA\xEF\xEE\xE4\xE0\xE2\xF6\xFC\xFB'ESIANRTOLCDUGMPHBYFVKWJKQXZ",
diff --git a/src/nvim/testdir/test_startup.vim b/src/nvim/testdir/test_startup.vim
index 1e70f28a00..9abaca5957 100644
--- a/src/nvim/testdir/test_startup.vim
+++ b/src/nvim/testdir/test_startup.vim
@@ -20,25 +20,27 @@ func Test_after_comes_later()
if !has('packages')
return
endif
- let before = [
- \ 'set nocp viminfo+=nviminfo',
- \ 'set guioptions+=M',
- \ 'let $HOME = "/does/not/exist"',
- \ 'set loadplugins',
- \ 'set rtp=Xhere,Xafter,Xanother',
- \ 'set packpath=Xhere,Xafter',
- \ 'set nomore',
- \ 'let g:sequence = ""',
- \ ]
- let after = [
- \ 'redir! > Xtestout',
- \ 'scriptnames',
- \ 'redir END',
- \ 'redir! > Xsequence',
- \ 'echo g:sequence',
- \ 'redir END',
- \ 'quit',
- \ ]
+ let before =<< trim [CODE]
+ set nocp viminfo+=nviminfo
+ set guioptions+=M
+ let $HOME = "/does/not/exist"
+ set loadplugins
+ set rtp=Xhere,Xafter,Xanother
+ set packpath=Xhere,Xafter
+ set nomore
+ let g:sequence = ""
+ [CODE]
+
+ let after =<< trim [CODE]
+ redir! > Xtestout
+ scriptnames
+ redir END
+ redir! > Xsequence
+ echo g:sequence
+ redir END
+ quit
+ [CODE]
+
call mkdir('Xhere/plugin', 'p')
call writefile(['let g:sequence .= "here "'], 'Xhere/plugin/here.vim')
call mkdir('Xanother/plugin', 'p')
@@ -77,15 +79,16 @@ func Test_pack_in_rtp_when_plugins_run()
if !has('packages')
return
endif
- let before = [
- \ 'set nocp viminfo+=nviminfo',
- \ 'set guioptions+=M',
- \ 'let $HOME = "/does/not/exist"',
- \ 'set loadplugins',
- \ 'set rtp=Xhere',
- \ 'set packpath=Xhere',
- \ 'set nomore',
- \ ]
+ let before =<< trim [CODE]
+ set nocp viminfo+=nviminfo
+ set guioptions+=M
+ let $HOME = "/does/not/exist"
+ set loadplugins
+ set rtp=Xhere
+ set packpath=Xhere
+ set nomore
+ [CODE]
+
let after = [
\ 'quit',
\ ]
@@ -133,11 +136,12 @@ endfunc
func Test_compatible_args()
throw "skipped: Nvim is always 'nocompatible'"
- let after = [
- \ 'call writefile([string(&compatible)], "Xtestout")',
- \ 'set viminfo+=nviminfo',
- \ 'quit',
- \ ]
+ let after =<< trim [CODE]
+ call writefile([string(&compatible)], "Xtestout")
+ set viminfo+=nviminfo
+ quit
+ [CODE]
+
if RunVim([], after, '-C')
let lines = readfile('Xtestout')
call assert_equal('1', lines[0])
@@ -154,14 +158,15 @@ endfunc
" Test the -o[N] and -O[N] arguments to open N windows split
" horizontally or vertically.
func Test_o_arg()
- let after = [
- \ 'call writefile([winnr("$"),
- \ winheight(1), winheight(2), &lines,
- \ winwidth(1), winwidth(2), &columns,
- \ bufname(winbufnr(1)), bufname(winbufnr(2))],
- \ "Xtestout")',
- \ 'qall',
- \ ]
+ let after =<< trim [CODE]
+ call writefile([winnr("$"),
+ \ winheight(1), winheight(2), &lines,
+ \ winwidth(1), winwidth(2), &columns,
+ \ bufname(winbufnr(1)), bufname(winbufnr(2))],
+ \ "Xtestout")
+ qall
+ [CODE]
+
if RunVim([], after, '-o2')
" Open 2 windows split horizontally. Expect:
" - 2 windows
@@ -230,17 +235,18 @@ endfunc
" Test the -p[N] argument to open N tabpages.
func Test_p_arg()
- let after = [
- \ 'call writefile(split(execute("tabs"), "\n"), "Xtestout")',
- \ 'qall',
- \ ]
+ let after =<< trim [CODE]
+ call writefile(split(execute("tabs"), "\n"), "Xtestout")
+ qall
+ [CODE]
+
if RunVim([], after, '-p2')
let lines = readfile('Xtestout')
call assert_equal(4, len(lines))
call assert_equal('Tab page 1', lines[0])
call assert_equal('> [No Name]', lines[1])
call assert_equal('Tab page 2', lines[2])
- call assert_equal(' [No Name]', lines[3])
+ call assert_equal('# [No Name]', lines[3])
endif
if RunVim([], after, '-p foo bar')
@@ -249,7 +255,7 @@ func Test_p_arg()
call assert_equal('Tab page 1', lines[0])
call assert_equal('> foo', lines[1])
call assert_equal('Tab page 2', lines[2])
- call assert_equal(' bar', lines[3])
+ call assert_equal('# bar', lines[3])
endif
call delete('Xtestout')
@@ -265,7 +271,7 @@ func Test_V_arg()
call assert_equal(" verbose=0\n", out)
let out = system(GetVimCommand() . ' --clean -es -X -V2 -c "set verbose?" -cq')
- " call assert_match("sourcing \"$VIMRUNTIME[\\/]defaults\.vim\"\r\nSearching for \"filetype\.vim\".*\n", out)
+ " call assert_match("sourcing \"$VIMRUNTIME[\\/]defaults\.vim\"\r\nline \\d\\+: sourcing \"[^\"]*runtime[\\/]filetype\.vim\".*\n", out)
call assert_match(" verbose=2\n", out)
let out = system(GetVimCommand() . ' --clean -es -X -V15 -c "set verbose?" -cq')
@@ -290,10 +296,11 @@ endfunc
" -M resets 'modifiable' and 'write'
" -R sets 'readonly'
func Test_m_M_R()
- let after = [
- \ 'call writefile([&write, &modifiable, &readonly, &updatecount], "Xtestout")',
- \ 'qall',
- \ ]
+ let after =<< trim [CODE]
+ call writefile([&write, &modifiable, &readonly, &updatecount], "Xtestout")
+ qall
+ [CODE]
+
if RunVim([], after, '')
let lines = readfile('Xtestout')
call assert_equal(['1', '1', '0', '200'], lines)
@@ -316,10 +323,11 @@ endfunc
" Test the -A, -F and -H arguments (Arabic, Farsi and Hebrew modes).
func Test_A_F_H_arg()
- let after = [
- \ 'call writefile([&rightleft, &arabic, 0, &hkmap], "Xtestout")',
- \ 'qall',
- \ ]
+ let after =<< trim [CODE]
+ call writefile([&rightleft, &arabic, 0, &hkmap], "Xtestout")
+ qall
+ [CODE]
+
" Use silent Ex mode to avoid the hit-Enter prompt for the warning that
" 'encoding' is not utf-8.
if has('arabic') && &encoding == 'utf-8' && RunVim([], after, '-e -s -A')
@@ -423,10 +431,11 @@ func Test_invalid_args()
endfunc
func Test_file_args()
- let after = [
- \ 'call writefile(argv(), "Xtestout")',
- \ 'qall',
- \ ]
+ let after =<< trim [CODE]
+ call writefile(argv(), "Xtestout")
+ qall
+ [CODE]
+
if RunVim([], after, '')
let lines = readfile('Xtestout')
call assert_equal(0, len(lines))
@@ -487,10 +496,11 @@ func Test_startuptime()
endfunc
func Test_read_stdin()
- let after = [
- \ 'write Xtestout',
- \ 'quit!',
- \ ]
+ let after =<< trim [CODE]
+ write Xtestout
+ quit!
+ [CODE]
+
if RunVimPiped([], after, '-', 'echo something | ')
let lines = readfile('Xtestout')
" MS-Windows adds a space after the word
@@ -540,20 +550,22 @@ endfunc
func Test_zzz_startinsert()
" Test :startinsert
call writefile(['123456'], 'Xtestout')
- let after = [
- \ ':startinsert',
- \ 'call feedkeys("foobar\<c-o>:wq\<cr>","t")'
- \ ]
+ let after =<< trim [CODE]
+ :startinsert
+ call feedkeys("foobar\<c-o>:wq\<cr>","t")
+ [CODE]
+
if RunVim([], after, 'Xtestout')
let lines = readfile('Xtestout')
call assert_equal(['foobar123456'], lines)
endif
" Test :startinsert!
call writefile(['123456'], 'Xtestout')
- let after = [
- \ ':startinsert!',
- \ 'call feedkeys("foobar\<c-o>:wq\<cr>","t")'
- \ ]
+ let after =<< trim [CODE]
+ :startinsert!
+ call feedkeys("foobar\<c-o>:wq\<cr>","t")
+ [CODE]
+
if RunVim([], after, 'Xtestout')
let lines = readfile('Xtestout')
call assert_equal(['123456foobar'], lines)
@@ -572,3 +584,12 @@ func Test_start_with_tabs()
" clean up
call StopVimInTerminal(buf)
endfunc
+
+func Test_v_argv()
+ let out = system(GetVimCommand() . ' -es -V1 -X arg1 --cmd "echo v:argv" --cmd q')
+ let list = split(out, "', '")
+ call assert_match('vim', list[0])
+ let idx = index(list, 'arg1')
+ call assert_true(idx > 2)
+ call assert_equal(['arg1', '--cmd', 'echo v:argv', '--cmd', 'q'']'], list[idx:])
+endfunc
diff --git a/src/nvim/testdir/test_startup_utf8.vim b/src/nvim/testdir/test_startup_utf8.vim
index b24b0eb5cf..1b3d2184a0 100644
--- a/src/nvim/testdir/test_startup_utf8.vim
+++ b/src/nvim/testdir/test_startup_utf8.vim
@@ -1,7 +1,7 @@
" Tests for startup using utf-8.
source shared.vim
-" source screendump.vim
+source screendump.vim
func Test_read_stdin_utf8()
let linesin = ['テスト', '€ÀÈÌÒÙ']
diff --git a/src/nvim/testdir/test_statusline.vim b/src/nvim/testdir/test_statusline.vim
index b86340a23a..8c81ec3431 100644
--- a/src/nvim/testdir/test_statusline.vim
+++ b/src/nvim/testdir/test_statusline.vim
@@ -7,6 +7,7 @@
" %X
source view_util.vim
+source term_util.vim
func s:get_statusline()
return ScreenLines(&lines - 1, &columns)[0]
@@ -29,7 +30,9 @@ endfunc
" Function used to display syntax group.
func SyntaxItem()
- return synIDattr(synID(line("."),col("."),1),"name")
+ call assert_equal(s:expected_curbuf, g:actual_curbuf)
+ call assert_equal(s:expected_curwin, g:actual_curwin)
+ return synIDattr(synID(line("."), col("."),1), "name")
endfunc
func Test_caught_error_in_statusline()
@@ -218,6 +221,8 @@ func Test_statusline()
"%{: Evaluate expression between '%{' and '}' and substitute result.
syntax on
+ let s:expected_curbuf = string(bufnr(''))
+ let s:expected_curwin = string(win_getid())
set statusline=%{SyntaxItem()}
call assert_match('^vimNumber\s*$', s:get_statusline())
s/^/"/
@@ -332,6 +337,23 @@ func Test_statusline()
set statusline=%!2*3+1
call assert_match('7\s*$', s:get_statusline())
+ func GetNested()
+ call assert_equal(string(win_getid()), g:actual_curwin)
+ call assert_equal(string(bufnr('')), g:actual_curbuf)
+ return 'nested'
+ endfunc
+ func GetStatusLine()
+ call assert_equal(win_getid(), g:statusline_winid)
+ return 'the %{GetNested()} line'
+ endfunc
+ set statusline=%!GetStatusLine()
+ call assert_match('the nested line', s:get_statusline())
+ call assert_false(exists('g:actual_curwin'))
+ call assert_false(exists('g:actual_curbuf'))
+ call assert_false(exists('g:statusline_winid'))
+ delfunc GetNested
+ delfunc GetStatusLine
+
" Check statusline in current and non-current window
" with the 'fillchars' option.
set fillchars=stl:^,stlnc:=,vert:\|,fold:-,diff:-
@@ -347,3 +369,46 @@ func Test_statusline()
set laststatus&
set splitbelow&
endfunc
+
+func Test_statusline_visual()
+ func CallWordcount()
+ call wordcount()
+ endfunc
+ new x1
+ setl statusline=count=%{CallWordcount()}
+ " buffer must not be empty
+ call setline(1, 'hello')
+
+ " window with more lines than x1
+ new x2
+ call setline(1, range(10))
+ $
+ " Visual mode in line below liast line in x1 should not give ml_get error
+ call feedkeys("\<C-V>", "xt")
+ redraw
+
+ delfunc CallWordcount
+ bwipe! x1
+ bwipe! x2
+endfunc
+
+func Test_statusline_removed_group()
+ if !CanRunVimInTerminal()
+ throw 'Skipped: cannot make screendumps'
+ endif
+
+ let lines =<< trim END
+ scriptencoding utf-8
+ set laststatus=2
+ let &statusline = '%#StatColorHi2#%(✓%#StatColorHi2#%) Q≡'
+ END
+ call writefile(lines, 'XTest_statusline')
+
+ let buf = RunVimInTerminal('-S XTest_statusline', {'rows': 10, 'cols': 50})
+ call term_wait(buf, 100)
+ call VerifyScreenDump(buf, 'Test_statusline_1', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+ call delete('XTest_statusline')
+endfunc
diff --git a/src/nvim/testdir/test_substitute.vim b/src/nvim/testdir/test_substitute.vim
index b29b678129..ff07d8eceb 100644
--- a/src/nvim/testdir/test_substitute.vim
+++ b/src/nvim/testdir/test_substitute.vim
@@ -149,6 +149,7 @@ func Run_SubCmd_Tests(tests)
for t in a:tests
let start = line('.') + 1
let end = start + len(t[2]) - 1
+ " TODO: why is there a one second delay the first time we get here?
exe "normal o" . t[0]
call cursor(start, 1)
exe t[1]
@@ -240,7 +241,7 @@ func Test_sub_cmd_3()
call Run_SubCmd_Tests(tests)
endfunc
-" Test for submatch() on :substitue.
+" Test for submatch() on :substitute.
func Test_sub_cmd_4()
set magic&
set cpo&
@@ -612,6 +613,25 @@ func Test_sub_replace_10()
call assert_equal('1aaa', substitute('123', '1\zs\|[23]', 'a', 'g'))
endfunc
+func SubReplacer(text, submatches)
+ return a:text .. a:submatches[0] .. a:text
+endfunc
+func SubReplacer20(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14, t15, t16, t17, t18, t19, submatches)
+ return a:t3 .. a:submatches[0] .. a:t11
+endfunc
+
+func Test_substitute_partial()
+ call assert_equal('1foo2foo3', substitute('123', '2', function('SubReplacer', ['foo']), 'g'))
+
+ " 19 arguments plus one is just OK
+ let Replacer = function('SubReplacer20', repeat(['foo'], 19))
+ call assert_equal('1foo2foo3', substitute('123', '2', Replacer, 'g'))
+
+ " 20 arguments plus one is too many
+ let Replacer = function('SubReplacer20', repeat(['foo'], 20))
+ call assert_fails("call substitute('123', '2', Replacer, 'g')", 'E118')
+endfunc
+
func Test_sub_cmd_9()
new
let input = ['1 aaa', '2 aaa', '3 aaa']
@@ -717,3 +737,12 @@ one two
close!
endfunc
+
+func Test_sub_beyond_end()
+ new
+ call setline(1, '#')
+ let @/ = '^#\n\zs'
+ s///e
+ call assert_equal('#', getline(1))
+ bwipe!
+endfunc
diff --git a/src/nvim/testdir/test_suspend.vim b/src/nvim/testdir/test_suspend.vim
index ef5a96bd72..4b3bd5eadf 100644
--- a/src/nvim/testdir/test_suspend.vim
+++ b/src/nvim/testdir/test_suspend.vim
@@ -1,6 +1,7 @@
" Test :suspend
source shared.vim
+source term_util.vim
func CheckSuspended(buf, fileExists)
call WaitForAssert({-> assert_match('[$#] $', term_getline(a:buf, '.'))})
@@ -55,7 +56,7 @@ func Test_suspend()
call term_wait(buf)
" Wait until Vim actually exited and shell shows a prompt
call WaitForAssert({-> assert_match('[$#] $', term_getline(buf, '.'))})
- call Stop_shell_in_terminal(buf)
+ call StopShellInTerminal(buf)
exe buf . 'bwipe!'
call delete('Xfoo')
diff --git a/src/nvim/testdir/test_swap.vim b/src/nvim/testdir/test_swap.vim
index 11eb324488..e072e9ed7f 100644
--- a/src/nvim/testdir/test_swap.vim
+++ b/src/nvim/testdir/test_swap.vim
@@ -221,3 +221,87 @@ func Test_swapfile_delete()
augroup END
augroup! test_swapfile_delete
endfunc
+
+func Test_swap_recover()
+ autocmd! SwapExists
+ augroup test_swap_recover
+ autocmd!
+ autocmd SwapExists * let v:swapchoice = 'r'
+ augroup END
+
+
+ call mkdir('Xswap')
+ let $Xswap = 'foo' " Check for issue #4369.
+ set dir=Xswap//
+ " Create a valid swapfile by editing a file.
+ split Xswap/text
+ call setline(1, ['one', 'two', 'three'])
+ write " file is written, not modified
+ " read the swapfile as a Blob
+ let swapfile_name = swapname('%')
+ let swapfile_bytes = readfile(swapfile_name, 'B')
+
+ " Close the file and recreate the swap file.
+ quit
+ call writefile(swapfile_bytes, swapfile_name)
+ " Edit the file again. This triggers recovery.
+ try
+ split Xswap/text
+ catch
+ " E308 should be caught, not E305.
+ call assert_exception('E308:') " Original file may have been changed
+ endtry
+ " The file should be recovered.
+ call assert_equal(['one', 'two', 'three'], getline(1, 3))
+ quit!
+
+ call delete('Xswap/text')
+ call delete(swapfile_name)
+ call delete('Xswap', 'd')
+ unlet $Xswap
+ set dir&
+ augroup test_swap_recover
+ autocmd!
+ augroup END
+ augroup! test_swap_recover
+endfunc
+
+func Test_swap_recover_ext()
+ autocmd! SwapExists
+ augroup test_swap_recover_ext
+ autocmd!
+ autocmd SwapExists * let v:swapchoice = 'r'
+ augroup END
+
+
+ " Create a valid swapfile by editing a file with a special extension.
+ split Xtest.scr
+ call setline(1, ['one', 'two', 'three'])
+ write " file is written, not modified
+ write " write again to make sure the swapfile is created
+ " read the swapfile as a Blob
+ let swapfile_name = swapname('%')
+ let swapfile_bytes = readfile(swapfile_name, 'B')
+
+ " Close and delete the file and recreate the swap file.
+ quit
+ call delete('Xtest.scr')
+ call writefile(swapfile_bytes, swapfile_name)
+ " Edit the file again. This triggers recovery.
+ try
+ split Xtest.scr
+ catch
+ " E308 should be caught, not E306.
+ call assert_exception('E308:') " Original file may have been changed
+ endtry
+ " The file should be recovered.
+ call assert_equal(['one', 'two', 'three'], getline(1, 3))
+ quit!
+
+ call delete('Xtest.scr')
+ call delete(swapfile_name)
+ augroup test_swap_recover_ext
+ autocmd!
+ augroup END
+ augroup! test_swap_recover_ext
+endfunc
diff --git a/src/nvim/testdir/test_syntax.vim b/src/nvim/testdir/test_syntax.vim
index 598a00476c..85ee42420e 100644
--- a/src/nvim/testdir/test_syntax.vim
+++ b/src/nvim/testdir/test_syntax.vim
@@ -1,6 +1,7 @@
" Test for syntax and syntax iskeyword option
source view_util.vim
+source screendump.vim
func GetSyntaxItem(pat)
let c = ''
@@ -152,7 +153,7 @@ endfunc
func Test_syntax_completion()
call feedkeys(":syn \<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"syn case clear cluster conceal enable include iskeyword keyword list manual match off on region reset spell sync', @:)
+ call assert_equal('"syn case clear cluster conceal enable foldlevel include iskeyword keyword list manual match off on region reset spell sync', @:)
call feedkeys(":syn case \<C-A>\<C-B>\"\<CR>", 'tx')
call assert_equal('"syn case ignore match', @:)
@@ -501,9 +502,7 @@ func Test_syntax_c()
endif
call writefile([
\ '/* comment line at the top */',
- \ ' int',
- \ 'main(int argc, char **argv)// another comment',
- \ '{',
+ \ 'int main(int argc, char **argv) { // another comment',
\ '#if 0',
\ ' int not_used;',
\ '#else',
@@ -518,6 +517,7 @@ func Test_syntax_c()
\ ' for (int i = 0; i < count; ++i) {',
\ ' break;',
\ ' }',
+ \ " Note: asdf",
\ '}',
\ ], 'Xtest.c')
@@ -526,7 +526,8 @@ func Test_syntax_c()
let $COLORFGBG = '15;0'
let buf = RunVimInTerminal('Xtest.c', {})
- call VerifyScreenDump(buf, 'Test_syntax_c_01')
+ call term_sendkeys(buf, ":syn keyword Search Note\r")
+ call VerifyScreenDump(buf, 'Test_syntax_c_01', {})
call StopVimInTerminal(buf)
let $COLORFGBG = ''
@@ -578,3 +579,86 @@ func Test_syntax_hangs()
set redrawtime&
bwipe!
endfunc
+
+func Test_syntax_foldlevel()
+ new
+ call setline(1, [
+ \ 'void f(int a)',
+ \ '{',
+ \ ' if (a == 1) {',
+ \ ' a = 0;',
+ \ ' } else if (a == 2) {',
+ \ ' a = 1;',
+ \ ' } else {',
+ \ ' a = 2;',
+ \ ' }',
+ \ ' if (a > 0) {',
+ \ ' if (a == 1) {',
+ \ ' a = 0;',
+ \ ' } /* missing newline */ } /* end of outer if */ else {',
+ \ ' a = 1;',
+ \ ' }',
+ \ ' if (a == 1)',
+ \ ' {',
+ \ ' a = 0;',
+ \ ' }',
+ \ ' else if (a == 2)',
+ \ ' {',
+ \ ' a = 1;',
+ \ ' }',
+ \ ' else',
+ \ ' {',
+ \ ' a = 2;',
+ \ ' }',
+ \ '}',
+ \ ])
+ setfiletype c
+ syntax on
+ set foldmethod=syntax
+
+ call assert_fails('syn foldlevel start start', 'E390')
+ call assert_fails('syn foldlevel not_an_option', 'E390')
+
+ set foldlevel=1
+
+ syn foldlevel start
+ redir @c
+ syn foldlevel
+ redir END
+ call assert_equal("\nsyntax foldlevel start", @c)
+ syn sync fromstart
+ let a = map(range(3,9), 'foldclosed(v:val)')
+ call assert_equal([3,3,3,3,3,3,3], a) " attached cascade folds together
+ let a = map(range(10,15), 'foldclosed(v:val)')
+ call assert_equal([10,10,10,10,10,10], a) " over-attached 'else' hidden
+ let a = map(range(16,27), 'foldclosed(v:val)')
+ let unattached_results = [-1,17,17,17,-1,21,21,21,-1,25,25,25]
+ call assert_equal(unattached_results, a) " unattached cascade folds separately
+
+ syn foldlevel minimum
+ redir @c
+ syn foldlevel
+ redir END
+ call assert_equal("\nsyntax foldlevel minimum", @c)
+ syn sync fromstart
+ let a = map(range(3,9), 'foldclosed(v:val)')
+ call assert_equal([3,3,5,5,7,7,7], a) " attached cascade folds separately
+ let a = map(range(10,15), 'foldclosed(v:val)')
+ call assert_equal([10,10,10,13,13,13], a) " over-attached 'else' visible
+ let a = map(range(16,27), 'foldclosed(v:val)')
+ call assert_equal(unattached_results, a) " unattached cascade folds separately
+
+ set foldlevel=2
+
+ syn foldlevel start
+ syn sync fromstart
+ let a = map(range(11,14), 'foldclosed(v:val)')
+ call assert_equal([11,11,11,-1], a) " over-attached 'else' hidden
+
+ syn foldlevel minimum
+ syn sync fromstart
+ let a = map(range(11,14), 'foldclosed(v:val)')
+ call assert_equal([11,11,-1,-1], a) " over-attached 'else' visible
+
+ quit!
+endfunc
diff --git a/src/nvim/testdir/test_tabline.vim b/src/nvim/testdir/test_tabline.vim
index f24552088b..117d962d08 100644
--- a/src/nvim/testdir/test_tabline.vim
+++ b/src/nvim/testdir/test_tabline.vim
@@ -64,3 +64,28 @@ func Test_redrawtabline()
let &showtabline = showtabline_save
au! Bufadd
endfunc
+
+function EmptyTabname()
+ return ""
+endfunction
+
+function MakeTabLine() abort
+ let titles = map(range(1, tabpagenr('$')), '"%( %" . v:val . "T%{EmptyTabname()}%T %)"')
+ let sep = 'ã‚'
+ let tabpages = join(titles, sep)
+ return tabpages .. sep .. '%=%999X X'
+endfunction
+
+func Test_tabline_empty_group()
+ " this was reading invalid memory
+ set tabline=%!MakeTabLine()
+ tabnew
+ redraw!
+
+ tabclose
+ set tabline=
+endfunc
+
+
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_tabpage.vim b/src/nvim/testdir/test_tabpage.vim
index c35ddc4473..55dff3d476 100644
--- a/src/nvim/testdir/test_tabpage.vim
+++ b/src/nvim/testdir/test_tabpage.vim
@@ -1,6 +1,6 @@
" Tests for tabpage
-" source screendump.vim
+source screendump.vim
function Test_tabpage()
bw!
@@ -548,7 +548,7 @@ func Test_tabs()
norm ixxx
let a=split(execute(':tabs'), "\n")
call assert_equal(['Tab page 1',
- \ ' [No Name]',
+ \ '# [No Name]',
\ 'Tab page 2',
\ '> + tab1'], a)
diff --git a/src/nvim/testdir/test_tagfunc.vim b/src/nvim/testdir/test_tagfunc.vim
new file mode 100644
index 0000000000..242aa3a235
--- /dev/null
+++ b/src/nvim/testdir/test_tagfunc.vim
@@ -0,0 +1,84 @@
+" Test 'tagfunc'
+
+func TagFunc(pat, flag, info)
+ let g:tagfunc_args = [a:pat, a:flag, a:info]
+ let tags = []
+ for num in range(1,10)
+ let tags += [{
+ \ 'cmd': '2', 'name': 'nothing'.num, 'kind': 'm',
+ \ 'filename': 'Xfile1', 'user_data': 'somedata'.num,
+ \}]
+ endfor
+ return tags
+endfunc
+
+func Test_tagfunc()
+ set tagfunc=TagFunc
+ new Xfile1
+ call setline(1, ['empty', 'one()', 'empty'])
+ write
+
+ call assert_equal({'cmd': '2', 'static': 0,
+ \ 'name': 'nothing2', 'user_data': 'somedata2',
+ \ 'kind': 'm', 'filename': 'Xfile1'}, taglist('.')[1])
+
+ call settagstack(win_getid(), {'items': []})
+
+ tag arbitrary
+ call assert_equal('arbitrary', g:tagfunc_args[0])
+ call assert_equal('', g:tagfunc_args[1])
+ call assert_equal('somedata1', gettagstack().items[0].user_data)
+ 5tag arbitrary
+ call assert_equal('arbitrary', g:tagfunc_args[0])
+ call assert_equal('', g:tagfunc_args[1])
+ call assert_equal('somedata5', gettagstack().items[1].user_data)
+ pop
+ tag
+ call assert_equal('arbitrary', g:tagfunc_args[0])
+ call assert_equal('', g:tagfunc_args[1])
+ call assert_equal('somedata5', gettagstack().items[1].user_data)
+
+ let g:tagfunc_args=[]
+ execute "normal! \<c-]>"
+ call assert_equal('one', g:tagfunc_args[0])
+ call assert_equal('c', g:tagfunc_args[1])
+
+ set cpt=t
+ let g:tagfunc_args=[]
+ execute "normal! i\<c-n>\<c-y>"
+ call assert_equal('ci', g:tagfunc_args[1])
+ call assert_equal('nothing1', getline('.')[0:7])
+
+ func BadTagFunc1(...)
+ return 0
+ endfunc
+ func BadTagFunc2(...)
+ return [1]
+ endfunc
+ func BadTagFunc3(...)
+ return [{'name': 'foo'}]
+ endfunc
+
+ for &tagfunc in ['BadTagFunc1', 'BadTagFunc2', 'BadTagFunc3']
+ try
+ tag nothing
+ call assert_false(1, 'tag command should have failed')
+ catch
+ call assert_exception('E987:')
+ endtry
+ exe 'delf' &tagfunc
+ endfor
+
+ func NullTagFunc(...)
+ return v:null
+ endfunc
+ set tags= tfu=NullTagFunc
+ call assert_fails('tag nothing', 'E426')
+ delf NullTagFunc
+
+ bwipe!
+ set tags& tfu& cpt&
+ call delete('Xfile1')
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_tagjump.vim b/src/nvim/testdir/test_tagjump.vim
index ce527a5e1d..6abe5b7c89 100644
--- a/src/nvim/testdir/test_tagjump.vim
+++ b/src/nvim/testdir/test_tagjump.vim
@@ -255,8 +255,52 @@ func Test_tagjump_etags()
ta foo
call assert_equal('void foo() {}', getline('.'))
+ " Test for including another tags file
+ call writefile([
+ \ "\x0c",
+ \ "Xmain.c,64",
+ \ "void foo() {}\x7ffoo\x011,0",
+ \ "\x0c",
+ \ "Xnonexisting,include",
+ \ "\x0c",
+ \ "Xtags2,include"
+ \ ], 'Xtags')
+ call writefile([
+ \ "\x0c",
+ \ "Xmain.c,64",
+ \ "int main(int argc, char **argv)\x7fmain\x012,14",
+ \ ], 'Xtags2')
+ tag main
+ call assert_equal(2, line('.'))
+
+ " corrupted tag line
+ call writefile([
+ \ "\x0c",
+ \ "Xmain.c,8",
+ \ "int main"
+ \ ], 'Xtags', 'b')
+ call assert_fails('tag foo', 'E426:')
+
+ " invalid line number
+ call writefile([
+ \ "\x0c",
+ \ "Xmain.c,64",
+ \ "void foo() {}\x7ffoo\x0abc,0",
+ \ ], 'Xtags')
+ call assert_fails('tag foo', 'E426:')
+
+ " invalid tag name
+ call writefile([
+ \ "\x0c",
+ \ "Xmain.c,64",
+ \ ";;;;\x7f1,0",
+ \ ], 'Xtags')
+ call assert_fails('tag foo', 'E426:')
+
call delete('Xtags')
+ call delete('Xtags2')
call delete('Xmain.c')
+ set tags&
bwipe!
endfunc
@@ -340,6 +384,28 @@ func Test_getsettagstack()
\ {'items' : [{'tagname' : 'abc', 'from' : [1, 10, 1, 0]}]}, 'a')
call assert_equal('abc', gettagstack().items[19].tagname)
+ " truncate the tag stack
+ call settagstack(1,
+ \ {'curidx' : 9,
+ \ 'items' : [{'tagname' : 'abc', 'from' : [1, 10, 1, 0]}]}, 't')
+ let t = gettagstack()
+ call assert_equal(9, t.length)
+ call assert_equal(10, t.curidx)
+
+ " truncate the tag stack without pushing any new items
+ call settagstack(1, {'curidx' : 5}, 't')
+ let t = gettagstack()
+ call assert_equal(4, t.length)
+ call assert_equal(5, t.curidx)
+
+ " truncate an empty tag stack and push new items
+ call settagstack(1, {'items' : []})
+ call settagstack(1,
+ \ {'items' : [{'tagname' : 'abc', 'from' : [1, 10, 1, 0]}]}, 't')
+ let t = gettagstack()
+ call assert_equal(1, t.length)
+ call assert_equal(2, t.curidx)
+
" Tag with multiple matches
call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
\ "two\tXfile1\t1",
@@ -449,7 +515,8 @@ func Test_tag_line_toolong()
call assert_report(v:exception)
catch /.*/
endtry
- call assert_equal('Ignoring long line in tags file', split(execute('messages'), '\n')[-1])
+ call assert_equal('Searching tags file Xtags', split(execute('messages'), '\n')[-1])
+
call writefile([
\ '123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567 django/contrib/admin/templates/admin/edit_inline/stacked.html 16;" j line:16 language:HTML'
\ ], 'Xtags')
@@ -460,10 +527,77 @@ func Test_tag_line_toolong()
call assert_report(v:exception)
catch /.*/
endtry
- call assert_equal('Ignoring long line in tags file', split(execute('messages'), '\n')[-1])
+ call assert_equal('Searching tags file Xtags', split(execute('messages'), '\n')[-1])
+
+ " binary search works in file with long line
+ call writefile([
+ \ 'asdfasfd nowhere 16',
+ \ 'foobar Xsomewhere 3; " 12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567',
+ \ 'zasdfasfd nowhere 16',
+ \ ], 'Xtags')
+ call writefile([
+ \ 'one',
+ \ 'two',
+ \ 'trhee',
+ \ 'four',
+ \ ], 'Xsomewhere')
+ tag foobar
+ call assert_equal('Xsomewhere', expand('%'))
+ call assert_equal(3, getcurpos()[1])
+
call delete('Xtags')
+ call delete('Xsomewhere')
set tags&
let &verbose = old_vbs
endfunc
+func Test_tagline()
+ call writefile([
+ \ 'provision Xtest.py /^ def provision(self, **kwargs):$/;" m line:1 language:Python class:Foo',
+ \ 'provision Xtest.py /^ def provision(self, **kwargs):$/;" m line:3 language:Python class:Bar',
+ \], 'Xtags')
+ call writefile([
+ \ ' def provision(self, **kwargs):',
+ \ ' pass',
+ \ ' def provision(self, **kwargs):',
+ \ ' pass',
+ \], 'Xtest.py')
+
+ set tags=Xtags
+
+ 1tag provision
+ call assert_equal(line('.'), 1)
+ 2tag provision
+ call assert_equal(line('.'), 3)
+
+ call delete('Xtags')
+ call delete('Xtest.py')
+ set tags&
+endfunc
+
+" Test for the 'taglength' option
+func Test_tag_length()
+ set tags=Xtags
+ call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
+ \ "tame\tXfile1\t1;",
+ \ "tape\tXfile2\t1;"], 'Xtags')
+ call writefile(['tame'], 'Xfile1')
+ call writefile(['tape'], 'Xfile2')
+
+ " Jumping to the tag 'tape', should instead jump to 'tame'
+ new
+ set taglength=2
+ tag tape
+ call assert_equal('Xfile1', @%)
+ " Tag search should jump to the right tag
+ enew
+ tag /^tape$
+ call assert_equal('Xfile2', @%)
+
+ call delete('Xtags')
+ call delete('Xfile1')
+ call delete('Xfile2')
+ set tags& taglength&
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_taglist.vim b/src/nvim/testdir/test_taglist.vim
index cb54ace695..d4ff42fd68 100644
--- a/src/nvim/testdir/test_taglist.vim
+++ b/src/nvim/testdir/test_taglist.vim
@@ -7,6 +7,7 @@ func Test_taglist()
\ "BFoo\tXbar\t1",
\ "BBar\tXbar\t2",
\ "Kindly\tXbar\t3;\"\tv\tfile:",
+ \ "Lambda\tXbar\t3;\"\tλ\tfile:",
\ "Command\tXbar\tcall cursor(3, 4)|;\"\td",
\ ], 'Xtags')
set tags=Xtags
@@ -17,12 +18,16 @@ func Test_taglist()
call assert_equal(['FFoo', 'BFoo'], map(taglist("Foo", "Xfoo"), {i, v -> v.name}))
call assert_equal(['BFoo', 'FFoo'], map(taglist("Foo", "Xbar"), {i, v -> v.name}))
- let kind = taglist("Kindly")
- call assert_equal(1, len(kind))
- call assert_equal('v', kind[0]['kind'])
- call assert_equal('3', kind[0]['cmd'])
- call assert_equal(1, kind[0]['static'])
- call assert_equal('Xbar', kind[0]['filename'])
+ let kindly = taglist("Kindly")
+ call assert_equal(1, len(kindly))
+ call assert_equal('v', kindly[0]['kind'])
+ call assert_equal('3', kindly[0]['cmd'])
+ call assert_equal(1, kindly[0]['static'])
+ call assert_equal('Xbar', kindly[0]['filename'])
+
+ let lambda = taglist("Lambda")
+ call assert_equal(1, len(lambda))
+ call assert_equal('λ', lambda[0]['kind'])
let cmd = taglist("Command")
call assert_equal(1, len(cmd))
diff --git a/src/nvim/testdir/test_textformat.vim b/src/nvim/testdir/test_textformat.vim
index 13fb50b985..75673adf0a 100644
--- a/src/nvim/testdir/test_textformat.vim
+++ b/src/nvim/testdir/test_textformat.vim
@@ -489,3 +489,426 @@ func Test_format_list_auto()
bwipe!
set fo& ai& bs&
endfunc
+
+" Test for formatting multi-byte text with 'fo=t'
+func Test_tw_2_fo_t()
+ new
+ let t =<< trim END
+ {
+ XYZ
+ abc XYZ
+ }
+ END
+ call setline(1, t)
+ call cursor(2, 1)
+
+ set tw=2 fo=t
+ let t =<< trim END
+ XYZ
+ abc XYZ
+ END
+ exe "normal gqgqjgqgq"
+ exe "normal o\n" . join(t, "\n")
+
+ let expected =<< trim END
+ {
+ XYZ
+ abc
+ XYZ
+
+ XYZ
+ abc
+ XYZ
+ }
+ END
+ call assert_equal(expected, getline(1, '$'))
+
+ set tw& fo&
+ bwipe!
+endfunc
+
+" Test for formatting multi-byte text with 'fo=tm' and 'tw=1'
+func Test_tw_1_fo_tm()
+ new
+ let t =<< trim END
+ {
+ X
+ Xa
+ X a
+ XY
+ X Y
+ }
+ END
+ call setline(1, t)
+ call cursor(2, 1)
+
+ set tw=1 fo=tm
+ let t =<< trim END
+ X
+ Xa
+ X a
+ XY
+ X Y
+ END
+ exe "normal gqgqjgqgqjgqgqjgqgqjgqgq"
+ exe "normal o\n" . join(t, "\n")
+
+ let expected =<< trim END
+ {
+ X
+ X
+ a
+ X
+ a
+ X
+ ï¼¹
+ X
+ ï¼¹
+
+ X
+ X
+ a
+ X
+ a
+ X
+ ï¼¹
+ X
+ ï¼¹
+ }
+ END
+ call assert_equal(expected, getline(1, '$'))
+
+ set tw& fo&
+ bwipe!
+endfunc
+
+" Test for formatting multi-byte text with 'fo=tm' and 'tw=2'
+func Test_tw_2_fo_tm()
+ new
+ let t =<< trim END
+ {
+ X
+ Xa
+ X a
+ XY
+ X Y
+ aX
+ abX
+ abcX
+ abX c
+ abXY
+ }
+ END
+ call setline(1, t)
+ call cursor(2, 1)
+
+ set tw=2 fo=tm
+ let t =<< trim END
+ X
+ Xa
+ X a
+ XY
+ X Y
+ aX
+ abX
+ abcX
+ abX c
+ abXY
+ END
+ exe "normal gqgqjgqgqjgqgqjgqgqjgqgqjgqgqjgqgqjgqgqjgqgqjgqgq"
+ exe "normal o\n" . join(t, "\n")
+
+ let expected =<< trim END
+ {
+ X
+ X
+ a
+ X
+ a
+ X
+ ï¼¹
+ X
+ ï¼¹
+ a
+ X
+ ab
+ X
+ abc
+ X
+ ab
+ X
+ c
+ ab
+ X
+ ï¼¹
+
+ X
+ X
+ a
+ X
+ a
+ X
+ ï¼¹
+ X
+ ï¼¹
+ a
+ X
+ ab
+ X
+ abc
+ X
+ ab
+ X
+ c
+ ab
+ X
+ ï¼¹
+ }
+ END
+ call assert_equal(expected, getline(1, '$'))
+
+ set tw& fo&
+ bwipe!
+endfunc
+
+" Test for formatting multi-byte text with 'fo=tm', 'tw=2' and 'autoindent'.
+func Test_tw_2_fo_tm_ai()
+ new
+ let t =<< trim END
+ {
+ X
+ Xa
+ }
+ END
+ call setline(1, t)
+ call cursor(2, 1)
+
+ set ai tw=2 fo=tm
+ let t =<< trim END
+ X
+ Xa
+ END
+ exe "normal gqgqjgqgq"
+ exe "normal o\n" . join(t, "\n")
+
+ let expected =<< trim END
+ {
+ X
+ X
+ a
+
+ X
+ X
+ a
+ }
+ END
+ call assert_equal(expected, getline(1, '$'))
+
+ set tw& fo& ai&
+ bwipe!
+endfunc
+
+" Test for formatting multi-byte text with 'fo=tm', 'tw=2' and 'noai'.
+func Test_tw_2_fo_tm_noai()
+ new
+ let t =<< trim END
+ {
+ X
+ Xa
+ }
+ END
+ call setline(1, t)
+ call cursor(2, 1)
+
+ set noai tw=2 fo=tm
+ exe "normal gqgqjgqgqo\n X\n Xa"
+
+ let expected =<< trim END
+ {
+ X
+ X
+ a
+
+ X
+ X
+ a
+ }
+ END
+ call assert_equal(expected, getline(1, '$'))
+
+ set tw& fo& ai&
+ bwipe!
+endfunc
+
+func Test_tw_2_fo_cqm_com()
+ new
+ let t =<< trim END
+ {
+ X
+ Xa
+ XaY
+ XY
+ XYZ
+ X Y
+ X YZ
+ XX
+ XXa
+ XXY
+ }
+ END
+ call setline(1, t)
+ call cursor(2, 1)
+
+ set tw=2 fo=cqm comments=n:X
+ exe "normal gqgqjgqgqjgqgqjgqgqjgqgqjgqgqjgqgqjgqgqjgqgqjgqgq"
+ let t =<< trim END
+ X
+ Xa
+ XaY
+ XY
+ XYZ
+ X Y
+ X YZ
+ XX
+ XXa
+ XXY
+ END
+ exe "normal o\n" . join(t, "\n")
+
+ let expected =<< trim END
+ {
+ X
+ Xa
+ Xa
+ XY
+ XY
+ XY
+ XZ
+ X Y
+ X Y
+ X Z
+ XX
+ XXa
+ XXY
+
+ X
+ Xa
+ Xa
+ XY
+ XY
+ XY
+ XZ
+ X Y
+ X Y
+ X Z
+ XX
+ XXa
+ XXY
+ }
+ END
+ call assert_equal(expected, getline(1, '$'))
+
+ set tw& fo& comments&
+ bwipe!
+endfunc
+
+func Test_tw_2_fo_tm_replace()
+ new
+ let t =<< trim END
+ {
+
+ }
+ END
+ call setline(1, t)
+ call cursor(2, 1)
+
+ set tw=2 fo=tm
+ exe "normal RXa"
+
+ let expected =<< trim END
+ {
+ X
+ a
+ }
+ END
+ call assert_equal(expected, getline(1, '$'))
+
+ set tw& fo&
+ bwipe!
+endfunc
+
+" Test for 'matchpairs' with multibyte chars
+func Test_mps()
+ new
+ let t =<< trim END
+ {
+ ‘ two three ’ four
+ }
+ END
+ call setline(1, t)
+ call cursor(2, 1)
+
+ exe "set mps+=\u2018:\u2019"
+ normal d%
+
+ let expected =<< trim END
+ {
+ four
+ }
+ END
+ call assert_equal(expected, getline(1, '$'))
+
+ set mps&
+ bwipe!
+endfunc
+
+" Test for ra on multi-byte characters
+func Test_ra_multibyte()
+ new
+ let t =<< trim END
+ ra test
+ ï½bbï½
+ ï½ï½b
+ END
+ call setline(1, t)
+ call cursor(1, 1)
+
+ normal jVjra
+
+ let expected =<< trim END
+ ra test
+ aaaa
+ aaa
+ END
+ call assert_equal(expected, getline(1, '$'))
+
+ bwipe!
+endfunc
+
+" Test for 'whichwrap' with multi-byte character
+func Test_whichwrap_multi_byte()
+ new
+ let t =<< trim END
+ á
+ x
+ END
+ call setline(1, t)
+ call cursor(2, 1)
+
+ set whichwrap+=h
+ normal dh
+ set whichwrap&
+
+ let expected =<< trim END
+ áx
+ END
+ call assert_equal(expected, getline(1, '$'))
+
+ bwipe!
+endfunc
+
+func Test_substitute()
+ call assert_equal('a1a2a3a', substitute('123', '\zs', 'a', 'g'))
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_textobjects.vim b/src/nvim/testdir/test_textobjects.vim
index 9194e0014d..7863317eb0 100644
--- a/src/nvim/testdir/test_textobjects.vim
+++ b/src/nvim/testdir/test_textobjects.vim
@@ -46,8 +46,18 @@ func Test_quote_selection_selection_exclusive()
new
call setline(1, "a 'bcde' f")
set selection=exclusive
+
exe "norm! fdvhi'y"
call assert_equal('bcde', @")
+
+ let @"='dummy'
+ exe "norm! $gevi'y"
+ call assert_equal('bcde', @")
+
+ let @"='dummy'
+ exe "norm! 0fbhvi'y"
+ call assert_equal('bcde', @")
+
set selection&vim
bw!
endfunc
@@ -280,5 +290,16 @@ func! Test_sentence_with_cursor_on_delimiter()
normal! 17|yas
call assert_equal("A '([sentence.])' ", @")
+ " don't get stuck on a quote at the start of a sentence
+ %delete _
+ call setline(1, ['A sentence.', '"A sentence"?', 'A sentence!'])
+ normal gg))
+ call assert_equal(3, getcurpos()[1])
+
+ %delete _
+ call setline(1, ['A sentence.', "'A sentence'?", 'A sentence!'])
+ normal gg))
+ call assert_equal(3, getcurpos()[1])
+
%delete _
endfunc
diff --git a/src/nvim/testdir/test_timers.vim b/src/nvim/testdir/test_timers.vim
index 24c735865c..cffd80ff4f 100644
--- a/src/nvim/testdir/test_timers.vim
+++ b/src/nvim/testdir/test_timers.vim
@@ -5,6 +5,7 @@ if !has('timers')
endif
source shared.vim
+source term_util.vim
source load.vim
func MyHandler(timer)
@@ -141,7 +142,7 @@ endfunc
func Test_delete_myself()
let g:called = 0
let t = timer_start(10, 'StopMyself', {'repeat': -1})
- call WaitFor('g:called == 2')
+ call WaitForAssert({-> assert_equal(2, g:called)})
call assert_equal(2, g:called)
call assert_equal([], timer_info(t))
endfunc
@@ -208,7 +209,7 @@ func Test_timer_errors()
let g:call_count = 0
let timer = timer_start(10, 'FuncWithError', {'repeat': -1})
" Timer will be stopped after failing 3 out of 3 times.
- call WaitFor('g:call_count == 3')
+ call WaitForAssert({-> assert_equal(3, g:call_count)})
sleep 50m
call assert_equal(3, g:call_count)
endfunc
@@ -226,7 +227,7 @@ func Test_timer_catch_error()
let g:call_count = 0
let timer = timer_start(10, 'FuncWithCaughtError', {'repeat': 4})
" Timer will not be stopped.
- call WaitFor('g:call_count == 4')
+ call WaitForAssert({-> assert_equal(4, g:call_count)})
sleep 50m
call assert_equal(4, g:call_count)
endfunc
@@ -252,4 +253,94 @@ func Test_peek_and_get_char()
call timer_stop(intr)
endfunc
+func Test_getchar_zero()
+ if has('win32') && !has('gui_running')
+ " Console: no low-level input
+ return
+ endif
+
+ " Measure the elapsed time to avoid a hang when it fails.
+ let start = reltime()
+ let id = timer_start(20, {-> feedkeys('x', 'L')})
+ let c = 0
+ while c == 0 && reltimefloat(reltime(start)) < 0.2
+ let c = getchar(0)
+ sleep 10m
+ endwhile
+ call assert_equal('x', nr2char(c))
+ call timer_stop(id)
+endfunc
+
+func Test_ex_mode()
+ " Function with an empty line.
+ func Foo(...)
+
+ endfunc
+ let timer = timer_start(40, function('g:Foo'), {'repeat':-1})
+ " This used to throw error E749.
+ exe "normal Qsleep 100m\rvi\r"
+ call timer_stop(timer)
+endfunc
+
+func Test_restore_count()
+ if !CanRunVimInTerminal()
+ return
+ endif
+ " Check that v:count is saved and restored, not changed by a timer.
+ call writefile([
+ \ 'nnoremap <expr><silent> L v:count ? v:count . "l" : "l"',
+ \ 'func Doit(id)',
+ \ ' normal 3j',
+ \ 'endfunc',
+ \ 'call timer_start(100, "Doit")',
+ \ ], 'Xtrcscript')
+ call writefile([
+ \ '1-1234',
+ \ '2-1234',
+ \ '3-1234',
+ \ ], 'Xtrctext')
+ let buf = RunVimInTerminal('-S Xtrcscript Xtrctext', {})
+
+ " Wait for the timer to move the cursor to the third line.
+ call WaitForAssert({-> assert_equal(3, term_getcursor(buf)[0])})
+ call assert_equal(1, term_getcursor(buf)[1])
+ " Now check that v:count has not been set to 3
+ call term_sendkeys(buf, 'L')
+ call WaitForAssert({-> assert_equal(2, term_getcursor(buf)[1])})
+
+ call StopVimInTerminal(buf)
+ call delete('Xtrcscript')
+ call delete('Xtrctext')
+endfunc
+
+" Test that the garbage collector isn't triggered if a timer callback invokes
+" vgetc().
+func Test_nocatch_garbage_collect()
+ " skipped: Nvim does not support test_garbagecollect_soon(), test_override()
+ return
+ " 'uptimetime. must be bigger than the timer timeout
+ set ut=200
+ call test_garbagecollect_soon()
+ call test_override('no_wait_return', 0)
+ func CauseAnError(id)
+ " This will show an error and wait for Enter.
+ let a = {'foo', 'bar'}
+ endfunc
+ func FeedChar(id)
+ call feedkeys('x', 't')
+ endfunc
+ call timer_start(300, 'FeedChar')
+ call timer_start(100, 'CauseAnError')
+ let x = getchar()
+
+ set ut&
+ call test_override('no_wait_return', 1)
+ delfunc CauseAnError
+ delfunc FeedChar
+endfunc
+
+func Test_timer_invalid_callback()
+ call assert_fails('call timer_start(0, "0")', 'E921')
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_undo.vim b/src/nvim/testdir/test_undo.vim
index 86674889ef..adcdcb1cd9 100644
--- a/src/nvim/testdir/test_undo.vim
+++ b/src/nvim/testdir/test_undo.vim
@@ -238,7 +238,37 @@ func Test_undojoin()
call assert_equal(['aaaa', 'bbbb', 'cccc'], getline(2, '$'))
call feedkeys("u", 'xt')
call assert_equal(['aaaa'], getline(2, '$'))
- close!
+ bwipe!
+endfunc
+
+func Test_undojoin_redo()
+ new
+ call setline(1, ['first line', 'second line'])
+ call feedkeys("ixx\<Esc>", 'xt')
+ call feedkeys(":undojoin | redo\<CR>", 'xt')
+ call assert_equal('xxfirst line', getline(1))
+ call assert_equal('second line', getline(2))
+ bwipe!
+endfunc
+
+" undojoin not allowed after undo
+func Test_undojoin_after_undo()
+ new
+ call feedkeys("ixx\<Esc>u", 'xt')
+ call assert_fails(':undojoin', 'E790:')
+ bwipe!
+endfunc
+
+" undojoin is a noop when no change yet, or when 'undolevels' is negative
+func Test_undojoin_noop()
+ new
+ call feedkeys(":undojoin\<CR>", 'xt')
+ call assert_equal([''], getline(1, '$'))
+ setlocal undolevels=-1
+ call feedkeys("ixx\<Esc>u", 'xt')
+ call feedkeys(":undojoin\<CR>", 'xt')
+ call assert_equal(['xx'], getline(1, '$'))
+ bwipe!
endfunc
func Test_undo_write()
@@ -327,6 +357,22 @@ func Test_undofile_earlier()
call delete('Xundofile')
endfunc
+func Test_wundo_errors()
+ new
+ call setline(1, 'hello')
+ call assert_fails('wundo! Xdoesnotexist/Xundofile', 'E828:')
+ bwipe!
+endfunc
+
+func Test_rundo_errors()
+ call assert_fails('rundo XfileDoesNotExist', 'E822:')
+
+ call writefile(['abc'], 'Xundofile')
+ call assert_fails('rundo Xundofile', 'E823:')
+
+ call delete('Xundofile')
+endfunc
+
" Test for undo working properly when executing commands from a register.
" Also test this in an empty buffer.
func Test_cmd_in_reg_undo()
@@ -343,6 +389,24 @@ func Test_cmd_in_reg_undo()
let @a = ''
endfunc
+" undo or redo are noop if there is nothing to undo or redo
+func Test_undo_redo_noop()
+ new
+ call assert_fails('undo 2', 'E830:')
+
+ message clear
+ undo
+ let messages = split(execute('message'), "\n")
+ call assert_equal('Already at oldest change', messages[-1])
+
+ message clear
+ redo
+ let messages = split(execute('message'), "\n")
+ call assert_equal('Already at newest change', messages[-1])
+
+ bwipe!
+endfunc
+
func Test_redo_empty_line()
new
exe "norm\x16r\x160"
@@ -443,3 +507,190 @@ func Test_undo_0()
bwipe!
endfunc
+
+" Tests for the undo file
+" Explicitly break changes up in undo-able pieces by setting 'undolevels'.
+func Test_undofile_2()
+ set undolevels=100 undofile
+ edit Xtestfile
+ call append(0, 'this is one line')
+ call cursor(1, 1)
+
+ " first a simple one-line change.
+ set undolevels=100
+ s/one/ONE/
+ set undolevels=100
+ write
+ bwipe!
+ edit Xtestfile
+ undo
+ call assert_equal('this is one line', getline(1))
+
+ " change in original file fails check
+ set noundofile
+ edit! Xtestfile
+ s/line/Line/
+ write
+ set undofile
+ bwipe!
+ edit Xtestfile
+ undo
+ call assert_equal('this is ONE Line', getline(1))
+
+ " add 10 lines, delete 6 lines, undo 3
+ set undofile
+ call setbufline(0, 1, ['one', 'two', 'three', 'four', 'five', 'six',
+ \ 'seven', 'eight', 'nine', 'ten'])
+ set undolevels=100
+ normal 3Gdd
+ set undolevels=100
+ normal dd
+ set undolevels=100
+ normal dd
+ set undolevels=100
+ normal dd
+ set undolevels=100
+ normal dd
+ set undolevels=100
+ normal dd
+ set undolevels=100
+ write
+ bwipe!
+ edit Xtestfile
+ normal uuu
+ call assert_equal(['one', 'two', 'six', 'seven', 'eight', 'nine', 'ten'],
+ \ getline(1, '$'))
+
+ " Test that reading the undofiles when setting undofile works
+ set noundofile undolevels=0
+ exe "normal i\n"
+ undo
+ edit! Xtestfile
+ set undofile undolevels=100
+ normal uuuuuu
+ call assert_equal(['one', 'two', 'three', 'four', 'five', 'six', 'seven',
+ \ 'eight', 'nine', 'ten'], getline(1, '$'))
+
+ bwipe!
+ call delete('Xtestfile')
+ let ufile = has('vms') ? '_un_Xtestfile' : '.Xtestfile.un~'
+ call delete(ufile)
+ set undofile& undolevels&
+endfunc
+
+" Test 'undofile' using a file encrypted with 'zip' crypt method
+func Test_undofile_cryptmethod_zip()
+ throw 'skipped: Nvim does not support cryptmethod'
+ edit Xtestfile
+ set undofile cryptmethod=zip
+ call append(0, ['monday', 'tuesday', 'wednesday', 'thursday', 'friday'])
+ call cursor(5, 1)
+
+ set undolevels=100
+ normal kkkdd
+ set undolevels=100
+ normal dd
+ set undolevels=100
+ normal dd
+ set undolevels=100
+ " encrypt the file using key 'foobar'
+ call feedkeys("foobar\nfoobar\n")
+ X
+ write!
+ bwipe!
+
+ call feedkeys("foobar\n")
+ edit Xtestfile
+ set key=
+ normal uu
+ call assert_equal(['monday', 'wednesday', 'thursday', 'friday', ''],
+ \ getline(1, '$'))
+
+ bwipe!
+ call delete('Xtestfile')
+ let ufile = has('vms') ? '_un_Xtestfile' : '.Xtestfile.un~'
+ call delete(ufile)
+ set undofile& undolevels& cryptmethod&
+endfunc
+
+" Test 'undofile' using a file encrypted with 'blowfish' crypt method
+func Test_undofile_cryptmethod_blowfish()
+ throw 'skipped: Nvim does not support cryptmethod'
+ edit Xtestfile
+ set undofile cryptmethod=blowfish
+ call append(0, ['jan', 'feb', 'mar', 'apr', 'jun'])
+ call cursor(5, 1)
+
+ set undolevels=100
+ exe 'normal kk0ifoo '
+ set undolevels=100
+ normal dd
+ set undolevels=100
+ exe 'normal ibar '
+ set undolevels=100
+ " encrypt the file using key 'foobar'
+ call feedkeys("foobar\nfoobar\n")
+ X
+ write!
+ bwipe!
+
+ call feedkeys("foobar\n")
+ edit Xtestfile
+ set key=
+ call search('bar')
+ call assert_equal('bar apr', getline('.'))
+ undo
+ call assert_equal('apr', getline('.'))
+ undo
+ call assert_equal('foo mar', getline('.'))
+ undo
+ call assert_equal('mar', getline('.'))
+
+ bwipe!
+ call delete('Xtestfile')
+ let ufile = has('vms') ? '_un_Xtestfile' : '.Xtestfile.un~'
+ call delete(ufile)
+ set undofile& undolevels& cryptmethod&
+endfunc
+
+" Test 'undofile' using a file encrypted with 'blowfish2' crypt method
+func Test_undofile_cryptmethod_blowfish2()
+ throw 'skipped: Nvim does not support cryptmethod'
+ edit Xtestfile
+ set undofile cryptmethod=blowfish2
+ call append(0, ['jan', 'feb', 'mar', 'apr', 'jun'])
+ call cursor(5, 1)
+
+ set undolevels=100
+ exe 'normal kk0ifoo '
+ set undolevels=100
+ normal dd
+ set undolevels=100
+ exe 'normal ibar '
+ set undolevels=100
+ " encrypt the file using key 'foo2bar'
+ call feedkeys("foo2bar\nfoo2bar\n")
+ X
+ write!
+ bwipe!
+
+ call feedkeys("foo2bar\n")
+ edit Xtestfile
+ set key=
+ call search('bar')
+ call assert_equal('bar apr', getline('.'))
+ normal u
+ call assert_equal('apr', getline('.'))
+ normal u
+ call assert_equal('foo mar', getline('.'))
+ normal u
+ call assert_equal('mar', getline('.'))
+
+ bwipe!
+ call delete('Xtestfile')
+ let ufile = has('vms') ? '_un_Xtestfile' : '.Xtestfile.un~'
+ call delete(ufile)
+ set undofile& undolevels& cryptmethod&
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_user_func.vim b/src/nvim/testdir/test_user_func.vim
index e7a3701386..67701ee3ca 100644
--- a/src/nvim/testdir/test_user_func.vim
+++ b/src/nvim/testdir/test_user_func.vim
@@ -94,3 +94,7 @@ func Test_user_func()
unlet g:retval g:counter
enew!
endfunc
+
+func Test_failed_call_in_try()
+ try | call UnknownFunc() | catch | endtry
+endfunc
diff --git a/src/nvim/testdir/test_utf8.vim b/src/nvim/testdir/test_utf8.vim
index b1f33f56dd..1b4ce4c4af 100644
--- a/src/nvim/testdir/test_utf8.vim
+++ b/src/nvim/testdir/test_utf8.vim
@@ -60,3 +60,46 @@ func Test_getvcol()
call assert_equal(2, virtcol("'["))
call assert_equal(2, virtcol("']"))
endfunc
+
+func Test_list2str_str2list_utf8()
+ " One Unicode codepoint
+ let s = "\u3042\u3044"
+ let l = [0x3042, 0x3044]
+ call assert_equal(l, str2list(s, 1))
+ call assert_equal(s, list2str(l, 1))
+ if &enc ==# 'utf-8'
+ call assert_equal(str2list(s), str2list(s, 1))
+ call assert_equal(list2str(l), list2str(l, 1))
+ endif
+
+ " With composing characters
+ let s = "\u304b\u3099\u3044"
+ let l = [0x304b, 0x3099, 0x3044]
+ call assert_equal(l, str2list(s, 1))
+ call assert_equal(s, list2str(l, 1))
+ if &enc ==# 'utf-8'
+ call assert_equal(str2list(s), str2list(s, 1))
+ call assert_equal(list2str(l), list2str(l, 1))
+ endif
+
+ " Null list is the same as an empty list
+ call assert_equal('', list2str([]))
+ " call assert_equal('', list2str(test_null_list()))
+endfunc
+
+func Test_list2str_str2list_latin1()
+ " When 'encoding' is not multi-byte can still get utf-8 string.
+ " But we need to create the utf-8 string while 'encoding' is utf-8.
+ let s = "\u3042\u3044"
+ let l = [0x3042, 0x3044]
+
+ let save_encoding = &encoding
+ " set encoding=latin1
+
+ let lres = str2list(s, 1)
+ let sres = list2str(l, 1)
+
+ let &encoding = save_encoding
+ call assert_equal(l, lres)
+ call assert_equal(s, sres)
+endfunc
diff --git a/src/nvim/testdir/test_vimscript.vim b/src/nvim/testdir/test_vimscript.vim
index f39e53d6dd..d2f13ff072 100644
--- a/src/nvim/testdir/test_vimscript.vim
+++ b/src/nvim/testdir/test_vimscript.vim
@@ -1284,7 +1284,7 @@ func s:DoNothing()
endfunc
func Test_script_local_func()
- set nocp viminfo+=nviminfo
+ set nocp nomore viminfo+=nviminfo
new
nnoremap <buffer> _x :call <SID>DoNothing()<bar>call <SID>DoLast()<bar>delfunc <SID>DoNothing<bar>delfunc <SID>DoLast<cr>
@@ -1409,6 +1409,76 @@ func Test_compound_assignment_operators()
let @/ = ''
endfunc
+func Test_function_defined_line()
+ if has('gui_running')
+ " Can't catch the output of gvim.
+ return
+ endif
+
+ let lines =<< trim [CODE]
+ " F1
+ func F1()
+ " F2
+ func F2()
+ "
+ "
+ "
+ return
+ endfunc
+ " F3
+ execute "func F3()\n\n\n\nreturn\nendfunc"
+ " F4
+ execute "func F4()\n
+ \\n
+ \\n
+ \\n
+ \return\n
+ \endfunc"
+ endfunc
+ " F5
+ execute "func F5()\n\n\n\nreturn\nendfunc"
+ " F6
+ execute "func F6()\n
+ \\n
+ \\n
+ \\n
+ \return\n
+ \endfunc"
+ call F1()
+ verbose func F1
+ verbose func F2
+ verbose func F3
+ verbose func F4
+ verbose func F5
+ verbose func F6
+ qall!
+ [CODE]
+
+ call writefile(lines, 'Xtest.vim')
+ let res = system(v:progpath .. ' --clean -es -X -S Xtest.vim')
+ call assert_equal(0, v:shell_error)
+
+ let m = matchstr(res, 'function F1()[^[:print:]]*[[:print:]]*')
+ call assert_match(' line 2$', m)
+
+ let m = matchstr(res, 'function F2()[^[:print:]]*[[:print:]]*')
+ call assert_match(' line 4$', m)
+
+ let m = matchstr(res, 'function F3()[^[:print:]]*[[:print:]]*')
+ call assert_match(' line 11$', m)
+
+ let m = matchstr(res, 'function F4()[^[:print:]]*[[:print:]]*')
+ call assert_match(' line 13$', m)
+
+ let m = matchstr(res, 'function F5()[^[:print:]]*[[:print:]]*')
+ call assert_match(' line 21$', m)
+
+ let m = matchstr(res, 'function F6()[^[:print:]]*[[:print:]]*')
+ call assert_match(' line 23$', m)
+
+ call delete('Xtest.vim')
+endfunc
+
"-------------------------------------------------------------------------------
" Modelines {{{1
" vim: ts=8 sw=4 tw=80 fdm=marker
diff --git a/src/nvim/testdir/test_virtualedit.vim b/src/nvim/testdir/test_virtualedit.vim
index 67adede8d7..8f992f7501 100644
--- a/src/nvim/testdir/test_virtualedit.vim
+++ b/src/nvim/testdir/test_virtualedit.vim
@@ -73,3 +73,147 @@ func Test_edit_CTRL_G()
bwipe!
set virtualedit=
endfunc
+
+func Test_edit_change()
+ new
+ set virtualedit=all
+ call setline(1, "\t⒌")
+ normal Cx
+ call assert_equal('x', getline(1))
+ bwipe!
+endfunc
+
+" Insert "keyword keyw", ESC, C CTRL-N, shows "keyword ykeyword".
+" Repeating CTRL-N fixes it. (Mary Ellen Foster)
+func Test_ve_completion()
+ new
+ set completeopt&vim
+ set virtualedit=all
+ exe "normal ikeyword keyw\<Esc>C\<C-N>"
+ call assert_equal('keyword keyword', getline(1))
+ bwipe!
+ set virtualedit=
+endfunc
+
+" Using "C" then then <CR> moves the last remaining character to the next
+" line. (Mary Ellen Foster)
+func Test_ve_del_to_eol()
+ new
+ set virtualedit=all
+ call append(0, 'all your base are belong to us')
+ call search('are', 'w')
+ exe "normal C\<CR>are belong to vim"
+ call assert_equal(['all your base ', 'are belong to vim'], getline(1, 2))
+ bwipe!
+ set virtualedit=
+endfunc
+
+" When past the end of a line that ends in a single character "b" skips
+" that word.
+func Test_ve_b_past_eol()
+ new
+ set virtualedit=all
+ call append(0, '1 2 3 4 5 6')
+ normal gg^$15lbC7
+ call assert_equal('1 2 3 4 5 7', getline(1))
+ bwipe!
+ set virtualedit=
+endfunc
+
+" Make sure 'i', 'C', 'a', 'A' and 'D' works
+func Test_ve_ins_del()
+ new
+ set virtualedit=all
+ call append(0, ["'i'", "'C'", "'a'", "'A'", "'D'"])
+ call cursor(1, 1)
+ normal $4lix
+ call assert_equal("'i' x", getline(1))
+ call cursor(2, 1)
+ normal $4lCx
+ call assert_equal("'C' x", getline(2))
+ call cursor(3, 1)
+ normal $4lax
+ call assert_equal("'a' x", getline(3))
+ call cursor(4, 1)
+ normal $4lAx
+ call assert_equal("'A'x", getline(4))
+ call cursor(5, 1)
+ normal $4lDix
+ call assert_equal("'D' x", getline(5))
+ bwipe!
+ set virtualedit=
+endfunc
+
+" Test for yank bug reported by Mark Waggoner.
+func Test_yank_block()
+ new
+ set virtualedit=block
+ call append(0, repeat(['this is a test'], 3))
+ exe "normal gg^2w\<C-V>3jy"
+ call assert_equal("a\na\na\n ", @")
+ bwipe!
+ set virtualedit=
+endfunc
+
+" Test "r" beyond the end of the line
+func Test_replace_after_eol()
+ new
+ set virtualedit=all
+ call append(0, '"r"')
+ normal gg$5lrxa
+ call assert_equal('"r" x', getline(1))
+ bwipe!
+ set virtualedit=
+endfunc
+
+" Test "r" on a tab
+" Note that for this test, 'ts' must be 8 (the default).
+func Test_replace_on_tab()
+ new
+ set virtualedit=all
+ call append(0, "'r'\t")
+ normal gg^5lrxAy
+ call assert_equal("'r' x y", getline(1))
+ bwipe!
+ set virtualedit=
+endfunc
+
+" Test to make sure 'x' can delete control characters
+func Test_ve_del_ctrl_chars()
+ new
+ set virtualedit=all
+ call append(0, "a\<C-V>b\<CR>sd")
+ set display=uhex
+ normal gg^xxxxxxi[text]
+ set display=
+ call assert_equal('[text]', getline(1))
+ bwipe!
+ set virtualedit=
+endfunc
+
+" Test for ^Y/^E due to bad w_virtcol value, reported by
+" Roy <royl@netropolis.net>.
+func Test_ins_copy_char()
+ new
+ set virtualedit=all
+ call append(0, 'abcv8efi.him2kl')
+ exe "normal gg^O\<Esc>3li\<C-E>\<Esc>4li\<C-E>\<Esc>4li\<C-E> <--"
+ exe "normal j^o\<Esc>4li\<C-Y>\<Esc>4li\<C-Y>\<Esc>4li\<C-Y> <--"
+ call assert_equal(' v i m <--', getline(1))
+ call assert_equal(' 8 . 2 <--', getline(3))
+ bwipe!
+ set virtualedit=
+endfunc
+
+" Test for yanking and pasting using the small delete register
+func Test_yank_paste_small_del_reg()
+ new
+ set virtualedit=all
+ call append(0, "foo, bar")
+ normal ggdewve"-p
+ call assert_equal(', foo', getline(1))
+ bwipe!
+ set virtualedit=
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_window_cmd.vim b/src/nvim/testdir/test_window_cmd.vim
index c87c0a0af4..aaa291f87d 100644
--- a/src/nvim/testdir/test_window_cmd.vim
+++ b/src/nvim/testdir/test_window_cmd.vim
@@ -69,18 +69,6 @@ function Test_window_cmd_wincmd_gf()
augroup! test_window_cmd_wincmd_gf
endfunc
-func Test_next_split_all()
- " This was causing an illegal memory access.
- n x
- norm axxx
- split
- split
- s/x
- s/x
- all
- bwipe!
-endfunc
-
func Test_window_quit()
e Xa
split Xb
@@ -117,15 +105,71 @@ func Test_window_vertical_split()
bw
endfunc
+" Test the ":wincmd ^" and "<C-W>^" commands.
func Test_window_split_edit_alternate()
- e Xa
- e Xb
+ " Test for failure when the alternate buffer/file no longer exists.
+ edit Xfoo | %bw
+ call assert_fails(':wincmd ^', 'E23')
+
+ " Test for the expected behavior when we have two named buffers.
+ edit Xfoo | edit Xbar
wincmd ^
- call assert_equal('Xa', bufname(winbufnr(1)))
- call assert_equal('Xb', bufname(winbufnr(2)))
+ call assert_equal('Xfoo', bufname(winbufnr(1)))
+ call assert_equal('Xbar', bufname(winbufnr(2)))
+ only
- bw Xa Xb
+ " Test for the expected behavior when the alternate buffer is not named.
+ enew | let l:nr1 = bufnr('%')
+ edit Xfoo | let l:nr2 = bufnr('%')
+ wincmd ^
+ call assert_equal(l:nr1, winbufnr(1))
+ call assert_equal(l:nr2, winbufnr(2))
+ only
+
+ " FIXME: this currently fails on AppVeyor, but passes locally
+ if !has('win32')
+ " Test the Normal mode command.
+ call feedkeys("\<C-W>\<C-^>", 'tx')
+ call assert_equal(l:nr2, winbufnr(1))
+ call assert_equal(l:nr1, winbufnr(2))
+ endif
+
+ %bw!
+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')
+
+ edit Xfoo | edit Xbar | edit Xbaz
+ let l:foo_nr = bufnr('Xfoo')
+ let l:bar_nr = bufnr('Xbar')
+ let l:baz_nr = bufnr('Xbaz')
+
+ " FIXME: this currently fails on AppVeyor, but passes locally
+ if !has('win32')
+ call feedkeys(l:foo_nr . "\<C-W>\<C-^>", 'tx')
+ call assert_equal('Xfoo', bufname(winbufnr(1)))
+ call assert_equal('Xbaz', bufname(winbufnr(2)))
+ only
+
+ call feedkeys(l:bar_nr . "\<C-W>\<C-^>", 'tx')
+ call assert_equal('Xbar', bufname(winbufnr(1)))
+ call assert_equal('Xfoo', bufname(winbufnr(2)))
+ only
+
+ execute l:baz_nr . 'wincmd ^'
+ call assert_equal('Xbaz', bufname(winbufnr(1)))
+ call assert_equal('Xbar', bufname(winbufnr(2)))
+ endif
+
+ %bw!
endfunc
func Test_window_preview()
@@ -446,6 +490,17 @@ func Test_window_newtab()
%bw!
endfunc
+func Test_next_split_all()
+ " This was causing an illegal memory access.
+ n x
+ norm axxx
+ split
+ split
+ s/x
+ s/x
+ all
+ bwipe!
+endfunc
" Tests for adjusting window and contents
func GetScreenStr(row)
@@ -485,6 +540,11 @@ func Test_window_contents()
call test_garbagecollect_now()
endfunc
+func Test_window_colon_command()
+ " This was reading invalid memory.
+ exe "norm! v\<C-W>:\<C-U>echo v:version"
+endfunc
+
func Test_access_freed_mem()
" This was accessing freed memory
au * 0 vs xxx
@@ -700,6 +760,42 @@ func Test_relative_cursor_second_line_after_resize()
let &so = so_save
endfunc
+func Test_split_noscroll()
+ let so_save = &so
+ enew
+ call setline(1, range(1, 8))
+ normal 100%
+ split
+
+ 1wincmd w
+ let winid1 = win_getid()
+ let info1 = getwininfo(winid1)[0]
+
+ 2wincmd w
+ let winid2 = win_getid()
+ let info2 = getwininfo(winid2)[0]
+
+ call assert_equal(1, info1.topline)
+ call assert_equal(1, info2.topline)
+
+ " window that fits all lines by itself, but not when split: closing other
+ " window should restore fraction.
+ only!
+ call setline(1, range(1, &lines - 10))
+ exe &lines / 4
+ let winid1 = win_getid()
+ let info1 = getwininfo(winid1)[0]
+ call assert_equal(1, info1.topline)
+ new
+ redraw
+ close
+ let info1 = getwininfo(winid1)[0]
+ call assert_equal(1, info1.topline)
+
+ bwipe!
+ let &so = so_save
+endfunc
+
" Tests for the winnr() function
func Test_winnr()
only | tabonly
@@ -745,9 +841,4 @@ func Test_winnr()
only | tabonly
endfunc
-func Test_window_colon_command()
- " This was reading invalid memory.
- exe "norm! v\<C-W>:\<C-U>echo v:version"
-endfunc
-
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_writefile.vim b/src/nvim/testdir/test_writefile.vim
index 6066d61af4..56031662a3 100644
--- a/src/nvim/testdir/test_writefile.vim
+++ b/src/nvim/testdir/test_writefile.vim
@@ -1,4 +1,4 @@
-" Tests for the writefile() function.
+" Tests for the writefile() function and some :write commands.
func Test_writefile()
let f = tempname()
@@ -16,6 +16,11 @@ func Test_writefile()
call delete(f)
endfunc
+func Test_writefile_ignore_regexp_error()
+ write Xt[z-a]est.txt
+ call delete('Xt[z-a]est.txt')
+endfunc
+
func Test_writefile_fails_gently()
call assert_fails('call writefile(["test"], "Xfile", [])', 'E730:')
call assert_false(filereadable("Xfile"))
diff --git a/src/nvim/testdir/view_util.vim b/src/nvim/testdir/view_util.vim
index 29ea073f97..1def201a05 100644
--- a/src/nvim/testdir/view_util.vim
+++ b/src/nvim/testdir/view_util.vim
@@ -1,10 +1,21 @@
" Functions about view shared by several tests
" Only load this script once.
-if exists('*ScreenLines')
+if exists('*Screenline')
finish
endif
+" Get line "lnum" as displayed on the screen.
+" Trailing white space is trimmed.
+func Screenline(lnum)
+ let chars = []
+ for c in range(1, winwidth(0))
+ call add(chars, nr2char(screenchar(a:lnum, c)))
+ endfor
+ let line = join(chars, '')
+ return matchstr(line, '^.\{-}\ze\s*$')
+endfunc
+
" ScreenLines(lnum, width) or
" ScreenLines([start, end], width)
function! ScreenLines(lnum, width) abort
@@ -42,6 +53,7 @@ endfunction
function! NewWindow(height, width) abort
exe a:height . 'new'
exe a:width . 'vsp'
+ set winfixwidth winfixheight
redraw!
endfunction
diff --git a/src/nvim/tui/input.c b/src/nvim/tui/input.c
index ed9b410a19..bbee7e4712 100644
--- a/src/nvim/tui/input.c
+++ b/src/nvim/tui/input.c
@@ -14,6 +14,9 @@
#include "nvim/option.h"
#include "nvim/os/os.h"
#include "nvim/os/input.h"
+#ifdef WIN32
+# include "nvim/os/os_win_console.h"
+#endif
#include "nvim/event/rstream.h"
#define KEY_BUFFER_SIZE 0xfff
@@ -26,7 +29,12 @@ void tinput_init(TermInput *input, Loop *loop)
{
input->loop = loop;
input->paste = 0;
- input->in_fd = 0;
+ input->in_fd = STDIN_FILENO;
+ input->waiting_for_bg_response = 0;
+ // The main thread is waiting for the UI thread to call CONTINUE, so it can
+ // safely access global variables.
+ input->ttimeout = (bool)p_ttimeout;
+ input->ttimeoutlen = p_ttm;
input->key_buffer = rbuffer_new(KEY_BUFFER_SIZE);
uv_mutex_init(&input->key_buffer_mutex);
uv_cond_init(&input->key_buffer_cond);
@@ -35,18 +43,12 @@ void tinput_init(TermInput *input, Loop *loop)
// echo q | nvim -es
// ls *.md | xargs nvim
#ifdef WIN32
- if (!os_isatty(0)) {
- const HANDLE conin_handle = CreateFile("CONIN$",
- GENERIC_READ | GENERIC_WRITE,
- FILE_SHARE_READ | FILE_SHARE_WRITE,
- (LPSECURITY_ATTRIBUTES)NULL,
- OPEN_EXISTING, 0, (HANDLE)NULL);
- input->in_fd = _open_osfhandle(conin_handle, _O_RDONLY);
- assert(input->in_fd != -1);
+ if (!os_isatty(input->in_fd)) {
+ input->in_fd = os_get_conin_fd();
}
#else
- if (!os_isatty(0) && os_isatty(2)) {
- input->in_fd = 2;
+ if (!os_isatty(input->in_fd) && os_isatty(STDERR_FILENO)) {
+ input->in_fd = STDERR_FILENO;
}
#endif
input_global_fd_init(input->in_fd);
@@ -287,21 +289,6 @@ static TermKeyResult tk_getkey(TermKey *tk, TermKeyKey *key, bool force)
static void tinput_timer_cb(TimeWatcher *watcher, void *data);
-static int get_key_code_timeout(void)
-{
- Integer ms = -1;
- // Check 'ttimeout' to determine if we should send ESC after 'ttimeoutlen'.
- Error err = ERROR_INIT;
- if (nvim_get_option(cstr_as_string("ttimeout"), &err).data.boolean) {
- Object rv = nvim_get_option(cstr_as_string("ttimeoutlen"), &err);
- if (!ERROR_SET(&err)) {
- ms = rv.data.integer;
- }
- }
- api_clear_error(&err);
- return (int)ms;
-}
-
static void tk_getkeys(TermInput *input, bool force)
{
TermKeyKey key;
@@ -326,12 +313,11 @@ static void tk_getkeys(TermInput *input, bool force)
// yet contain all the bytes required. `key` structure indicates what
// termkey_getkey_force() would return.
- int ms = get_key_code_timeout();
-
- if (ms > 0) {
+ if (input->ttimeout && input->ttimeoutlen >= 0) {
// Stop the current timer if already running
time_watcher_stop(&input->timer_handle);
- time_watcher_start(&input->timer_handle, tinput_timer_cb, (uint32_t)ms, 0);
+ time_watcher_start(&input->timer_handle, tinput_timer_cb,
+ (uint64_t)input->ttimeoutlen, 0);
} else {
tk_getkeys(input, true);
}
@@ -443,6 +429,9 @@ static void set_bg_deferred(void **argv)
// [1] https://en.wikipedia.org/wiki/Luma_%28video%29
static bool handle_background_color(TermInput *input)
{
+ if (input->waiting_for_bg_response <= 0) {
+ return false;
+ }
size_t count = 0;
size_t component = 0;
size_t header_size = 0;
@@ -461,8 +450,13 @@ static bool handle_background_color(TermInput *input)
header_size = 10;
num_components = 4;
} else {
+ input->waiting_for_bg_response--;
+ if (input->waiting_for_bg_response == 0) {
+ DLOG("did not get a response for terminal background query");
+ }
return false;
}
+ input->waiting_for_bg_response = 0;
rbuffer_consumed(input->read_stream.buffer, header_size);
RBUFFER_EACH(input->read_stream.buffer, c, i) {
count = i + 1;
@@ -503,6 +497,12 @@ static bool handle_background_color(TermInput *input)
}
return true;
}
+#ifdef UNIT_TESTING
+bool ut_handle_background_color(TermInput *input)
+{
+ return handle_background_color(input);
+}
+#endif
static void tinput_read_cb(Stream *stream, RBuffer *buf, size_t count_,
void *data, bool eof)
diff --git a/src/nvim/tui/input.h b/src/nvim/tui/input.h
index a4071fab40..b30546c815 100644
--- a/src/nvim/tui/input.h
+++ b/src/nvim/tui/input.h
@@ -12,6 +12,9 @@ typedef struct term_input {
// Phases: -1=all 0=disabled 1=first-chunk 2=continue 3=last-chunk
int8_t paste;
bool waiting;
+ bool ttimeout;
+ int8_t waiting_for_bg_response;
+ long ttimeoutlen;
TermKey *tk;
#if TERMKEY_VERSION_MAJOR > 0 || TERMKEY_VERSION_MINOR > 18
TermKey_Terminfo_Getstr_Hook *tk_ti_hook_fn; ///< libtermkey terminfo hook
@@ -28,4 +31,8 @@ typedef struct term_input {
# include "tui/input.h.generated.h"
#endif
+#ifdef UNIT_TESTING
+bool ut_handle_background_color(TermInput *input);
+#endif
+
#endif // NVIM_TUI_INPUT_H
diff --git a/src/nvim/tui/terminfo.c b/src/nvim/tui/terminfo.c
index 14023ce2cb..ff2a357752 100644
--- a/src/nvim/tui/terminfo.c
+++ b/src/nvim/tui/terminfo.c
@@ -31,7 +31,10 @@ bool terminfo_is_term_family(const char *term, const char *family)
return tlen >= flen
&& 0 == memcmp(term, family, flen)
// Per commentary in terminfo, minus is the only valid suffix separator.
- && ('\0' == term[flen] || '-' == term[flen]);
+ // The screen terminfo may have a terminal name like screen.xterm. By making
+ // the dot(.) a valid separator, such terminal names will also be the
+ // terminal family of the screen.
+ && ('\0' == term[flen] || '-' == term[flen] || '.' == term[flen]);
}
bool terminfo_is_bsd_console(const char *term)
@@ -47,9 +50,8 @@ bool terminfo_is_bsd_console(const char *term)
// like cursor-shaping. Assume that TERM=xterm is degraded. #8644
return strequal(term, "xterm") && !!os_getenv("XTERM_VERSION");
# endif
-#else
- return false;
#endif
+ return false;
}
/// Loads a built-in terminfo db when we (unibilium) failed to load a terminfo
@@ -188,7 +190,7 @@ void terminfo_info_msg(const unibi_term *const ut)
msg_printf_attr(0, " %-25s %-10s = ", unibi_name_str(i),
unibi_short_name_str(i));
// Most of these strings will contain escape sequences.
- msg_outtrans_special((char_u *)s, false);
+ msg_outtrans_special((char_u *)s, false, 0);
msg_putchar('\n');
}
}
@@ -215,7 +217,7 @@ void terminfo_info_msg(const unibi_term *const ut)
msg_puts("Extended string capabilities:\n");
for (size_t i = 0; i < unibi_count_ext_str(ut); i++) {
msg_printf_attr(0, " %-25s = ", unibi_get_ext_str_name(ut, i));
- msg_outtrans_special((char_u *)unibi_get_ext_str(ut, i), false);
+ msg_outtrans_special((char_u *)unibi_get_ext_str(ut, i), false, 0);
msg_putchar('\n');
}
}
diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c
index 791756e5c5..bfd9435c49 100644
--- a/src/nvim/tui/tui.c
+++ b/src/nvim/tui/tui.c
@@ -31,7 +31,11 @@
#include "nvim/event/signal.h"
#include "nvim/os/input.h"
#include "nvim/os/os.h"
+#include "nvim/os/signal.h"
#include "nvim/os/tty.h"
+#ifdef WIN32
+# include "nvim/os/os_win_console.h"
+#endif
#include "nvim/strings.h"
#include "nvim/syntax.h"
#include "nvim/ui_bridge.h"
@@ -48,10 +52,15 @@
#define OUTBUF_SIZE 0xffff
#define TOO_MANY_EVENTS 1000000
-#define STARTS_WITH(str, prefix) (strlen(str) >= (sizeof(prefix) - 1) \
- && 0 == memcmp((str), (prefix), sizeof(prefix) - 1))
-#define TMUX_WRAP(is_tmux, seq) ((is_tmux) \
- ? "\x1bPtmux;\x1b" seq "\x1b\\" : seq)
+#define STARTS_WITH(str, prefix) \
+ (strlen(str) >= (sizeof(prefix) - 1) && 0 == memcmp((str), (prefix), \
+ sizeof(prefix) - 1))
+#define SCREEN_WRAP(is_screen, seq) ((is_screen) \
+ ? DCS_STR seq STERM_STR : seq)
+#define SCREEN_TMUX_WRAP(is_screen, is_tmux, seq) \
+ ((is_screen) \
+ ? DCS_STR seq STERM_STR : (is_tmux) \
+ ? DCS_STR "tmux;\x1b" seq STERM_STR : seq)
#define LINUXSET0C "\x1b[?0c"
#define LINUXSET1C "\x1b[?1c"
@@ -93,14 +102,14 @@ typedef struct {
int out_fd;
bool scroll_region_is_full_screen;
bool can_change_scroll_region;
- bool can_set_lr_margin;
+ bool can_set_lr_margin; // smglr
bool can_set_left_right_margin;
bool can_scroll;
bool can_erase_chars;
bool immediate_wrap_after_last_column;
bool bce;
bool mouse_enabled;
- bool busy, is_invisible;
+ bool busy, is_invisible, want_invisible;
bool cork, overflow;
bool cursor_color_changed;
bool is_starting;
@@ -198,6 +207,7 @@ static void terminfo_start(UI *ui)
data->default_attr = false;
data->can_clear_attr = false;
data->is_invisible = true;
+ data->want_invisible = false;
data->busy = false;
data->cork = false;
data->overflow = false;
@@ -220,7 +230,7 @@ static void terminfo_start(UI *ui)
data->unibi_ext.reset_cursor_style = -1;
data->unibi_ext.get_bg = -1;
data->unibi_ext.set_underline_color = -1;
- data->out_fd = 1;
+ data->out_fd = STDOUT_FILENO;
data->out_isatty = os_isatty(data->out_fd);
const char *term = os_getenv("TERM");
@@ -234,7 +244,9 @@ static void terminfo_start(UI *ui)
// Set up unibilium/terminfo.
char *termname = NULL;
if (term) {
+ os_env_var_lock();
data->ut = unibi_from_term(term);
+ os_env_var_unlock();
if (data->ut) {
termname = xstrdup(term);
}
@@ -262,7 +274,7 @@ static void terminfo_start(UI *ui)
: (konsole ? 1 : 0);
patch_terminfo_bugs(data, term, colorterm, vtev, konsolev, iterm_env, nsterm);
- augment_terminfo(data, term, colorterm, vtev, konsolev, iterm_env, nsterm);
+ augment_terminfo(data, term, vtev, konsolev, iterm_env, nsterm);
data->can_change_scroll_region =
!!unibi_get_str(data->ut, unibi_change_scroll_region);
data->can_set_lr_margin =
@@ -296,6 +308,7 @@ static void terminfo_start(UI *ui)
unibi_out(ui, unibi_keypad_xmit);
unibi_out(ui, unibi_clear_screen);
// Ask the terminal to send us the background color.
+ data->input.waiting_for_bg_response = 5;
unibi_out_ext(ui, data->unibi_ext.get_bg);
// Enable bracketed paste
unibi_out_ext(ui, data->unibi_ext.enable_bracketed_paste);
@@ -312,6 +325,7 @@ static void terminfo_start(UI *ui)
uv_pipe_init(&data->write_loop, &data->output_handle.pipe, 0);
uv_pipe_open(&data->output_handle.pipe, data->out_fd);
}
+ flush_buf(ui);
}
static void terminfo_stop(UI *ui)
@@ -363,6 +377,7 @@ static void tui_terminal_after_startup(UI *ui)
// Emit this after Nvim startup, not during. This works around a tmux
// 2.3 bug(?) which caused slow drawing during startup. #7649
unibi_out_ext(ui, data->unibi_ext.enable_focus_reporting);
+ flush_buf(ui);
}
static void tui_terminal_stop(UI *ui)
@@ -430,9 +445,6 @@ static void tui_main(UIBridgeData *bridge, UI *ui)
}
if (!tui_is_stopped(ui)) {
tui_terminal_after_startup(ui);
- // Tickle `main_loop` with a dummy event, else the initial "focus-gained"
- // terminal response may not get processed until user hits a key.
- loop_schedule_deferred(&main_loop, event_create(loop_dummy_event, 0));
}
// "Passive" (I/O-driven) loop: TUI thread "main loop".
while (!tui_is_stopped(ui)) {
@@ -513,20 +525,8 @@ static void update_attrs(UI *ui, int attr_id)
}
data->print_attr_id = attr_id;
HlAttrs attrs = kv_A(data->attrs, (size_t)attr_id);
-
- int fg = ui->rgb ? attrs.rgb_fg_color : (attrs.cterm_fg_color - 1);
- if (fg == -1) {
- fg = ui->rgb ? data->clear_attrs.rgb_fg_color
- : (data->clear_attrs.cterm_fg_color - 1);
- }
-
- int bg = ui->rgb ? attrs.rgb_bg_color : (attrs.cterm_bg_color - 1);
- if (bg == -1) {
- bg = ui->rgb ? data->clear_attrs.rgb_bg_color
- : (data->clear_attrs.cterm_bg_color - 1);
- }
-
int attr = ui->rgb ? attrs.rgb_ae_attr : attrs.cterm_ae_attr;
+
bool bold = attr & HL_BOLD;
bool italic = attr & HL_ITALIC;
bool reverse = attr & HL_INVERSE;
@@ -594,14 +594,29 @@ static void update_attrs(UI *ui, int attr_id)
unibi_out_ext(ui, data->unibi_ext.set_underline_color);
}
}
- if (ui->rgb) {
+
+ int fg, bg;
+ if (ui->rgb && !(attr & HL_FG_INDEXED)) {
+ fg = ((attrs.rgb_fg_color != -1)
+ ? attrs.rgb_fg_color : data->clear_attrs.rgb_fg_color);
if (fg != -1) {
UNIBI_SET_NUM_VAR(data->params[0], (fg >> 16) & 0xff); // red
UNIBI_SET_NUM_VAR(data->params[1], (fg >> 8) & 0xff); // green
UNIBI_SET_NUM_VAR(data->params[2], fg & 0xff); // blue
unibi_out_ext(ui, data->unibi_ext.set_rgb_foreground);
}
+ } else {
+ fg = (attrs.cterm_fg_color
+ ? attrs.cterm_fg_color - 1 : (data->clear_attrs.cterm_fg_color - 1));
+ if (fg != -1) {
+ UNIBI_SET_NUM_VAR(data->params[0], fg);
+ unibi_out(ui, unibi_set_a_foreground);
+ }
+ }
+ if (ui->rgb && !(attr & HL_BG_INDEXED)) {
+ bg = ((attrs.rgb_bg_color != -1)
+ ? attrs.rgb_bg_color : data->clear_attrs.rgb_bg_color);
if (bg != -1) {
UNIBI_SET_NUM_VAR(data->params[0], (bg >> 16) & 0xff); // red
UNIBI_SET_NUM_VAR(data->params[1], (bg >> 8) & 0xff); // green
@@ -609,17 +624,15 @@ static void update_attrs(UI *ui, int attr_id)
unibi_out_ext(ui, data->unibi_ext.set_rgb_background);
}
} else {
- if (fg != -1) {
- UNIBI_SET_NUM_VAR(data->params[0], fg);
- unibi_out(ui, unibi_set_a_foreground);
- }
-
+ bg = (attrs.cterm_bg_color
+ ? attrs.cterm_bg_color - 1 : (data->clear_attrs.cterm_bg_color - 1));
if (bg != -1) {
UNIBI_SET_NUM_VAR(data->params[0], bg);
unibi_out(ui, unibi_set_a_background);
}
}
+
data->default_attr = fg == -1 && bg == -1
&& !bold && !italic && !underline && !undercurl && !reverse && !standout
&& !strikethrough;
@@ -1005,8 +1018,22 @@ static void tui_mouse_on(UI *ui)
{
TUIData *data = ui->data;
if (!data->mouse_enabled) {
+#ifdef WIN32
+ // Windows versions with vtp(ENABLE_VIRTUAL_TERMINAL_PROCESSING) and
+ // no vti(ENABLE_VIRTUAL_TERMINAL_INPUT) will need to use mouse traking of
+ // libuv. For this reason, vtp (vterm) state of libuv is temporarily
+ // disabled because the control sequence needs to be processed by libuv
+ // instead of Windows vtp.
+ // ref. https://docs.microsoft.com/en-us/windows/console/setconsolemode
+ flush_buf(ui);
+ os_set_vtp(false);
+#endif
unibi_out_ext(ui, data->unibi_ext.enable_mouse);
data->mouse_enabled = true;
+#ifdef WIN32
+ flush_buf(ui);
+ os_set_vtp(true);
+#endif
}
}
@@ -1014,8 +1041,22 @@ static void tui_mouse_off(UI *ui)
{
TUIData *data = ui->data;
if (data->mouse_enabled) {
+#ifdef WIN32
+ // Windows versions with vtp(ENABLE_VIRTUAL_TERMINAL_PROCESSING) and
+ // no vti(ENABLE_VIRTUAL_TERMINAL_INPUT) will need to use mouse traking of
+ // libuv. For this reason, vtp (vterm) state of libuv is temporarily
+ // disabled because the control sequence needs to be processed by libuv
+ // instead of Windows vtp.
+ // ref. https://docs.microsoft.com/en-us/windows/console/setconsolemode
+ flush_buf(ui);
+ os_set_vtp(false);
+#endif
unibi_out_ext(ui, data->unibi_ext.disable_mouse);
data->mouse_enabled = false;
+#ifdef WIN32
+ flush_buf(ui);
+ os_set_vtp(true);
+#endif
}
}
@@ -1029,7 +1070,11 @@ static void tui_set_mode(UI *ui, ModeShape mode)
if (c.id != 0 && c.id < (int)kv_size(data->attrs) && ui->rgb) {
HlAttrs aep = kv_A(data->attrs, c.id);
- if (aep.rgb_ae_attr & HL_INVERSE) {
+
+ data->want_invisible = aep.hl_blend == 100;
+ if (data->want_invisible) {
+ unibi_out(ui, unibi_cursor_invisible);
+ } else if (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(ui, data->unibi_ext.reset_cursor_color);
@@ -1065,9 +1110,8 @@ static void tui_mode_change(UI *ui, String mode, Integer mode_idx)
static void tui_grid_scroll(UI *ui, Integer g, Integer startrow, Integer endrow,
Integer startcol, Integer endcol,
- Integer rows, Integer cols)
+ Integer rows, Integer cols FUNC_ATTR_UNUSED)
{
- (void)cols; // unused
TUIData *data = ui->data;
UGrid *grid = &data->grid;
int top = (int)startrow, bot = (int)endrow-1;
@@ -1092,6 +1136,7 @@ static void tui_grid_scroll(UI *ui, Integer g, Integer startrow, Integer endrow,
set_scroll_region(ui, top, bot, left, right);
}
cursor_goto(ui, top, left);
+ update_attrs(ui, 0);
if (rows > 0) {
if (rows == 1) {
@@ -1231,7 +1276,9 @@ static void suspend_event(void **argv)
tui_terminal_stop(ui);
data->cont_received = false;
stream_set_blocking(input_global_fd(), true); // normalize stream (#2598)
+ signal_stop();
kill(0, SIGTSTP);
+ signal_start();
while (!data->cont_received) {
// poll the event loop until SIGCONT is received
loop_poll_events(data->loop, -1);
@@ -1284,6 +1331,12 @@ static void tui_option_set(UI *ui, String name, Object value)
data->print_attr_id = -1;
invalidate(ui, 0, data->grid.height, 0, data->grid.width);
}
+ if (strequal(name.data, "ttimeout")) {
+ data->input.ttimeout = value.data.boolean;
+ }
+ if (strequal(name.data, "ttimeoutlen")) {
+ data->input.ttimeoutlen = (long)value.data.integer;
+ }
}
static void tui_raw_line(UI *ui, Integer g, Integer linerow, Integer startcol,
@@ -1538,6 +1591,10 @@ static void patch_terminfo_bugs(TUIData *data, const char *term,
bool mate_pretending_xterm = xterm && colorterm
&& strstr(colorterm, "mate-terminal");
bool true_xterm = xterm && !!xterm_version && !bsdvt;
+ bool true_screen = screen && !os_getenv("TMUX");
+ bool screen_host_linuxvt =
+ terminfo_is_term_family(true_screen && term[6] == '.'
+ ? term + 7 : NULL, "linux");
bool cygwin = terminfo_is_term_family(term, "cygwin");
char *fix_normal = (char *)unibi_get_str(ut, unibi_cursor_normal);
@@ -1598,6 +1655,12 @@ static void patch_terminfo_bugs(TUIData *data, const char *term,
unibi_set_if_empty(ut, unibi_set_lr_margin, "\x1b[%i%p1%d;%p2%ds");
unibi_set_if_empty(ut, unibi_set_left_margin_parm, "\x1b[%i%p1%ds");
unibi_set_if_empty(ut, unibi_set_right_margin_parm, "\x1b[%i;%p2%ds");
+ } else {
+ // Fix things advertised via TERM=xterm, for non-xterm.
+ if (unibi_get_str(ut, unibi_set_lr_margin)) {
+ ILOG("Disabling smglr with TERM=xterm for non-xterm.");
+ unibi_set_str(ut, unibi_set_lr_margin, NULL);
+ }
}
#ifdef WIN32
@@ -1617,6 +1680,11 @@ static void patch_terminfo_bugs(TUIData *data, const char *term,
// per the screen manual; 2017-04 terminfo.src lacks these.
unibi_set_if_empty(ut, unibi_to_status_line, "\x1b_");
unibi_set_if_empty(ut, unibi_from_status_line, "\x1b\\");
+ // Fix an issue where smglr is inherited by TERM=screen.xterm.
+ if (unibi_get_str(ut, unibi_set_lr_margin)) {
+ ILOG("Disabling smglr with TERM=screen.xterm for screen.");
+ unibi_set_str(ut, unibi_set_lr_margin, NULL);
+ }
} else if (tmux) {
unibi_set_if_empty(ut, unibi_to_status_line, "\x1b_");
unibi_set_if_empty(ut, unibi_from_status_line, "\x1b\\");
@@ -1669,8 +1737,10 @@ static void patch_terminfo_bugs(TUIData *data, const char *term,
#define XTERM_SETAB_16 \
"\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e39%;m"
- data->unibi_ext.get_bg = (int)unibi_add_ext_str(ut, "ext.get_bg",
- "\x1b]11;?\x07");
+ data->unibi_ext.get_bg =
+ (int)unibi_add_ext_str(ut, "ext.get_bg",
+ SCREEN_TMUX_WRAP(true_screen,
+ tmux, "\x1b]11;?\x07"));
// Terminals with 256-colour SGR support despite what terminfo says.
if (unibi_get_num(ut, unibi_max_colors) < 256) {
@@ -1705,6 +1775,32 @@ static void patch_terminfo_bugs(TUIData *data, const char *term,
data->unibi_ext.set_cursor_style = unibi_find_ext_str(ut, "Ss");
}
+ // GNU Screen does not have Ss/Se. When terminfo has Ss/Se, it is wrapped with
+ // DCS because it is inherited from the host terminal.
+ if (true_screen) {
+ size_t len;
+ size_t dcs_st_len = strlen(DCS_STR) + strlen(STERM_STR);
+ if (-1 != data->unibi_ext.set_cursor_style) {
+ const char *orig_ss =
+ unibi_get_ext_str(data->ut, (size_t)data->unibi_ext.reset_cursor_style);
+ len = STRLEN(orig_ss) + dcs_st_len + 1;
+ char *ss = xmalloc(len);
+ snprintf(ss, len, "%s%s%s", DCS_STR, orig_ss, STERM_STR);
+ unibi_set_ext_str(data->ut, (size_t)data->unibi_ext.set_cursor_style, ss);
+ xfree(ss);
+ }
+ if (-1 != data->unibi_ext.reset_cursor_style) {
+ const char *orig_se =
+ unibi_get_ext_str(data->ut, (size_t)data->unibi_ext.reset_cursor_style);
+ len = strlen(orig_se) + dcs_st_len + 1;
+ char *se = xmalloc(len);
+ snprintf(se, len, "%s%s%s", DCS_STR, orig_se, STERM_STR);
+ unibi_set_ext_str(data->ut,
+ (size_t)data->unibi_ext.reset_cursor_style, se);
+ xfree(se);
+ }
+ }
+
// Dickey ncurses terminfo includes Ss/Se capabilities since 2011-07-14. So
// adding them to terminal types, that have such control sequences but lack
// the correct terminfo entries, is a fixup, not an augmentation.
@@ -1720,7 +1816,12 @@ static void patch_terminfo_bugs(TUIData *data, const char *term,
|| (konsolev >= 180770) // #9364
|| tmux // per tmux manual page
// https://lists.gnu.org/archive/html/screen-devel/2013-03/msg00000.html
- || screen
+ || (true_screen
+ && (!screen_host_linuxvt
+ || (screen_host_linuxvt
+ && (xterm_version || (vte_version > 0) || colorterm))))
+ // Since GNU Screen does not support DECSCUSR, DECSCUSR is wrapped
+ // in DCS and output to the host terminal.
|| st // #7641
|| rxvt // per command.C
// per analysis of VT100Terminal.m
@@ -1733,58 +1834,72 @@ static void patch_terminfo_bugs(TUIData *data, const char *term,
|| (linuxvt
&& (xterm_version || (vte_version > 0) || colorterm)))) {
data->unibi_ext.set_cursor_style =
- (int)unibi_add_ext_str(ut, "Ss", "\x1b[%p1%d q");
+ (int)unibi_add_ext_str(ut, "Ss",
+ SCREEN_WRAP(true_screen, "\x1b[%p1%d q"));
if (-1 == data->unibi_ext.reset_cursor_style) {
data->unibi_ext.reset_cursor_style = (int)unibi_add_ext_str(ut, "Se",
"");
}
unibi_set_ext_str(ut, (size_t)data->unibi_ext.reset_cursor_style,
- "\x1b[ q");
- } else if (linuxvt) {
+ SCREEN_WRAP(true_screen, "\x1b[ q"));
+ } else if (linuxvt || screen_host_linuxvt) {
// Linux uses an idiosyncratic escape code to set the cursor shape and
// does not support DECSCUSR.
// See http://linuxgazette.net/137/anonymous.html for more info
- data->unibi_ext.set_cursor_style = (int)unibi_add_ext_str(ut, "Ss",
- "\x1b[?"
- "%?"
- // The parameter passed to Ss is the DECSCUSR parameter, so the
- // terminal capability has to translate into the Linux idiosyncratic
- // parameter.
- //
- // linuxvt only supports block and underline. It is also only
- // possible to have a steady block (no steady underline)
- "%p1%{2}%<" "%t%{8}" // blink block
- "%e%p1%{2}%=" "%t%{112}" // steady block
- "%e%p1%{3}%=" "%t%{4}" // blink underline (set to half block)
- "%e%p1%{4}%=" "%t%{4}" // steady underline
- "%e%p1%{5}%=" "%t%{2}" // blink bar (set to underline)
- "%e%p1%{6}%=" "%t%{2}" // steady bar
- "%e%{0}" // anything else
- "%;" "%dc");
+ //
+ // Since gnu Screen does not have Ss/Se, if the host terminal is a linux
+ // console that does not support xterm extensions, it will wraps the
+ // linux-specific sequence in DCS and outputs it.
+ data->unibi_ext.set_cursor_style = (int)unibi_add_ext_str(
+ ut, "Ss",
+ SCREEN_WRAP(true_screen,
+ "\x1b[?"
+ "%?"
+ // The parameter passed to Ss is the DECSCUSR parameter,
+ // so the
+ // terminal capability has to translate into the Linux
+ // idiosyncratic parameter.
+ //
+ // linuxvt only supports block and underline. It is also
+ // only possible to have a steady block (no steady
+ // underline)
+ "%p1%{2}%<" "%t%{8}" // blink block
+ "%e%p1%{2}%=" "%t%{112}" // steady block
+ "%e%p1%{3}%=" "%t%{4}" // blink underline (set to half
+ // block)
+ "%e%p1%{4}%=" "%t%{4}" // steady underline
+ "%e%p1%{5}%=" "%t%{2}" // blink bar (set to underline)
+ "%e%p1%{6}%=" "%t%{2}" // steady bar
+ "%e%{0}" // anything else
+ "%;" "%dc"));
if (-1 == data->unibi_ext.reset_cursor_style) {
data->unibi_ext.reset_cursor_style = (int)unibi_add_ext_str(ut, "Se",
"");
}
unibi_set_ext_str(ut, (size_t)data->unibi_ext.reset_cursor_style,
- "\x1b[?c");
+ SCREEN_WRAP(true_screen, "\x1b[?c"));
} else if (konsolev > 0 && konsolev < 180770) {
// Konsole before version 18.07.70: set up a nonce profile. This has
// side-effects on temporary font resizing. #6798
- data->unibi_ext.set_cursor_style = (int)unibi_add_ext_str(ut, "Ss",
- TMUX_WRAP(tmux, "\x1b]50;CursorShape=%?"
- "%p1%{3}%<" "%t%{0}" // block
- "%e%p1%{5}%<" "%t%{2}" // underline
- "%e%{1}" // everything else is bar
- "%;%d;BlinkingCursorEnabled=%?"
- "%p1%{1}%<" "%t%{1}" // Fortunately if we exclude zero as special,
- "%e%p1%{1}%&" // in all other cases we can treat bit #0 as a flag.
- "%;%d\x07"));
+ data->unibi_ext.set_cursor_style = (int)unibi_add_ext_str(
+ ut, "Ss",
+ SCREEN_TMUX_WRAP(true_screen, tmux,
+ "\x1b]50;CursorShape=%?"
+ "%p1%{3}%<" "%t%{0}" // block
+ "%e%p1%{5}%<" "%t%{2}" // underline
+ "%e%{1}" // everything else is bar
+ "%;%d;BlinkingCursorEnabled=%?"
+ "%p1%{1}%<" "%t%{1}" // Fortunately if we exclude
+ // zero as special,
+ "%e%p1%{1}%&" // in all other c2ses we can treat bit
+ // #0 as a flag.
+ "%;%d\x07"));
if (-1 == data->unibi_ext.reset_cursor_style) {
data->unibi_ext.reset_cursor_style = (int)unibi_add_ext_str(ut, "Se",
"");
}
unibi_set_ext_str(ut, (size_t)data->unibi_ext.reset_cursor_style,
- "\x1b]50;\x07");
+ SCREEN_TMUX_WRAP(true_screen, tmux, "\x1b]50;\x07"));
}
}
}
@@ -1792,7 +1907,7 @@ static void patch_terminfo_bugs(TUIData *data, const char *term,
/// This adds stuff that is not in standard terminfo as extended unibilium
/// capabilities.
static void augment_terminfo(TUIData *data, const char *term,
- const char *colorterm, long vte_version,
+ long vte_version,
long konsolev, bool iterm_env, bool nsterm)
{
unibi_term *ut = data->ut;
@@ -1816,6 +1931,10 @@ static void augment_terminfo(TUIData *data, const char *term,
const char *xterm_version = os_getenv("XTERM_VERSION");
bool true_xterm = xterm && !!xterm_version && !bsdvt;
+ bool true_screen = screen && !os_getenv("TMUX");
+ bool screen_host_rxvt =
+ terminfo_is_term_family(true_screen
+ && term[6] == '.' ? term + 7 : NULL, "rxvt");
// Only define this capability for terminal types that we know understand it.
if (dtterm // originated this extension
@@ -1882,8 +2001,8 @@ static void augment_terminfo(TUIData *data, const char *term,
// all panes, which is not particularly desirable. A better approach
// would use a tmux control sequence and an extra if(screen) test.
data->unibi_ext.set_cursor_color = (int)unibi_add_ext_str(
- ut, NULL, TMUX_WRAP(tmux, "\033]Pl%p1%06x\033\\"));
- } else if ((xterm || rxvt || alacritty)
+ ut, NULL, SCREEN_TMUX_WRAP(true_screen, tmux, "\033]Pl%p1%06x\033\\"));
+ } else if ((xterm || rxvt || tmux || alacritty)
&& (vte_version == 0 || vte_version >= 3900)) {
// Supported in urxvt, newer VTE.
data->unibi_ext.set_cursor_color = (int)unibi_add_ext_str(
@@ -1902,21 +2021,27 @@ static void augment_terminfo(TUIData *data, const char *term,
/// Terminals usually ignore unrecognized private modes, and there is no
/// known ambiguity with these. So we just set them unconditionally.
+ /// If the DECSET is not supported by GNU Screen, it is wrapped with DCS and
+ /// sent to the host terminal.
data->unibi_ext.enable_lr_margin = (int)unibi_add_ext_str(
ut, "ext.enable_lr_margin", "\x1b[?69h");
data->unibi_ext.disable_lr_margin = (int)unibi_add_ext_str(
ut, "ext.disable_lr_margin", "\x1b[?69l");
data->unibi_ext.enable_bracketed_paste = (int)unibi_add_ext_str(
- ut, "ext.enable_bpaste", "\x1b[?2004h");
+ ut, "ext.enable_bpaste", SCREEN_WRAP(true_screen, "\x1b[?2004h"));
data->unibi_ext.disable_bracketed_paste = (int)unibi_add_ext_str(
- ut, "ext.disable_bpaste", "\x1b[?2004l");
+ ut, "ext.disable_bpaste", SCREEN_WRAP(true_screen, "\x1b[?2004l"));
// For urxvt send BOTH xterm and old urxvt sequences. #8695
data->unibi_ext.enable_focus_reporting = (int)unibi_add_ext_str(
ut, "ext.enable_focus",
- rxvt ? "\x1b[?1004h\x1b]777;focus;on\x7" : "\x1b[?1004h");
+ (rxvt || screen_host_rxvt)
+ ? SCREEN_WRAP(true_screen, "\x1b[?1004h\x1b]777;focus;on\x7")
+ : SCREEN_WRAP(true_screen, "\x1b[?1004h"));
data->unibi_ext.disable_focus_reporting = (int)unibi_add_ext_str(
ut, "ext.disable_focus",
- rxvt ? "\x1b[?1004l\x1b]777;focus;off\x7" : "\x1b[?1004l");
+ (rxvt || screen_host_rxvt)
+ ? SCREEN_WRAP(true_screen, "\x1b[?1004l\x1b]777;focus;off\x7")
+ : SCREEN_WRAP(true_screen, "\x1b[?1004l"));
data->unibi_ext.enable_mouse = (int)unibi_add_ext_str(
ut, "ext.enable_mouse", "\x1b[?1002h\x1b[?1006h");
data->unibi_ext.disable_mouse = (int)unibi_add_ext_str(
@@ -1948,7 +2073,23 @@ static void flush_buf(UI *ui)
uv_buf_t *bufp = &bufs[0];
TUIData *data = ui->data;
- if (data->bufpos <= 0 && data->busy == data->is_invisible) {
+ // The content of the output for each condition is shown in the following
+ // table. Therefore, if data->bufpos == 0 and N/A or invis + norm, there is
+ // no need to output it.
+ //
+ // | is_invisible | !is_invisible
+ // ------+-----------------+--------------+---------------
+ // busy | want_invisible | N/A | invis
+ // | !want_invisible | N/A | invis
+ // ------+-----------------+--------------+---------------
+ // !busy | want_invisible | N/A | invis
+ // | !want_invisible | norm | invis + norm
+ // ------+-----------------+--------------+---------------
+ //
+ if (data->bufpos <= 0
+ && ((data->is_invisible && data->busy)
+ || (data->is_invisible && !data->busy && data->want_invisible)
+ || (!data->is_invisible && !data->busy && !data->want_invisible))) {
return;
}
@@ -1971,10 +2112,12 @@ static void flush_buf(UI *ui)
assert(data->is_invisible);
// not busy and the cursor is invisible. Write a "cursor normal" command
// after writing the buffer.
- bufp->base = data->norm;
- bufp->len = UV_BUF_LEN(data->normlen);
- bufp++;
- data->is_invisible = data->busy;
+ if (!data->want_invisible) {
+ bufp->base = data->norm;
+ bufp->len = UV_BUF_LEN(data->normlen);
+ bufp++;
+ data->is_invisible = false;
+ }
}
uv_write(&req, STRUCT_CAST(uv_stream_t, &data->output_handle),
diff --git a/src/nvim/ui.c b/src/nvim/ui.c
index 79fa8b8223..685da77b39 100644
--- a/src/nvim/ui.c
+++ b/src/nvim/ui.c
@@ -226,7 +226,7 @@ int ui_pum_get_height(void)
{
int pum_height = 0;
for (size_t i = 1; i < ui_count; i++) {
- int ui_pum_height = uis[i]->pum_height;
+ int ui_pum_height = uis[i]->pum_nlines;
if (ui_pum_height) {
pum_height =
pum_height != 0 ? MIN(pum_height, ui_pum_height) : ui_pum_height;
@@ -235,6 +235,21 @@ int ui_pum_get_height(void)
return pum_height;
}
+bool ui_pum_get_pos(double *pwidth, double *pheight, double *prow, double *pcol)
+{
+ for (size_t i = 1; i < ui_count; i++) {
+ if (!uis[i]->pum_pos) {
+ continue;
+ }
+ *pwidth = uis[i]->pum_width;
+ *pheight = uis[i]->pum_height;
+ *prow = uis[i]->pum_row;
+ *pcol = uis[i]->pum_col;
+ return true;
+ }
+ return false;
+}
+
static void ui_refresh_event(void **argv)
{
ui_refresh();
@@ -424,7 +439,7 @@ int ui_current_col(void)
void ui_flush(void)
{
cmdline_ui_flush();
- win_ui_flush_positions();
+ win_ui_flush();
msg_ext_ui_flush();
msg_scroll_flush();
@@ -505,7 +520,7 @@ void ui_grid_resize(handle_T grid_handle, int width, int height, Error *error)
}
if (wp->w_floating) {
- if (width != wp->w_width && height != wp->w_height) {
+ if (width != wp->w_width || height != wp->w_height) {
wp->w_float_config.width = width;
wp->w_float_config.height = height;
win_config_float(wp, wp->w_float_config);
diff --git a/src/nvim/ui.h b/src/nvim/ui.h
index 8867b5ee24..d00243d35f 100644
--- a/src/nvim/ui.h
+++ b/src/nvim/ui.h
@@ -53,7 +53,12 @@ struct ui_t {
bool ui_ext[kUIExtCount]; ///< Externalized UI capabilities.
int width;
int height;
- int pum_height;
+ int pum_nlines; /// actual nr. lines shown in PUM
+ bool pum_pos; /// UI reports back pum position?
+ double pum_row;
+ double pum_col;
+ double pum_height;
+ double pum_width;
void *data;
#ifdef INCLUDE_GENERATED_DECLARATIONS
diff --git a/src/nvim/ui_compositor.c b/src/nvim/ui_compositor.c
index 20ffc1b88e..e582d8f859 100644
--- a/src/nvim/ui_compositor.c
+++ b/src/nvim/ui_compositor.c
@@ -425,6 +425,15 @@ static void compose_line(Integer row, Integer startcol, Integer endcol,
flags = flags & ~kLineFlagWrap;
}
+ for (int i = skipstart; i < (endcol-skipend)-startcol; i++) {
+ if (attrbuf[i] < 0) {
+ if (rdb_flags & RDB_INVALID) {
+ abort();
+ } else {
+ attrbuf[i] = 0;
+ }
+ }
+ }
ui_composed_call_raw_line(1, row, startcol+skipstart,
endcol-skipend, endcol-skipend, 0, flags,
(const schar_T *)linebuf+skipstart,
@@ -535,6 +544,11 @@ static void ui_comp_raw_line(UI *ui, Integer grid, Integer row,
} else {
compose_debug(row, row+1, startcol, endcol, dbghl_normal, false);
compose_debug(row, row+1, endcol, clearcol, dbghl_clear, true);
+#ifndef NDEBUG
+ for (int i = 0; i < endcol-startcol; i++) {
+ assert(attrs[i] >= 0);
+ }
+#endif
ui_composed_call_raw_line(1, row, startcol, endcol, clearcol, clearattr,
flags, chunk, attrs);
}
@@ -612,7 +626,7 @@ static void ui_comp_grid_scroll(UI *ui, Integer grid, Integer top,
if (covered || curgrid->blending) {
// TODO(bfredl):
// 1. check if rectangles actually overlap
- // 2. calulate subareas that can scroll.
+ // 2. calculate subareas that can scroll.
compose_debug(top, bot, left, right, dbghl_recompose, true);
for (int r = (int)(top + MAX(-rows, 0)); r < bot - MAX(rows, 0); r++) {
// TODO(bfredl): workaround for win_update() performing two scrolls in a
diff --git a/src/nvim/undo.c b/src/nvim/undo.c
index 035613c7fd..97018f6c02 100644
--- a/src/nvim/undo.c
+++ b/src/nvim/undo.c
@@ -91,7 +91,9 @@
#include "nvim/fileio.h"
#include "nvim/fold.h"
#include "nvim/buffer_updates.h"
+#include "nvim/pos.h" // MAXLNUM
#include "nvim/mark.h"
+#include "nvim/extmark.h"
#include "nvim/memline.h"
#include "nvim/message.h"
#include "nvim/misc1.h"
@@ -106,6 +108,7 @@
#include "nvim/types.h"
#include "nvim/os/os.h"
#include "nvim/os/time.h"
+#include "nvim/lib/kvec.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "undo.c.generated.h"
@@ -222,9 +225,6 @@ int u_save_cursor(void)
*/
int u_save(linenr_T top, linenr_T bot)
{
- if (undo_off)
- return OK;
-
if (top >= bot || bot > (curbuf->b_ml.ml_line_count + 1)) {
return FAIL; /* rely on caller to do error messages */
}
@@ -243,10 +243,7 @@ int u_save(linenr_T top, linenr_T bot)
*/
int u_savesub(linenr_T lnum)
{
- if (undo_off)
- return OK;
-
- return u_savecommon(lnum - 1, lnum + 1, lnum + 1, FALSE);
+ return u_savecommon(lnum - 1, lnum + 1, lnum + 1, false);
}
/*
@@ -257,10 +254,7 @@ int u_savesub(linenr_T lnum)
*/
int u_inssub(linenr_T lnum)
{
- if (undo_off)
- return OK;
-
- return u_savecommon(lnum - 1, lnum, lnum + 1, FALSE);
+ return u_savecommon(lnum - 1, lnum, lnum + 1, false);
}
/*
@@ -272,9 +266,6 @@ int u_inssub(linenr_T lnum)
*/
int u_savedel(linenr_T lnum, long nlines)
{
- if (undo_off)
- return OK;
-
return u_savecommon(lnum - 1, lnum + nlines,
nlines == curbuf->b_ml.ml_line_count ? 2 : lnum, FALSE);
}
@@ -384,6 +375,7 @@ int u_savecommon(linenr_T top, linenr_T bot, linenr_T newbot, int reload)
* up the undo info when out of memory.
*/
uhp = xmalloc(sizeof(u_header_T));
+ kv_init(uhp->uh_extmark);
#ifdef U_DEBUG
uhp->uh_magic = UH_MAGIC;
#endif
@@ -1065,7 +1057,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(_("Cannot write undo file in any directory in 'undodir'"));
+ smsg("%s", _("Cannot write undo file in any directory in 'undodir'"));
verbose_leave();
}
return;
@@ -2249,10 +2241,10 @@ static void u_undoredo(int undo, bool do_buf_event)
xfree((char_u *)uep->ue_array);
}
- /* adjust marks */
+ // Adjust marks
if (oldsize != newsize) {
mark_adjust(top + 1, top + oldsize, (long)MAXLNUM,
- (long)newsize - (long)oldsize, false);
+ (long)newsize - (long)oldsize, kExtmarkNOOP);
if (curbuf->b_op_start.lnum > top + oldsize) {
curbuf->b_op_start.lnum += newsize - oldsize;
}
@@ -2285,6 +2277,23 @@ static void u_undoredo(int undo, bool do_buf_event)
newlist = uep;
}
+ // Adjust Extmarks
+ ExtmarkUndoObject undo_info;
+ if (undo) {
+ for (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 (i = 0; i < (int)kv_size(curhead->uh_extmark); i++) {
+ undo_info = kv_A(curhead->uh_extmark, i);
+ extmark_apply_undo(undo_info, undo);
+ }
+ }
+ // finish Adjusting extmarks
+
+
curhead->uh_entry = newlist;
curhead->uh_flags = new_flags;
if ((old_flags & UH_EMPTYBUF) && BUFEMPTY()) {
@@ -2432,10 +2441,11 @@ static void u_undo_end(
uhp = curbuf->b_u_newhead;
}
- if (uhp == NULL)
+ if (uhp == NULL) {
*msgbuf = NUL;
- else
- u_add_time(msgbuf, sizeof(msgbuf), uhp->uh_time);
+ } else {
+ add_time(msgbuf, sizeof(msgbuf), uhp->uh_time);
+ }
{
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
@@ -2500,8 +2510,8 @@ void ex_undolist(exarg_T *eap)
&& uhp->uh_walk != mark) {
vim_snprintf((char *)IObuff, IOSIZE, "%6ld %7d ",
uhp->uh_seq, changes);
- u_add_time(IObuff + STRLEN(IObuff), IOSIZE - STRLEN(IObuff),
- uhp->uh_time);
+ add_time(IObuff + STRLEN(IObuff), IOSIZE - STRLEN(IObuff),
+ uhp->uh_time);
if (uhp->uh_save_nr > 0) {
while (STRLEN(IObuff) < 33)
STRCAT(IObuff, " ");
@@ -2566,30 +2576,6 @@ void ex_undolist(exarg_T *eap)
}
/*
- * Put the timestamp of an undo header in "buf[buflen]" in a nice format.
- */
-static void u_add_time(char_u *buf, size_t buflen, time_t tt)
-{
- struct tm curtime;
-
- if (time(NULL) - tt >= 100) {
- os_localtime_r(&tt, &curtime);
- if (time(NULL) - tt < (60L * 60L * 12L))
- /* within 12 hours */
- (void)strftime((char *)buf, buflen, "%H:%M:%S", &curtime);
- else
- /* longer ago */
- (void)strftime((char *)buf, buflen, "%Y/%m/%d %H:%M:%S", &curtime);
- } else {
- int64_t seconds = time(NULL) - tt;
- vim_snprintf((char *)buf, buflen,
- NGETTEXT("%" PRId64 " second ago",
- "%" PRId64 " seconds ago", (uint32_t)seconds),
- seconds);
- }
-}
-
-/*
* ":undojoin": continue adding to the last entry list
*/
void ex_undojoin(exarg_T *eap)
@@ -2828,6 +2814,8 @@ u_freeentries(
u_freeentry(uep, uep->ue_size);
}
+ kv_destroy(uhp->uh_extmark);
+
#ifdef U_DEBUG
uhp->uh_magic = 0;
#endif
@@ -2902,9 +2890,6 @@ void u_undoline(void)
colnr_T t;
char_u *oldp;
- if (undo_off)
- return;
-
if (curbuf->b_u_line_ptr == NULL
|| curbuf->b_u_line_lnum > curbuf->b_ml.ml_line_count) {
beep_flush();
@@ -2963,7 +2948,10 @@ static char_u *u_save_line(linenr_T lnum)
bool bufIsChanged(buf_T *buf)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
{
- return !bt_dontwrite(buf) && (buf->b_changed || file_ff_differs(buf, true));
+ // In a "prompt" buffer we do respect 'modified', so that we can control
+ // closing the window by setting or resetting that option.
+ return (!bt_dontwrite(buf) || bt_prompt(buf))
+ && (buf->b_changed || file_ff_differs(buf, true));
}
// Return true if any buffer has changes. Also buffers that are not written.
@@ -3022,3 +3010,34 @@ list_T *u_eval_tree(const u_header_T *const first_uhp)
return list;
}
+
+// 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)
+{
+ u_header_T *uhp = NULL;
+ if (buf->b_u_curhead != NULL) {
+ uhp = buf->b_u_curhead;
+ } else if (buf->b_u_newhead) {
+ uhp = buf->b_u_newhead;
+ }
+ // Create the first undo header for the buffer
+ if (!uhp) {
+ // Undo is normally invoked in change code, which already has swapped
+ // curbuf.
+ buf_T *save_curbuf = curbuf;
+ curbuf = buf;
+ // Args are tricky: this means replace empty range by empty range..
+ u_savecommon(0, 1, 1, true);
+ curbuf = save_curbuf;
+
+ uhp = buf->b_u_curhead;
+ if (!uhp) {
+ uhp = buf->b_u_newhead;
+ if (get_undolevel() > 0 && !uhp) {
+ abort();
+ }
+ }
+ }
+ return uhp;
+}
diff --git a/src/nvim/undo_defs.h b/src/nvim/undo_defs.h
index 6c7e2bba41..cc2c39a711 100644
--- a/src/nvim/undo_defs.h
+++ b/src/nvim/undo_defs.h
@@ -4,6 +4,7 @@
#include <time.h> // for time_t
#include "nvim/pos.h"
+#include "nvim/extmark_defs.h"
#include "nvim/mark_defs.h"
typedef struct u_header u_header_T;
@@ -56,14 +57,15 @@ struct u_header {
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 */
- 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_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
#ifdef U_DEBUG
- int uh_magic; /* magic number to check allocation */
+ int uh_magic; // magic number to check allocation
#endif
};
diff --git a/src/nvim/version.c b/src/nvim/version.c
index b0619d6273..190f13e74b 100644
--- a/src/nvim/version.c
+++ b/src/nvim/version.c
@@ -83,7 +83,7 @@ static const int included_patches[] = {
1838,
1837,
1836,
- // 1835,
+ 1835,
1834,
1833,
1832,
@@ -101,12 +101,12 @@ static const int included_patches[] = {
1820,
1819,
1818,
- // 1817,
+ 1817,
1816,
1815,
1814,
1813,
- // 1812,
+ 1812,
1811,
1810,
1809,
@@ -120,38 +120,38 @@ static const int included_patches[] = {
1801,
1800,
1799,
- // 1798,
+ 1798,
1797,
1796,
1795,
// 1794,
- // 1793,
+ 1793,
1792,
1791,
1790,
- // 1789,
+ 1789,
1788,
1787,
- // 1786,
+ 1786,
1785,
1784,
- // 1783,
+ 1783,
1782,
1781,
1780,
1779,
1778,
1777,
- // 1776,
+ 1776,
1775,
// 1774,
1773,
- // 1772,
- // 1771,
- // 1770,
- // 1769,
+ 1772,
+ 1771,
+ 1770,
+ 1769,
1768,
- // 1767,
+ 1767,
1766,
1765,
1764,
@@ -164,7 +164,7 @@ static const int included_patches[] = {
1757,
1756,
1755,
- // 1754,
+ 1754,
1753,
1752,
1751,
@@ -176,7 +176,7 @@ static const int included_patches[] = {
// 1745,
// 1744,
// 1743,
- // 1742,
+ 1742,
1741,
1740,
1739,
@@ -185,7 +185,7 @@ static const int included_patches[] = {
1736,
1735,
1734,
- // 1733,
+ 1733,
// 1732,
1731,
1730,
@@ -193,10 +193,10 @@ static const int included_patches[] = {
1728,
1727,
1726,
- // 1725,
+ 1725,
1724,
1723,
- // 1722,
+ 1722,
1721,
1720,
1719,
@@ -207,7 +207,7 @@ static const int included_patches[] = {
1714,
1713,
// 1712,
- // 1711,
+ 1711,
1710,
1709,
1708,
@@ -233,7 +233,7 @@ static const int included_patches[] = {
1688,
1687,
1686,
- // 1685,
+ 1685,
1684,
1683,
1682,
@@ -250,15 +250,15 @@ static const int included_patches[] = {
1671,
1670,
1669,
- // 1668,
+ 1668,
1667,
- // 1666,
- // 1665,
+ 1666,
+ 1665,
1664,
1663,
1662,
1661,
- // 1660,
+ 1660,
1659,
1658,
1657,
@@ -267,7 +267,7 @@ static const int included_patches[] = {
1654,
1653,
1652,
- // 1651,
+ 1651,
1650,
1649,
1648,
@@ -294,12 +294,12 @@ static const int included_patches[] = {
1627,
1626,
1625,
- // 1624,
+ 1624,
1623,
1622,
- // 1621,
+ 1621,
1620,
- // 1619,
+ 1619,
1618,
// 1617,
// 1616,
@@ -325,18 +325,18 @@ static const int included_patches[] = {
1596,
1595,
1594,
- // 1593,
+ 1593,
// 1592,
// 1591,
1590,
// 1589,
// 1588,
- // 1587,
+ 1587,
1586,
1585,
1584,
1583,
- // 1582,
+ 1582,
1581,
1580,
1579,
@@ -364,7 +364,7 @@ static const int included_patches[] = {
1557,
1556,
1555,
- // 1554,
+ 1554,
1553,
1552,
1551,
@@ -375,12 +375,12 @@ static const int included_patches[] = {
1546,
1545,
// 1544,
- // 1543,
+ 1543,
1542,
1541,
- // 1540,
- // 1539,
- // 1538,
+ 1540,
+ 1539,
+ 1538,
1537,
1536,
1535,
@@ -389,16 +389,16 @@ static const int included_patches[] = {
1532,
// 1531,
1530,
- // 1529,
+ 1529,
1528,
1527,
1526,
// 1525,
1524,
- // 1523,
- // 1522,
+ 1523,
+ 1522,
1521,
- // 1520,
+ 1520,
1519,
1518,
1517,
@@ -423,11 +423,11 @@ static const int included_patches[] = {
1498,
1497,
1496,
- // 1495,
+ 1495,
1494,
1493,
- // 1492,
- // 1491,
+ 1492,
+ 1491,
1490,
1489,
1488,
@@ -467,10 +467,10 @@ static const int included_patches[] = {
// 1454,
1453,
1452,
- // 1451,
+ 1451,
1450,
- // 1449,
- // 1448,
+ 1449,
+ 1448,
1447,
1446,
1445,
@@ -496,7 +496,7 @@ static const int included_patches[] = {
1425,
1424,
1423,
- // 1422,
+ 1422,
1421,
1420,
1419,
@@ -513,7 +513,7 @@ static const int included_patches[] = {
1408,
1407,
1406,
- // 1405,
+ 1405,
1404,
1403,
1402,
@@ -530,7 +530,7 @@ static const int included_patches[] = {
1391,
1390,
1389,
- // 1388,
+ 1388,
1387,
1386,
1385,
@@ -543,16 +543,16 @@ static const int included_patches[] = {
1378,
1377,
1376,
- // 1375,
+ 1375,
1374,
1373,
1372,
- // 1371,
+ 1371,
1370,
1369,
1368,
// 1367,
- // 1366,
+ 1366,
1365,
1364,
1363,
@@ -560,11 +560,11 @@ static const int included_patches[] = {
1361,
1360,
1359,
- // 1358,
+ 1358,
1357,
- // 1356,
- // 1355,
- // 1354,
+ 1356,
+ 1355,
+ 1354,
1353,
1352,
1351,
@@ -573,7 +573,7 @@ static const int included_patches[] = {
1348,
1347,
1346,
- // 1345,
+ 1345,
1344,
1343,
1342,
@@ -584,7 +584,7 @@ static const int included_patches[] = {
1337,
1336,
// 1335,
- // 1334,
+ 1334,
1333,
1332,
1331,
@@ -626,12 +626,12 @@ static const int included_patches[] = {
1295,
1294,
1293,
- // 1292,
+ 1292,
1291,
1290,
1289,
1288,
- // 1287,
+ 1287,
1286,
1285,
1284,
@@ -742,7 +742,7 @@ static const int included_patches[] = {
1179,
1178,
1177,
- // 1176,
+ 1176,
1175,
1174,
1173,
@@ -776,26 +776,26 @@ static const int included_patches[] = {
1145,
1144,
1143,
- // 1142,
+ 1142,
1141,
1140,
- // 1139,
+ 1139,
1138,
1137,
1136,
1135,
- // 1134,
+ 1134,
1133,
1132,
1131,
1130,
- // 1129,
+ 1129,
1128,
- // 1127,
+ 1127,
1126,
- // 1125,
+ 1125,
1124,
- // 1123,
+ 1123,
1122,
1121,
1120,
@@ -809,7 +809,7 @@ static const int included_patches[] = {
1112,
1111,
1110,
- // 1109,
+ 1109,
1108,
1107,
1106,
@@ -880,7 +880,7 @@ static const int included_patches[] = {
1041,
1040,
1039,
- // 1038,
+ 1038,
1037,
1036,
1035,
@@ -948,7 +948,7 @@ static const int included_patches[] = {
973,
972,
971,
- // 970,
+ 970,
969,
968,
967,
@@ -977,14 +977,14 @@ static const int included_patches[] = {
944,
943,
942,
- // 941,
+ 941,
940,
939,
938,
- // 937,
+ 937,
936,
935,
- // 934,
+ 934,
933,
932,
931,
@@ -1004,7 +1004,7 @@ static const int included_patches[] = {
917,
916,
915,
- // 914,
+ 914,
913,
912,
911,
@@ -1016,7 +1016,7 @@ static const int included_patches[] = {
905,
904,
903,
- // 902,
+ 902,
901,
900,
899,
@@ -1093,7 +1093,7 @@ static const int included_patches[] = {
828,
827,
826,
- // 825,
+ 825,
824,
823,
822,
diff --git a/src/nvim/vim.h b/src/nvim/vim.h
index 51f143a3d7..832703e83d 100644
--- a/src/nvim/vim.h
+++ b/src/nvim/vim.h
@@ -101,6 +101,7 @@ typedef enum {
#define VAR_TYPE_DICT 4
#define VAR_TYPE_FLOAT 5
#define VAR_TYPE_BOOL 6
+#define VAR_TYPE_SPECIAL 7
// values for xp_context when doing command line completion
@@ -248,6 +249,9 @@ enum { FOLD_TEXT_LEN = 51 }; //!< buffer size for get_foldtext()
# define vim_strpbrk(s, cs) (char_u *)strpbrk((char *)(s), (char *)(cs))
+// Character used as separated in autoload function/variable names.
+#define AUTOLOAD_CHAR '#'
+
#include "nvim/message.h"
// Prefer using emsgf(), because perror() may send the output to the wrong
@@ -257,6 +261,8 @@ enum { FOLD_TEXT_LEN = 51 }; //!< buffer size for get_foldtext()
#define SHOWCMD_COLS 10 // columns needed by shown command
#define STL_MAX_ITEM 80 // max nr of %<flag> in statusline
+#include "nvim/path.h"
+
/// Compare file names
///
/// On some systems case in a file name does not matter, on others it does.
diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c
index b4a0f57e99..b77b80a5f3 100644
--- a/src/nvim/viml/parser/expressions.c
+++ b/src/nvim/viml/parser/expressions.c
@@ -133,9 +133,6 @@ typedef enum {
# include "viml/parser/expressions.c.generated.h"
#endif
-/// Character used as a separator in autoload function/variable names.
-#define AUTOLOAD_CHAR '#'
-
/// Scale number by a given factor
///
/// Used to apply exponent to a number. Idea taken from uClibc.
diff --git a/src/nvim/viml/parser/expressions.h b/src/nvim/viml/parser/expressions.h
index 23e172da75..838a742271 100644
--- a/src/nvim/viml/parser/expressions.h
+++ b/src/nvim/viml/parser/expressions.h
@@ -326,7 +326,7 @@ struct expr_ast_node {
} data;
};
-enum {
+enum ExprParserFlags {
/// Allow multiple expressions in a row: e.g. for :echo
///
/// Parser will still parse only one of them though.
@@ -345,7 +345,7 @@ enum {
// viml_expressions_parser.c, nvim_parse_expression() flags parsing
// alongside with its documentation and flag sets in check_parsing()
// function in expressions parser functional and unit tests.
-} ExprParserFlags;
+};
/// AST error definition
typedef struct {
diff --git a/src/nvim/window.c b/src/nvim/window.c
index 1e6de73549..0fff93d984 100644
--- a/src/nvim/window.c
+++ b/src/nvim/window.c
@@ -70,8 +70,8 @@ static char *m_onlyone = N_("Already only one window");
/*
* all CTRL-W window commands are handled here, called from normal_cmd().
*/
-void
-do_window (
+void
+do_window(
int nchar,
long Prenum,
int xchar /* extra char from ":wincmd gx" or NUL */
@@ -85,10 +85,7 @@ do_window (
size_t len;
char cbuf[40];
- if (Prenum == 0)
- Prenum1 = 1;
- else
- Prenum1 = Prenum;
+ Prenum1 = Prenum == 0 ? 1 : Prenum;
# define CHECK_CMDWIN \
do { \
@@ -131,8 +128,20 @@ do_window (
case '^':
CHECK_CMDWIN;
reset_VIsual_and_resel(); // stop Visual mode
- cmd_with_count("split #", (char_u *)cbuf, sizeof(cbuf), Prenum);
- do_cmdline_cmd(cbuf);
+
+ if (buflist_findnr(Prenum == 0 ? curwin->w_alt_fnum : Prenum) == NULL) {
+ if (Prenum == 0) {
+ EMSG(_(e_noalt));
+ } else {
+ EMSGN(_("E92: Buffer %" PRId64 " not found"), Prenum);
+ }
+ break;
+ }
+
+ if (!curbuf_locked() && win_split(0, 0) == OK) {
+ (void)buflist_getfile(Prenum == 0 ? curwin->w_alt_fnum : Prenum,
+ (linenr_T)0, GETF_ALT, false);
+ }
break;
/* open new window */
@@ -507,11 +516,22 @@ wingotofile:
do_nv_ident('g', xchar);
break;
+ case TAB:
+ goto_tabpage_lastused();
+ break;
+
case 'f': /* CTRL-W gf: "gf" in a new tab page */
case 'F': /* CTRL-W gF: "gF" in a new tab page */
cmdmod.tab = tabpage_index(curtab) + 1;
nchar = xchar;
goto wingotofile;
+ case 't': // CTRL-W gt: go to next tab page
+ goto_tabpage((int)Prenum);
+ break;
+
+ case 'T': // CTRL-W gT: go to previous tab page
+ goto_tabpage(-(int)Prenum1);
+ break;
case 'e':
if (curwin->w_floating || !ui_has(kUIMultigrid)) {
@@ -598,7 +618,6 @@ void win_set_minimal_style(win_T *wp)
wp->w_p_cuc = false;
wp->w_p_spell = false;
wp->w_p_list = false;
- wp->w_p_fdc = 0;
// Hide EOB region: use " " fillchar and cleared highlighting
if (wp->w_p_fcs_chars.eob != ' ') {
@@ -621,6 +640,18 @@ void win_set_minimal_style(win_T *wp)
xfree(wp->w_p_scl);
wp->w_p_scl = (char_u *)xstrdup("auto");
}
+
+ // foldcolumn: use 'auto'
+ if (wp->w_p_fdc[0] != '0') {
+ xfree(wp->w_p_fdc);
+ wp->w_p_fdc = (char_u *)xstrdup("0");
+ }
+
+ // colorcolumn: cleared
+ if (wp->w_p_cc != NULL && *wp->w_p_cc != NUL) {
+ xfree(wp->w_p_cc);
+ wp->w_p_cc = (char_u *)xstrdup("");
+ }
}
void win_config_float(win_T *wp, FloatConfig fconfig)
@@ -663,6 +694,21 @@ void win_check_anchored_floats(win_T *win)
}
}
+/// Return the number of fold columns to display
+int win_fdccol_count(win_T *wp)
+{
+ const char *fdc = (const char *)wp->w_p_fdc;
+
+ // auto:<NUM>
+ if (strncmp(fdc, "auto:", 5) == 0) {
+ int needed_fdccols = getDeepestNesting(wp);
+ return MIN(fdc[5] - '0', needed_fdccols);
+ } else {
+ return fdc[0] - '0';
+ }
+}
+
+
static void ui_ext_win_position(win_T *wp)
{
if (!wp->w_floating) {
@@ -727,6 +773,21 @@ static void ui_ext_win_position(win_T *wp)
}
+void ui_ext_win_viewport(win_T *wp)
+{
+ if ((wp == curwin || ui_has(kUIMultigrid)) && wp->w_viewport_invalid) {
+ int botline = wp->w_botline;
+ if (botline == wp->w_buffer->b_ml.ml_line_count+1
+ && wp->w_empty_rows == 0) {
+ // TODO(bfredl): The might be more cases to consider, like how does this
+ // interact with incomplete final line? Diff filler lines?
+ botline = wp->w_buffer->b_ml.ml_line_count;
+ }
+ ui_call_win_viewport(wp->w_grid.handle, wp->handle, wp->w_topline-1,
+ botline, wp->w_cursor.lnum-1, wp->w_cursor.col);
+ wp->w_viewport_invalid = false;
+ }
+}
static bool parse_float_anchor(String anchor, FloatAnchor *out)
{
@@ -1051,7 +1112,8 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir)
// add a status line when p_ls == 1 and splitting the first window
if (one_nonfloat() && p_ls == 1 && oldwin->w_status_height == 0) {
- if (oldwin->w_height <= p_wmh && new_in_layout) {
+ if ((oldwin->w_height + oldwin->w_winbar_height) <= p_wmh
+ && new_in_layout) {
EMSG(_(e_noroom));
return FAIL;
}
@@ -1148,7 +1210,7 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir)
* height.
*/
// Current window requires at least 1 space.
- wmh1 = (p_wmh == 0 ? 1 : p_wmh);
+ wmh1 = (p_wmh == 0 ? 1 : p_wmh) + curwin->w_winbar_height;
needed = wmh1 + STATUS_HEIGHT;
if (flags & WSP_ROOM) {
needed += p_wh - wmh1;
@@ -1346,12 +1408,12 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir)
if (flags & (WSP_TOP | WSP_BOT)) {
/* set height and row of new window to full height */
wp->w_winrow = tabline_height();
- win_new_height(wp, curfrp->fr_height - (p_ls > 0));
+ win_new_height(wp, curfrp->fr_height - (p_ls > 0) - wp->w_winbar_height);
wp->w_status_height = (p_ls > 0);
} else {
/* height and row of new window is same as current window */
wp->w_winrow = oldwin->w_winrow;
- win_new_height(wp, oldwin->w_height);
+ win_new_height(wp, oldwin->w_height + oldwin->w_winbar_height);
wp->w_status_height = oldwin->w_status_height;
}
frp->fr_height = curfrp->fr_height;
@@ -1398,7 +1460,7 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir)
* one row for the status line */
win_new_height(wp, new_size);
if (flags & (WSP_TOP | WSP_BOT)) {
- int new_fr_height = curfrp->fr_height - new_size;
+ int new_fr_height = curfrp->fr_height - new_size + wp->w_winbar_height;
if (!((flags & WSP_BOT) && p_ls == 0)) {
new_fr_height -= STATUS_HEIGHT;
@@ -1411,8 +1473,9 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir)
wp->w_winrow = oldwin->w_winrow;
wp->w_status_height = STATUS_HEIGHT;
oldwin->w_winrow += wp->w_height + STATUS_HEIGHT;
- } else { /* new window below current one */
- wp->w_winrow = oldwin->w_winrow + oldwin->w_height + STATUS_HEIGHT;
+ } else { // new window below current one
+ wp->w_winrow = oldwin->w_winrow + oldwin->w_height
+ + STATUS_HEIGHT + oldwin->w_winbar_height;
wp->w_status_height = oldwin->w_status_height;
if (!(flags & WSP_BOT)) {
oldwin->w_status_height = STATUS_HEIGHT;
@@ -1518,17 +1581,22 @@ static void win_init(win_T *newp, win_T *oldp, int flags)
/* Don't copy the location list. */
newp->w_llist = NULL;
newp->w_llist_ref = NULL;
- } else
- copy_loclist(oldp, newp);
+ } else {
+ copy_loclist_stack(oldp, newp);
+ }
newp->w_localdir = (oldp->w_localdir == NULL)
? NULL : vim_strsave(oldp->w_localdir);
/* copy tagstack and folds */
for (i = 0; i < oldp->w_tagstacklen; i++) {
- newp->w_tagstack[i] = oldp->w_tagstack[i];
- if (newp->w_tagstack[i].tagname != NULL)
- newp->w_tagstack[i].tagname =
- vim_strsave(newp->w_tagstack[i].tagname);
+ taggy_T *tag = &newp->w_tagstack[i];
+ *tag = oldp->w_tagstack[i];
+ if (tag->tagname != NULL) {
+ tag->tagname = vim_strsave(tag->tagname);
+ }
+ if (tag->user_data != NULL) {
+ tag->user_data = vim_strsave(tag->user_data);
+ }
}
newp->w_tagstackidx = oldp->w_tagstackidx;
newp->w_tagstacklen = oldp->w_tagstacklen;
@@ -1558,7 +1626,7 @@ static void win_init_some(win_T *newp, win_T *oldp)
/// Check if "win" is a pointer to an existing window in the current tabpage.
///
/// @param win window to check
-bool win_valid(win_T *win) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
+bool win_valid(const win_T *win) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
if (win == NULL) {
return false;
@@ -1623,8 +1691,9 @@ make_windows (
maxcount = (curwin->w_width + curwin->w_vsep_width
- (p_wiw - p_wmw)) / (p_wmw + 1);
} else {
- /* Each window needs at least 'winminheight' lines and a status line. */
- maxcount = (curwin->w_height + curwin->w_status_height
+ // Each window needs at least 'winminheight' lines and a status line.
+ maxcount = (curwin->w_height + curwin->w_winbar_height
+ + curwin->w_status_height
- (p_wh - p_wmh)) / (p_wmh + STATUS_HEIGHT);
}
@@ -2401,6 +2470,7 @@ int win_close(win_T *win, bool free_buf)
bool help_window = false;
tabpage_T *prev_curtab = curtab;
frame_T *win_frame = win->w_floating ? NULL : win->w_frame->fr_parent;
+ const bool had_diffmode = win->w_p_diff;
if (last_window() && !win->w_floating) {
EMSG(_("E444: Cannot close last window"));
@@ -2470,9 +2540,10 @@ int win_close(win_T *win, bool free_buf)
return FAIL;
}
win->w_closing = true;
- apply_autocmds(EVENT_WINLEAVE, NULL, NULL, FALSE, curbuf);
- if (!win_valid(win))
+ apply_autocmds(EVENT_WINLEAVE, NULL, NULL, false, curbuf);
+ if (!win_valid(win)) {
return FAIL;
+ }
win->w_closing = false;
if (last_window())
return FAIL;
@@ -2502,6 +2573,12 @@ int win_close(win_T *win, bool free_buf)
}
}
+ // Fire WinClosed just before starting to free window-related resources.
+ do_autocmd_winclosed(win);
+ // autocmd may have freed the window already.
+ if (!win_valid_any_tab(win)) {
+ return OK;
+ }
/* Free independent synblock before the buffer is freed. */
if (win->w_buffer != NULL)
@@ -2544,6 +2621,7 @@ int win_close(win_T *win, bool free_buf)
win_close_othertab(win, false, prev_curtab);
return FAIL;
}
+
// Autocommands may have closed the window already, or closed the only
// other window or moved to another tab page.
if (!win_valid(win) || (!win->w_floating && last_window())
@@ -2553,8 +2631,9 @@ int win_close(win_T *win, bool free_buf)
// let terminal buffers know that this window dimensions may be ignored
win->w_closing = true;
- /* Free the memory used for the window and get the window that received
- * the screen space. */
+
+ // Free the memory used for the window and get the window that received
+ // the screen space.
wp = win_free_mem(win, &dir, NULL);
if (help_window) {
@@ -2625,11 +2704,41 @@ int win_close(win_T *win, bool free_buf)
if (help_window)
restore_snapshot(SNAP_HELP_IDX, close_curwin);
+ // If the window had 'diff' set and now there is only one window left in
+ // the tab page with 'diff' set, and "closeoff" is in 'diffopt', then
+ // execute ":diffoff!".
+ if (diffopt_closeoff() && had_diffmode && curtab == prev_curtab) {
+ int diffcount = 0;
+
+ FOR_ALL_WINDOWS_IN_TAB(dwin, curtab) {
+ if (dwin->w_p_diff) {
+ diffcount++;
+ }
+ }
+ if (diffcount == 1) {
+ do_cmdline_cmd("diffoff!");
+ }
+ }
+
curwin->w_pos_changed = true;
redraw_all_later(NOT_VALID);
return OK;
}
+static void do_autocmd_winclosed(win_T *win)
+ FUNC_ATTR_NONNULL_ALL
+{
+ static bool recursive = false;
+ if (recursive || !has_event(EVENT_WINCLOSED)) {
+ return;
+ }
+ recursive = true;
+ char_u winid[NUMBUFLEN];
+ vim_snprintf((char *)winid, sizeof(winid), "%i", win->handle);
+ apply_autocmds(EVENT_WINCLOSED, winid, winid, false, win->w_buffer);
+ recursive = false;
+}
+
/*
* Close window "win" in tab page "tp", which is not the current tab page.
* This may be the last window in that tab page and result in closing the tab,
@@ -2650,6 +2759,13 @@ void win_close_othertab(win_T *win, int free_buf, tabpage_T *tp)
return; // window is already being closed
}
+ // Fire WinClosed just before starting to free window-related resources.
+ do_autocmd_winclosed(win);
+ // autocmd may have freed the window already.
+ if (!win_valid_any_tab(win)) {
+ return;
+ }
+
if (win->w_buffer != NULL) {
// Close the link to the buffer.
close_buffer(win, win->w_buffer, free_buf ? DOBUF_UNLOAD : 0, false);
@@ -3031,15 +3147,18 @@ frame_new_height (
int wfh /* obey 'winfixheight' when there is a choice;
may cause the height not to be set */
)
+ FUNC_ATTR_NONNULL_ALL
{
frame_T *frp;
int extra_lines;
int h;
if (topfrp->fr_win != NULL) {
- /* Simple case: just one window. */
+ // Simple case: just one window.
win_new_height(topfrp->fr_win,
- height - topfrp->fr_win->w_status_height);
+ height
+ - topfrp->fr_win->w_status_height
+ - topfrp->fr_win->w_winbar_height);
} else if (topfrp->fr_layout == FR_ROW) {
do {
// All frames in this row get the same new height.
@@ -3344,8 +3463,10 @@ static void frame_fix_width(win_T *wp)
* Set frame height from the window it contains.
*/
static void frame_fix_height(win_T *wp)
+ FUNC_ATTR_NONNULL_ALL
{
- wp->w_frame->fr_height = wp->w_height + wp->w_status_height;
+ wp->w_frame->fr_height =
+ wp->w_height + wp->w_status_height + wp->w_winbar_height;
}
/*
@@ -3362,14 +3483,18 @@ static int frame_minheight(frame_T *topfrp, win_T *next_curwin)
int n;
if (topfrp->fr_win != NULL) {
- if (topfrp->fr_win == next_curwin)
+ if (topfrp->fr_win == next_curwin) {
m = p_wh + topfrp->fr_win->w_status_height;
- else {
- /* window: minimal height of the window plus status line */
+ } else {
+ // window: minimal height of the window plus status line
m = p_wmh + topfrp->fr_win->w_status_height;
- /* Current window is minimal one line high */
- if (p_wmh == 0 && topfrp->fr_win == curwin && next_curwin == NULL)
- ++m;
+ if (topfrp->fr_win == curwin && next_curwin == NULL) {
+ // Current window is minimal one line high and WinBar is visible.
+ if (p_wmh == 0) {
+ m++;
+ }
+ m += curwin->w_winbar_height;
+ }
}
} else if (topfrp->fr_layout == FR_ROW) {
/* get the minimal height from each frame in this row */
@@ -3651,6 +3776,10 @@ void free_tabpage(tabpage_T *tp)
hash_init(&tp->tp_vars->dv_hashtab);
unref_var_dict(tp->tp_vars);
+ if (tp == lastused_tabpage) {
+ lastused_tabpage = NULL;
+ }
+
xfree(tp->tp_localdir);
xfree(tp);
}
@@ -3710,6 +3839,8 @@ int win_new_tabpage(int after, char_u *filename)
tabpage_check_windows(tp);
+ lastused_tabpage = tp;
+
apply_autocmds(EVENT_WINNEW, NULL, NULL, false, curbuf);
apply_autocmds(EVENT_WINENTER, NULL, NULL, false, curbuf);
apply_autocmds(EVENT_TABNEW, filename, filename, false, curbuf);
@@ -3936,6 +4067,7 @@ static void enter_tabpage(tabpage_T *tp, buf_T *old_curbuf, int trigger_enter_au
if (curtab->tp_old_Columns != Columns && starting == 0)
shell_new_columns(); /* update window widths */
+ lastused_tabpage = old_curtab;
/* Apply autocommands after updating the display, when 'rows' and
* 'columns' have been set correctly. */
@@ -4055,6 +4187,15 @@ void goto_tabpage_tp(tabpage_T *tp, int trigger_enter_autocmds, int trigger_leav
}
}
+// Go to the last accessed tab page, if there is one.
+void goto_tabpage_lastused(void)
+{
+ int index = tabpage_index(lastused_tabpage);
+ if (index < tabpage_index(NULL)) {
+ goto_tabpage(index);
+ }
+}
+
/*
* Enter window "wp" in tab page "tp".
* Also updates the GUI tab.
@@ -4332,9 +4473,10 @@ static void win_goto_hor(bool left, long count)
}
}
-/*
- * Make window "wp" the current window.
- */
+/// Make window `wp` the current window.
+///
+/// @warning Autocmds may close the window immediately, so caller must check
+/// win_valid(wp).
void win_enter(win_T *wp, bool undo_sync)
{
win_enter_ext(wp, undo_sync, false, false, true, true);
@@ -4573,6 +4715,11 @@ static win_T *win_alloc(win_T *after, int hidden)
new_wp->w_scbind_pos = 1;
new_wp->w_floating = 0;
new_wp->w_float_config = FLOAT_CONFIG_INIT;
+ new_wp->w_viewport_invalid = true;
+
+ // use global option for global-local options
+ new_wp->w_p_so = -1;
+ new_wp->w_p_siso = -1;
/* We won't calculate w_fraction until resizing the window */
new_wp->w_fraction = 0;
@@ -4626,8 +4773,10 @@ win_free (
xfree(wp->w_lines);
- for (i = 0; i < wp->w_tagstacklen; ++i)
+ for (i = 0; i < wp->w_tagstacklen; i++) {
xfree(wp->w_tagstack[i].tagname);
+ xfree(wp->w_tagstack[i].user_data);
+ }
xfree(wp->w_localdir);
@@ -4645,6 +4794,7 @@ win_free (
qf_free_all(wp);
+ remove_winbar(wp);
xfree(wp->w_p_cc_cols);
@@ -4911,7 +5061,9 @@ static void frame_comp_pos(frame_T *topfrp, int *row, int *col)
wp->w_redr_status = true;
wp->w_pos_changed = true;
}
- *row += wp->w_height + wp->w_status_height;
+ // WinBar will not show if the window height is zero
+ const int h = wp->w_height + wp->w_winbar_height + wp->w_status_height;
+ *row += h > topfrp->fr_height ? topfrp->fr_height : h;
*col += wp->w_width + wp->w_vsep_width;
} else {
startrow = *row;
@@ -4944,12 +5096,15 @@ void win_setheight(int height)
void win_setheight_win(int height, win_T *win)
{
if (win == curwin) {
- /* Always keep current window at least one line high, even when
- * 'winminheight' is zero. */
- if (height < p_wmh)
+ // Always keep current window at least one line high, even when
+ // 'winminheight' is zero.
+ if (height < p_wmh) {
height = p_wmh;
- if (height == 0)
+ }
+ if (height == 0) {
height = 1;
+ }
+ height += curwin->w_winbar_height;
}
if (win->w_floating) {
@@ -5046,7 +5201,7 @@ static void frame_setheight(frame_T *curfrp, int height)
} else {
win_T *wp = lastwin_nofloating();
room_cmdline = Rows - p_ch - (wp->w_winrow
- + wp->w_height +
+ + wp->w_height + wp->w_winbar_height +
wp->w_status_height);
if (room_cmdline < 0) {
room_cmdline = 0;
@@ -5571,11 +5726,10 @@ void set_fraction(win_T *wp)
}
}
-/*
- * Set the height of a window.
- * This takes care of the things inside the window, not what happens to the
- * window position, the frame or to other windows.
- */
+// Set the height of a window.
+// "height" excludes any window toolbar.
+// This takes care of the things inside the window, not what happens to the
+// window position, the frame or to other windows.
void win_new_height(win_T *wp, int height)
{
// Don't want a negative height. Happens when splitting a tiny window.
@@ -5598,10 +5752,14 @@ void scroll_to_fraction(win_T *wp, int prev_height)
int sline, line_size;
int height = wp->w_height_inner;
- // Don't change w_topline when height is zero. Don't set w_topline when
- // 'scrollbind' is set and this isn't the current window.
+ // Don't change w_topline in any of these cases:
+ // - window height is 0
+ // - 'scrollbind' is set and this isn't the current window
+ // - window height is sufficient to display the whole buffer and first line
+ // is visible.
if (height > 0
&& (!wp->w_p_scb || wp == curwin)
+ && (height < wp->w_buffer->b_ml.ml_line_count || wp->w_topline > 1)
) {
/*
* Find a value for w_topline that shows the cursor at the same
@@ -5678,9 +5836,10 @@ void scroll_to_fraction(win_T *wp, int prev_height)
}
if (wp == curwin) {
- if (p_so)
+ if (get_scrolloff_value()) {
update_topline();
- curs_columns(FALSE); /* validate w_wrow */
+ }
+ curs_columns(false); // validate w_wrow
}
if (prev_height > 0) {
wp->w_prev_fraction_row = wp->w_wrow;
@@ -5973,10 +6132,20 @@ file_name_in_line (
if (file_lnum != NULL) {
char_u *p;
+ const char *line_english = " line ";
+ const char *line_transl = _(line_msg);
// Get the number after the file name and a separator character.
+ // Also accept " line 999" with and without the same translation as
+ // used in last_set_msg().
p = ptr + len;
- p = skipwhite(p);
+ if (STRNCMP(p, line_english, STRLEN(line_english)) == 0) {
+ p += STRLEN(line_english);
+ } else if (STRNCMP(p, line_transl, STRLEN(line_transl)) == 0) {
+ p += STRLEN(line_transl);
+ } else {
+ p = skipwhite(p);
+ }
if (*p != NUL) {
if (!isdigit(*p)) {
p++; // skip the separator
@@ -6392,10 +6561,12 @@ void restore_buffer(bufref_T *save_curbuf)
/// @param[in] id a desired ID 'id' can be specified
/// (greater than or equal to 1). -1 must be specified if no
/// particular ID is desired
+/// @param[in] conceal_char pointer to conceal replacement char
/// @return ID of added match, -1 on failure.
int match_add(win_T *wp, const char *const grp, const char *const pat,
int prio, int id, list_T *pos_list,
const char *const conceal_char)
+ FUNC_ATTR_NONNULL_ARG(1, 2)
{
matchitem_T *cur;
matchitem_T *prev;
@@ -6432,7 +6603,7 @@ int match_add(win_T *wp, const char *const grp, const char *const pat,
return -1;
}
- /* Find available match ID. */
+ // Find available match ID.
while (id == -1) {
cur = wp->w_match_head;
while (cur != NULL && cur->id != wp->w_next_match_id)
@@ -6442,7 +6613,7 @@ int match_add(win_T *wp, const char *const grp, const char *const pat,
wp->w_next_match_id++;
}
- /* Build new match. */
+ // Build new match.
m = xcalloc(1, sizeof(matchitem_T));
m->id = id;
m->priority = prio;
@@ -6550,9 +6721,9 @@ int match_add(win_T *wp, const char *const grp, const char *const pat,
rtype = VALID;
}
}
-
- /* Insert new match. The match list is in ascending order with regard to
- * the match priorities. */
+
+ // Insert new match. The match list is in ascending order with regard to
+ // the match priorities.
cur = wp->w_match_head;
prev = cur;
while (cur != NULL && prio >= cur->priority) {
@@ -6565,7 +6736,7 @@ int match_add(win_T *wp, const char *const grp, const char *const pat,
prev->next = m;
m->next = cur;
- redraw_later(rtype);
+ redraw_win_later(wp, rtype);
return id;
fail:
@@ -6623,7 +6794,7 @@ int match_delete(win_T *wp, int id, int perr)
rtype = VALID;
}
xfree(cur);
- redraw_later(rtype);
+ redraw_win_later(wp, rtype);
return 0;
}
@@ -6641,7 +6812,7 @@ void clear_matches(win_T *wp)
xfree(wp->w_match_head);
wp->w_match_head = m;
}
- redraw_later(SOME_VALID);
+ redraw_win_later(wp, SOME_VALID);
}
/*
@@ -6856,7 +7027,7 @@ void get_framelayout(const frame_T *fr, list_T *l, bool outer)
}
}
-void win_ui_flush_positions(void)
+void win_ui_flush(void)
{
FOR_ALL_TAB_WINDOWS(tp, wp) {
if (wp->w_pos_changed && wp->w_grid.chars != NULL) {
@@ -6867,6 +7038,9 @@ void win_ui_flush_positions(void)
}
wp->w_pos_changed = false;
}
+ if (tp == curtab) {
+ ui_ext_win_viewport(wp);
+ }
}
}
diff --git a/src/nvim/xdiff/xdiff.h b/src/nvim/xdiff/xdiff.h
index bc26fb64fd..8ff4a05bfb 100644
--- a/src/nvim/xdiff/xdiff.h
+++ b/src/nvim/xdiff/xdiff.h
@@ -25,9 +25,9 @@
#ifdef __cplusplus
extern "C" {
-#endif /* #ifdef __cplusplus */
+#endif // #ifdef __cplusplus
-/* xpparm_t.flags */
+// xpparm_t.flags
#define XDF_NEED_MINIMAL (1 << 0)
#define XDF_IGNORE_WHITESPACE (1 << 1)
@@ -48,22 +48,22 @@ extern "C" {
#define XDF_INDENT_HEURISTIC (1 << 23)
-/* xdemitconf_t.flags */
+// xdemitconf_t.flags
#define XDL_EMIT_FUNCNAMES (1 << 0)
#define XDL_EMIT_FUNCCONTEXT (1 << 2)
-/* merge simplification levels */
+// merge simplification levels
#define XDL_MERGE_MINIMAL 0
#define XDL_MERGE_EAGER 1
#define XDL_MERGE_ZEALOUS 2
#define XDL_MERGE_ZEALOUS_ALNUM 3
-/* merge favor modes */
+// merge favor modes
#define XDL_MERGE_FAVOR_OURS 1
#define XDL_MERGE_FAVOR_THEIRS 2
#define XDL_MERGE_FAVOR_UNION 3
-/* merge output styles */
+// merge output styles
#define XDL_MERGE_DIFF3 1
typedef struct s_mmfile {
@@ -79,7 +79,7 @@ typedef struct s_mmbuffer {
typedef struct s_xpparam {
unsigned long flags;
- /* See Documentation/diff-options.txt. */
+ // See Documentation/diff-options.txt.
char **anchors;
size_t anchors_nr;
} xpparam_t;
@@ -126,9 +126,9 @@ typedef struct s_xmparam {
int level;
int favor;
int style;
- const char *ancestor; /* label for orig */
- const char *file1; /* label for mf1 */
- const char *file2; /* label for mf2 */
+ const char *ancestor; // label for orig
+ const char *file1; // label for mf1
+ const char *file2; // label for mf2
} xmparam_t;
#define DEFAULT_CONFLICT_MARKER_SIZE 7
@@ -138,6 +138,6 @@ int xdl_merge(mmfile_t *orig, mmfile_t *mf1, mmfile_t *mf2,
#ifdef __cplusplus
}
-#endif /* #ifdef __cplusplus */
+#endif // #ifdef __cplusplus
-#endif /* #if !defined(XDIFF_H) */
+#endif // #if !defined(XDIFF_H)
diff --git a/src/nvim/xdiff/xdiffi.c b/src/nvim/xdiff/xdiffi.c
index 96d5277027..3806903986 100644
--- a/src/nvim/xdiff/xdiffi.c
+++ b/src/nvim/xdiff/xdiffi.c
@@ -418,24 +418,24 @@ static int xget_indent(xrecord_t *rec)
ret += 1;
else if (c == '\t')
ret += 8 - ret % 8;
- /* ignore other whitespace characters */
+ // ignore other whitespace characters
if (ret >= MAX_INDENT)
return MAX_INDENT;
}
- /* The line contains only whitespace. */
+ // The line contains only whitespace.
return -1;
}
/*
- * If more than this number of consecutive blank rows are found, just return this
- * value. This avoids requiring O(N^2) work for pathological cases, and also
- * ensures that the output of score_split fits in an int.
+ * If more than this number of consecutive blank rows are found, just return
+ * this value. This avoids requiring O(N^2) work for pathological cases, and
+ * also ensures that the output of score_split fits in an int.
*/
#define MAX_BLANKS 20
-/* Characteristics measured about a hypothetical split position. */
+// Characteristics measured about a hypothetical split position.
struct split_measurement {
/*
* Is the split at the end of the file (aside from any blank lines)?
@@ -472,10 +472,10 @@ struct split_measurement {
};
struct split_score {
- /* The effective indent of this split (smaller is preferred). */
+ // The effective indent of this split (smaller is preferred).
int effective_indent;
- /* Penalty for this split (smaller is preferred). */
+ // Penalty for this split (smaller is preferred).
int penalty;
};
@@ -534,16 +534,16 @@ static void measure_split(const xdfile_t *xdf, long split,
* integer math.
*/
-/* Penalty if there are no non-blank lines before the split */
+// Penalty if there are no non-blank lines before the split
#define START_OF_FILE_PENALTY 1
-/* Penalty if there are no non-blank lines after the split */
+// Penalty if there are no non-blank lines after the split
#define END_OF_FILE_PENALTY 21
-/* Multiplier for the number of blank lines around the split */
+// Multiplier for the number of blank lines around the split
#define TOTAL_BLANK_WEIGHT (-30)
-/* Multiplier for the number of blank lines after the split */
+// Multiplier for the number of blank lines after the split
#define POST_BLANK_WEIGHT 6
/*
@@ -610,7 +610,7 @@ static void score_add_split(const struct split_measurement *m, struct split_scor
post_blank = (m->indent == -1) ? 1 + m->post_blank : 0;
total_blank = m->pre_blank + post_blank;
- /* Penalties based on nearby blank lines: */
+ // Penalties based on nearby blank lines:
s->penalty += TOTAL_BLANK_WEIGHT * total_blank;
s->penalty += POST_BLANK_WEIGHT * post_blank;
@@ -621,13 +621,13 @@ static void score_add_split(const struct split_measurement *m, struct split_scor
any_blanks = (total_blank != 0);
- /* Note that the effective indent is -1 at the end of the file: */
+ // Note that the effective indent is -1 at the end of the file:
s->effective_indent += indent;
if (indent == -1) {
- /* No additional adjustments needed. */
+ // No additional adjustments needed.
} else if (m->pre_indent == -1) {
- /* No additional adjustments needed. */
+ // No additional adjustments needed.
} else if (indent > m->pre_indent) {
/*
* The line is indented more than its predecessor.
@@ -669,7 +669,7 @@ static void score_add_split(const struct split_measurement *m, struct split_scor
static int score_cmp(struct split_score *s1, struct split_score *s2)
{
- /* -1 if s1.effective_indent < s2->effective_indent, etc. */
+ // -1 if s1.effective_indent < s2->effective_indent, etc.
int cmp_indents = ((s1->effective_indent > s2->effective_indent) -
(s1->effective_indent < s2->effective_indent));
@@ -809,7 +809,7 @@ int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags) {
group_init(xdfo, &go);
while (1) {
- /* If the group is empty in the to-be-compacted file, skip it: */
+ // If the group is empty in the to-be-compacted file, skip it:
if (g.end == g.start)
goto next;
@@ -828,7 +828,7 @@ int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags) {
*/
end_matching_other = -1;
- /* Shift the group backward as much as possible: */
+ // Shift the group backward as much as possible:
while (!group_slide_up(xdf, &g, flags))
if (group_previous(xdfo, &go))
xdl_bug("group sync broken sliding up");
@@ -842,7 +842,7 @@ int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags) {
if (go.end > go.start)
end_matching_other = g.end;
- /* Now shift the group forward as far as possible: */
+ // Now shift the group forward as far as possible:
while (1) {
if (group_slide_down(xdf, &g, flags))
break;
@@ -863,7 +863,7 @@ int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags) {
*/
if (g.end == earliest_end) {
- /* no shifting was possible */
+ // no shifting was possible
} else if (end_matching_other != -1) {
/*
* Move the possibly merged group of changes back to line
@@ -921,7 +921,7 @@ int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags) {
}
next:
- /* Move past the just-processed group: */
+ // Move past the just-processed group:
if (group_next(xdf, &g))
break;
if (group_next(xdfo, &go))
diff --git a/src/nvim/xdiff/xdiffi.h b/src/nvim/xdiff/xdiffi.h
index 8f1c7c8b04..467a1e85cd 100644
--- a/src/nvim/xdiff/xdiffi.h
+++ b/src/nvim/xdiff/xdiffi.h
@@ -61,4 +61,4 @@ int xdl_do_patience_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
int xdl_do_histogram_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
xdfenv_t *env);
-#endif /* #if !defined(XDIFFI_H) */
+#endif // #if !defined(XDIFFI_H)
diff --git a/src/nvim/xdiff/xemit.c b/src/nvim/xdiff/xemit.c
index d8a6f1ed38..f1a45139cc 100644
--- a/src/nvim/xdiff/xemit.c
+++ b/src/nvim/xdiff/xemit.c
@@ -54,9 +54,9 @@ xdchange_t *xdl_get_hunk(xdchange_t **xscr, xdemitconf_t const *xecfg)
xdchange_t *xch, *xchp, *lxch;
long max_common = 2 * xecfg->ctxlen + xecfg->interhunkctxlen;
long max_ignorable = xecfg->ctxlen;
- unsigned long ignored = 0; /* number of ignored blank lines */
+ unsigned long ignored = 0; // number of ignored blank lines
- /* remove ignorable changes that are too far before other changes */
+ // remove ignorable changes that are too far before other changes
for (xchp = *xscr; xchp && xchp->ignore; xchp = xchp->next) {
xch = xchp->next;
@@ -99,9 +99,9 @@ xdchange_t *xdl_get_hunk(xdchange_t **xscr, xdemitconf_t const *xecfg)
static long def_ff(const char *rec, long len, char *buf, long sz, void *priv UNUSED)
{
if (len > 0 &&
- (isalpha((unsigned char)*rec) || /* identifier? */
- *rec == '_' || /* also identifier? */
- *rec == '$')) { /* identifiers from VMS and other esoterico */
+ (isalpha((unsigned char)*rec) || // identifier?
+ *rec == '_' || // also identifier?
+ *rec == '$')) { // identifiers from VMS and other esoterico
if (len > sz)
len = sz;
while (0 < len && isspace((unsigned char)rec[len - 1]))
@@ -197,7 +197,7 @@ int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
if (xecfg->flags & XDL_EMIT_FUNCCONTEXT) {
long fs1, i1 = xch->i1;
- /* Appended chunk? */
+ // Appended chunk?
if (i1 >= xe->xdf1.nrec) {
long i2 = xch->i2;
diff --git a/src/nvim/xdiff/xemit.h b/src/nvim/xdiff/xemit.h
index 1b9887e670..3ce7e3dd50 100644
--- a/src/nvim/xdiff/xemit.h
+++ b/src/nvim/xdiff/xemit.h
@@ -33,4 +33,4 @@ int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
-#endif /* #if !defined(XEMIT_H) */
+#endif // #if !defined(XEMIT_H)
diff --git a/src/nvim/xdiff/xhistogram.c b/src/nvim/xdiff/xhistogram.c
index 3fb8974dd4..28cf8258e5 100644
--- a/src/nvim/xdiff/xhistogram.c
+++ b/src/nvim/xdiff/xhistogram.c
@@ -55,8 +55,8 @@ struct histindex {
struct record {
unsigned int ptr, cnt;
struct record *next;
- } **records, /* an occurrence */
- **line_map; /* map of line to record chain */
+ } **records, // an occurrence
+ **line_map; // map of line to record chain
chastore_t rcha;
unsigned int *next_ptrs;
unsigned int table_bits,
@@ -128,7 +128,7 @@ static int scanA(struct histindex *index, int line1, int count1)
*/
NEXT_PTR(index, ptr) = rec->ptr;
rec->ptr = ptr;
- /* cap rec->cnt at MAX_CNT */
+ // cap rec->cnt at MAX_CNT
rec->cnt = XDL_MIN(MAX_CNT, rec->cnt + 1);
LINE_MAP(index, ptr) = rec;
goto continue_scan;
@@ -154,7 +154,7 @@ static int scanA(struct histindex *index, int line1, int count1)
LINE_MAP(index, ptr) = rec;
continue_scan:
- ; /* no op */
+ ; // no op
}
return 0;
@@ -266,7 +266,7 @@ static int find_lcs(xpparam_t const *xpp, xdfenv_t *env,
index.records = NULL;
index.line_map = NULL;
- /* in case of early xdl_cha_free() */
+ // in case of early xdl_cha_free()
index.rcha.head = NULL;
index.table_bits = xdl_hashbits(count1);
@@ -288,7 +288,7 @@ static int find_lcs(xpparam_t const *xpp, xdfenv_t *env,
goto cleanup;
memset(index.next_ptrs, 0, sz);
- /* lines / 4 + 1 comes from xprepare.c:xdl_prepare_ctx() */
+ // lines / 4 + 1 comes from xprepare.c:xdl_prepare_ctx()
if (xdl_cha_init(&index.rcha, sizeof(struct record), count1 / 4 + 1) < 0)
goto cleanup;
diff --git a/src/nvim/xdiff/xinclude.h b/src/nvim/xdiff/xinclude.h
index 46b8608314..5a359d1431 100644
--- a/src/nvim/xdiff/xinclude.h
+++ b/src/nvim/xdiff/xinclude.h
@@ -20,13 +20,13 @@
*
*/
-/* defines HAVE_ATTRIBUTE_UNUSED */
+// defines HAVE_ATTRIBUTE_UNUSED
#ifdef HAVE_CONFIG_H
# include "../auto/config.h"
#endif
-/* Mark unused function arguments with UNUSED, so that gcc -Wunused-parameter
- * can be used to check for mistakes. */
+// Mark unused function arguments with UNUSED, so that gcc -Wunused-parameter
+// can be used to check for mistakes.
#ifdef HAVE_ATTRIBUTE_UNUSED
# define UNUSED __attribute__((unused))
#else
@@ -58,4 +58,4 @@
#include "xemit.h"
-#endif /* #if !defined(XINCLUDE_H) */
+#endif // #if !defined(XINCLUDE_H)
diff --git a/src/nvim/xdiff/xmacros.h b/src/nvim/xdiff/xmacros.h
index 2809a28ca9..1167ebbb05 100644
--- a/src/nvim/xdiff/xmacros.h
+++ b/src/nvim/xdiff/xmacros.h
@@ -51,4 +51,4 @@ do { \
} while (0)
-#endif /* #if !defined(XMACROS_H) */
+#endif // #if !defined(XMACROS_H)
diff --git a/src/nvim/xdiff/xpatience.c b/src/nvim/xdiff/xpatience.c
index 2c65aac386..f6c84c67d8 100644
--- a/src/nvim/xdiff/xpatience.c
+++ b/src/nvim/xdiff/xpatience.c
@@ -69,7 +69,7 @@ struct hashmap {
*/
unsigned anchor : 1;
} *entries, *first, *last;
- /* were common records found? */
+ // were common records found?
unsigned long has_matches;
mmfile_t *file1, *file2;
xdfenv_t *env;
@@ -86,7 +86,7 @@ static int is_anchor(xpparam_t const *xpp, const char *line)
return 0;
}
-/* The argument "pass" is 1 for the first file, 2 for the second. */
+// The argument "pass" is 1 for the first file, 2 for the second.
static void insert_record(xpparam_t const *xpp, int line, struct hashmap *map,
int pass)
{
@@ -155,7 +155,7 @@ static int fill_hashmap(mmfile_t *file1, mmfile_t *file2,
result->xpp = xpp;
result->env = env;
- /* We know exactly how large we want the hash map */
+ // We know exactly how large we want the hash map
result->alloc = count1 * 2;
result->entries = (struct entry *)
xdl_malloc(result->alloc * sizeof(struct entry));
@@ -163,11 +163,11 @@ static int fill_hashmap(mmfile_t *file1, mmfile_t *file2,
return -1;
memset(result->entries, 0, result->alloc * sizeof(struct entry));
- /* First, fill with entries from the first file */
+ // First, fill with entries from the first file
while (count1--)
insert_record(xpp, line1++, result, 1);
- /* Then search for matches in the second file */
+ // Then search for matches in the second file
while (count2--)
insert_record(xpp, line2++, result, 2);
@@ -185,13 +185,13 @@ static int binary_search(struct entry **sequence, int longest,
while (left + 1 < right) {
int middle = left + (right - left) / 2;
- /* by construction, no two entries can be equal */
+ // by construction, no two entries can be equal
if (sequence[middle]->line2 > entry->line2)
right = middle;
else
left = middle;
}
- /* return the index in "sequence", _not_ the sequence length */
+ // return the index in "sequence", _not_ the sequence length
return left;
}
@@ -216,7 +216,7 @@ static struct entry *find_longest_common_sequence(struct hashmap *map)
*/
int anchor_i = -1;
- /* Added to silence Coverity. */
+ // Added to silence Coverity.
if (sequence == NULL)
return map->first;
@@ -237,13 +237,13 @@ static struct entry *find_longest_common_sequence(struct hashmap *map)
}
}
- /* No common unique lines were found */
+ // No common unique lines were found
if (!longest) {
xdl_free(sequence);
return NULL;
}
- /* Iterate starting at the last element, adjusting the "next" members */
+ // Iterate starting at the last element, adjusting the "next" members
entry = sequence[longest - 1];
entry->next = NULL;
while (entry->previous) {
@@ -273,7 +273,7 @@ static int walk_common_sequence(struct hashmap *map, struct entry *first,
int next1, next2;
for (;;) {
- /* Try to grow the line ranges of common lines */
+ // Try to grow the line ranges of common lines
if (first) {
next1 = first->line1;
next2 = first->line2;
@@ -292,7 +292,7 @@ static int walk_common_sequence(struct hashmap *map, struct entry *first,
line2++;
}
- /* Recurse */
+ // Recurse
if (next1 > line1 || next2 > line2) {
struct hashmap submap;
@@ -343,7 +343,7 @@ static int patience_diff(mmfile_t *file1, mmfile_t *file2,
struct entry *first;
int result = 0;
- /* trivial case: one side is empty */
+ // trivial case: one side is empty
if (!count1) {
while(count2--)
env->xdf2.rchg[line2++ - 1] = 1;
@@ -359,7 +359,7 @@ static int patience_diff(mmfile_t *file1, mmfile_t *file2,
line1, count1, line2, count2))
return -1;
- /* are there any matching lines at all? */
+ // are there any matching lines at all?
if (!map.has_matches) {
while(count1--)
env->xdf1.rchg[line1++ - 1] = 1;
@@ -387,7 +387,7 @@ int xdl_do_patience_diff(mmfile_t *file1, mmfile_t *file2,
if (xdl_prepare_env(file1, file2, xpp, env) < 0)
return -1;
- /* environment is cleaned up in xdl_diff() */
+ // environment is cleaned up in xdl_diff()
return patience_diff(file1, file2, xpp, env,
1, env->xdf1.nrec, 1, env->xdf2.nrec);
}
diff --git a/src/nvim/xdiff/xprepare.h b/src/nvim/xdiff/xprepare.h
index 947d9fc1bb..b67b3b25ab 100644
--- a/src/nvim/xdiff/xprepare.h
+++ b/src/nvim/xdiff/xprepare.h
@@ -31,4 +31,4 @@ void xdl_free_env(xdfenv_t *xe);
-#endif /* #if !defined(XPREPARE_H) */
+#endif // #if !defined(XPREPARE_H)
diff --git a/src/nvim/xdiff/xtypes.h b/src/nvim/xdiff/xtypes.h
index 8442bd436e..026999c1bf 100644
--- a/src/nvim/xdiff/xtypes.h
+++ b/src/nvim/xdiff/xtypes.h
@@ -64,4 +64,4 @@ typedef struct s_xdfenv {
-#endif /* #if !defined(XTYPES_H) */
+#endif // #if !defined(XTYPES_H)
diff --git a/src/nvim/xdiff/xutils.c b/src/nvim/xdiff/xutils.c
index 25a090fb73..e8c7d2f884 100644
--- a/src/nvim/xdiff/xutils.c
+++ b/src/nvim/xdiff/xutils.c
@@ -168,7 +168,7 @@ static int ends_with_optional_cr(const char *l, long s, long i)
s--;
if (s == i)
return 1;
- /* do not ignore CR at the end of an incomplete line */
+ // do not ignore CR at the end of an incomplete line
if (complete && s == i + 1 && l[i] == '\r')
return 1;
return 0;
@@ -208,7 +208,7 @@ int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags)
} else if (flags & XDF_IGNORE_WHITESPACE_CHANGE) {
while (i1 < s1 && i2 < s2) {
if (XDL_ISSPACE(l1[i1]) && XDL_ISSPACE(l2[i2])) {
- /* Skip matching spaces and try again */
+ // Skip matching spaces and try again
while (i1 < s1 && XDL_ISSPACE(l1[i1]))
i1++;
while (i2 < s2 && XDL_ISSPACE(l2[i2]))
@@ -224,7 +224,7 @@ int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags)
i2++;
}
} else if (flags & XDF_IGNORE_CR_AT_EOL) {
- /* Find the first difference and see how the line ends */
+ // Find the first difference and see how the line ends
while (i1 < s1 && i2 < s2 && l1[i1] == l2[i2]) {
i1++;
i2++;
@@ -261,7 +261,7 @@ static unsigned long xdl_hash_record_with_whitespace(char const **data,
for (; ptr < top && *ptr != '\n'; ptr++) {
if (cr_at_eol_only) {
- /* do not ignore CR at the end of an incomplete line */
+ // do not ignore CR at the end of an incomplete line
if (*ptr == '\r' &&
(ptr + 1 < top && ptr[1] == '\n'))
continue;
@@ -274,7 +274,7 @@ static unsigned long xdl_hash_record_with_whitespace(char const **data,
ptr++;
at_eol = (top <= ptr + 1 || ptr[1] == '\n');
if (flags & XDF_IGNORE_WHITESPACE)
- ; /* already handled */
+ ; // already handled
else if (flags & XDF_IGNORE_WHITESPACE_CHANGE
&& !at_eol) {
ha += (ha << 5);
diff --git a/src/nvim/xdiff/xutils.h b/src/nvim/xdiff/xutils.h
index fba7bae03c..0bebd93022 100644
--- a/src/nvim/xdiff/xutils.h
+++ b/src/nvim/xdiff/xutils.h
@@ -44,4 +44,4 @@ int xdl_fall_back_diff(xdfenv_t *diff_env, xpparam_t const *xpp,
-#endif /* #if !defined(XUTILS_H) */
+#endif // #if !defined(XUTILS_H)
diff --git a/src/tree_sitter/LICENSE b/src/tree_sitter/LICENSE
new file mode 100644
index 0000000000..971b81f9a8
--- /dev/null
+++ b/src/tree_sitter/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2018 Max Brunsfeld
+
+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.
diff --git a/src/tree_sitter/README.md b/src/tree_sitter/README.md
new file mode 100644
index 0000000000..20cb35e7c3
--- /dev/null
+++ b/src/tree_sitter/README.md
@@ -0,0 +1,16 @@
+Tree-sitter vendor runtime
+==========================
+
+This is the vendor runtime code for treesitter.
+
+The original code can be found [here](https://github.com/tree-sitter/tree-sitter).
+
+As this code is not ours, if you find any bugs, feel free to open an issue, so that we can
+investigate and determine if this should go upstream.
+
+# Updating
+
+To update the treesitter runtime, use the `update-ts-runtime.sh` script in the `scripts` directory:
+```sh
+./scripts/update-ts-runtime.sh <commit you want to update to>
+```
diff --git a/src/tree_sitter/alloc.h b/src/tree_sitter/alloc.h
new file mode 100644
index 0000000000..d3c6b5eca8
--- /dev/null
+++ b/src/tree_sitter/alloc.h
@@ -0,0 +1,95 @@
+#ifndef TREE_SITTER_ALLOC_H_
+#define TREE_SITTER_ALLOC_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdlib.h>
+#include <stdbool.h>
+#include <stdio.h>
+
+#include "nvim/memory.h"
+
+#if 1
+
+static inline bool ts_toggle_allocation_recording(bool value) {
+ return false;
+}
+
+#define ts_malloc xmalloc
+#define ts_calloc xcalloc
+#define ts_realloc xrealloc
+#define ts_free xfree
+
+#elif defined(TREE_SITTER_TEST)
+
+void *ts_record_malloc(size_t);
+void *ts_record_calloc(size_t, size_t);
+void *ts_record_realloc(void *, size_t);
+void ts_record_free(void *);
+bool ts_toggle_allocation_recording(bool);
+
+static inline void *ts_malloc(size_t size) {
+ return ts_record_malloc(size);
+}
+
+static inline void *ts_calloc(size_t count, size_t size) {
+ return ts_record_calloc(count, size);
+}
+
+static inline void *ts_realloc(void *buffer, size_t size) {
+ return ts_record_realloc(buffer, size);
+}
+
+static inline void ts_free(void *buffer) {
+ ts_record_free(buffer);
+}
+
+#else
+
+#include <stdlib.h>
+
+static inline bool ts_toggle_allocation_recording(bool value) {
+ (void)value;
+ return false;
+}
+
+static inline void *ts_malloc(size_t size) {
+ void *result = malloc(size);
+ if (size > 0 && !result) {
+ fprintf(stderr, "tree-sitter failed to allocate %lu bytes", size);
+ exit(1);
+ }
+ return result;
+}
+
+static inline void *ts_calloc(size_t count, size_t size) {
+ void *result = calloc(count, size);
+ if (count > 0 && !result) {
+ fprintf(stderr, "tree-sitter failed to allocate %lu bytes", count * size);
+ exit(1);
+ }
+ return result;
+}
+
+static inline void *ts_realloc(void *buffer, size_t size) {
+ void *result = realloc(buffer, size);
+ if (size > 0 && !result) {
+ fprintf(stderr, "tree-sitter failed to reallocate %lu bytes", size);
+ exit(1);
+ }
+ return result;
+}
+
+static inline void ts_free(void *buffer) {
+ free(buffer);
+}
+
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // TREE_SITTER_ALLOC_H_
diff --git a/src/tree_sitter/api.h b/src/tree_sitter/api.h
new file mode 100644
index 0000000000..9d832e6ec4
--- /dev/null
+++ b/src/tree_sitter/api.h
@@ -0,0 +1,876 @@
+#ifndef TREE_SITTER_API_H_
+#define TREE_SITTER_API_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdbool.h>
+
+/****************************/
+/* Section - ABI Versioning */
+/****************************/
+
+/**
+ * The latest ABI version that is supported by the current version of the
+ * library. When Languages are generated by the Tree-sitter CLI, they are
+ * assigned an ABI version number that corresponds to the current CLI version.
+ * The Tree-sitter library is generally backwards-compatible with languages
+ * generated using older CLI versions, but is not forwards-compatible.
+ */
+#define TREE_SITTER_LANGUAGE_VERSION 11
+
+/**
+ * The earliest ABI version that is supported by the current version of the
+ * library.
+ */
+#define TREE_SITTER_MIN_COMPATIBLE_LANGUAGE_VERSION 9
+
+/*******************/
+/* Section - Types */
+/*******************/
+
+typedef uint16_t TSSymbol;
+typedef uint16_t TSFieldId;
+typedef struct TSLanguage TSLanguage;
+typedef struct TSParser TSParser;
+typedef struct TSTree TSTree;
+typedef struct TSQuery TSQuery;
+typedef struct TSQueryCursor TSQueryCursor;
+
+typedef enum {
+ TSInputEncodingUTF8,
+ TSInputEncodingUTF16,
+} TSInputEncoding;
+
+typedef enum {
+ TSSymbolTypeRegular,
+ TSSymbolTypeAnonymous,
+ TSSymbolTypeAuxiliary,
+} TSSymbolType;
+
+typedef struct {
+ uint32_t row;
+ uint32_t column;
+} TSPoint;
+
+typedef struct {
+ TSPoint start_point;
+ TSPoint end_point;
+ uint32_t start_byte;
+ uint32_t end_byte;
+} TSRange;
+
+typedef struct {
+ void *payload;
+ const char *(*read)(void *payload, uint32_t byte_index, TSPoint position, uint32_t *bytes_read);
+ TSInputEncoding encoding;
+} TSInput;
+
+typedef enum {
+ TSLogTypeParse,
+ TSLogTypeLex,
+} TSLogType;
+
+typedef struct {
+ void *payload;
+ void (*log)(void *payload, TSLogType, const char *);
+} TSLogger;
+
+typedef struct {
+ uint32_t start_byte;
+ uint32_t old_end_byte;
+ uint32_t new_end_byte;
+ TSPoint start_point;
+ TSPoint old_end_point;
+ TSPoint new_end_point;
+} TSInputEdit;
+
+typedef struct {
+ uint32_t context[4];
+ const void *id;
+ const TSTree *tree;
+} TSNode;
+
+typedef struct {
+ const void *tree;
+ const void *id;
+ uint32_t context[2];
+} TSTreeCursor;
+
+typedef struct {
+ TSNode node;
+ uint32_t index;
+} TSQueryCapture;
+
+typedef struct {
+ uint32_t id;
+ uint16_t pattern_index;
+ uint16_t capture_count;
+ const TSQueryCapture *captures;
+} TSQueryMatch;
+
+typedef enum {
+ TSQueryPredicateStepTypeDone,
+ TSQueryPredicateStepTypeCapture,
+ TSQueryPredicateStepTypeString,
+} TSQueryPredicateStepType;
+
+typedef struct {
+ TSQueryPredicateStepType type;
+ uint32_t value_id;
+} TSQueryPredicateStep;
+
+typedef enum {
+ TSQueryErrorNone = 0,
+ TSQueryErrorSyntax,
+ TSQueryErrorNodeType,
+ TSQueryErrorField,
+ TSQueryErrorCapture,
+} TSQueryError;
+
+/********************/
+/* Section - Parser */
+/********************/
+
+/**
+ * Create a new parser.
+ */
+TSParser *ts_parser_new(void);
+
+/**
+ * Delete the parser, freeing all of the memory that it used.
+ */
+void ts_parser_delete(TSParser *parser);
+
+/**
+ * Set the language that the parser should use for parsing.
+ *
+ * Returns a boolean indicating whether or not the language was successfully
+ * assigned. True means assignment succeeded. False means there was a version
+ * mismatch: the language was generated with an incompatible version of the
+ * Tree-sitter CLI. Check the language's version using `ts_language_version`
+ * and compare it to this library's `TREE_SITTER_LANGUAGE_VERSION` and
+ * `TREE_SITTER_MIN_COMPATIBLE_LANGUAGE_VERSION` constants.
+ */
+bool ts_parser_set_language(TSParser *self, const TSLanguage *language);
+
+/**
+ * Get the parser's current language.
+ */
+const TSLanguage *ts_parser_language(const TSParser *self);
+
+/**
+ * Set the ranges of text that the parser should include when parsing.
+ *
+ * By default, the parser will always include entire documents. This function
+ * allows you to parse only a *portion* of a document but still return a syntax
+ * tree whose ranges match up with the document as a whole. You can also pass
+ * multiple disjoint ranges.
+ *
+ * The second and third parameters specify the location and length of an array
+ * of ranges. The parser does *not* take ownership of these ranges; it copies
+ * the data, so it doesn't matter how these ranges are allocated.
+ *
+ * If `length` is zero, then the entire document will be parsed. Otherwise,
+ * the given ranges must be ordered from earliest to latest in the document,
+ * and they must not overlap. That is, the following must hold for all
+ * `i` < `length - 1`:
+ *
+ * ranges[i].end_byte <= ranges[i + 1].start_byte
+ *
+ * If this requirement is not satisfied, the operation will fail, the ranges
+ * will not be assigned, and this function will return `false`. On success,
+ * this function returns `true`
+ */
+bool ts_parser_set_included_ranges(
+ TSParser *self,
+ const TSRange *ranges,
+ uint32_t length
+);
+
+/**
+ * Get the ranges of text that the parser will include when parsing.
+ *
+ * The returned pointer is owned by the parser. The caller should not free it
+ * or write to it. The length of the array will be written to the given
+ * `length` pointer.
+ */
+const TSRange *ts_parser_included_ranges(
+ const TSParser *self,
+ uint32_t *length
+);
+
+/**
+ * Use the parser to parse some source code and create a syntax tree.
+ *
+ * If you are parsing this document for the first time, pass `NULL` for the
+ * `old_tree` parameter. Otherwise, if you have already parsed an earlier
+ * version of this document and the document has since been edited, pass the
+ * previous syntax tree so that the unchanged parts of it can be reused.
+ * This will save time and memory. For this to work correctly, you must have
+ * already edited the old syntax tree using the `ts_tree_edit` function in a
+ * way that exactly matches the source code changes.
+ *
+ * The `TSInput` parameter lets you specify how to read the text. It has the
+ * following three fields:
+ * 1. `read`: A function to retrieve a chunk of text at a given byte offset
+ * and (row, column) position. The function should return a pointer to the
+ * text and write its length to the the `bytes_read` pointer. The parser
+ * does not take ownership of this buffer; it just borrows it until it has
+ * finished reading it. The function should write a zero value to the
+ * `bytes_read` pointer to indicate the end of the document.
+ * 2. `payload`: An arbitrary pointer that will be passed to each invocation
+ * of the `read` function.
+ * 3. `encoding`: An indication of how the text is encoded. Either
+ * `TSInputEncodingUTF8` or `TSInputEncodingUTF16`.
+ *
+ * This function returns a syntax tree on success, and `NULL` on failure. There
+ * are three possible reasons for failure:
+ * 1. The parser does not have a language assigned. Check for this using the
+ `ts_parser_language` function.
+ * 2. Parsing was cancelled due to a timeout that was set by an earlier call to
+ * the `ts_parser_set_timeout_micros` function. You can resume parsing from
+ * where the parser left out by calling `ts_parser_parse` again with the
+ * same arguments. Or you can start parsing from scratch by first calling
+ * `ts_parser_reset`.
+ * 3. Parsing was cancelled using a cancellation flag that was set by an
+ * earlier call to `ts_parser_set_cancellation_flag`. You can resume parsing
+ * from where the parser left out by calling `ts_parser_parse` again with
+ * the same arguments.
+ */
+TSTree *ts_parser_parse(
+ TSParser *self,
+ const TSTree *old_tree,
+ TSInput input
+);
+
+/**
+ * Use the parser to parse some source code stored in one contiguous buffer.
+ * The first two parameters are the same as in the `ts_parser_parse` function
+ * above. The second two parameters indicate the location of the buffer and its
+ * length in bytes.
+ */
+TSTree *ts_parser_parse_string(
+ TSParser *self,
+ const TSTree *old_tree,
+ const char *string,
+ uint32_t length
+);
+
+/**
+ * Use the parser to parse some source code stored in one contiguous buffer with
+ * a given encoding. The first four parameters work the same as in the
+ * `ts_parser_parse_string` method above. The final parameter indicates whether
+ * the text is encoded as UTF8 or UTF16.
+ */
+TSTree *ts_parser_parse_string_encoding(
+ TSParser *self,
+ const TSTree *old_tree,
+ const char *string,
+ uint32_t length,
+ TSInputEncoding encoding
+);
+
+/**
+ * Instruct the parser to start the next parse from the beginning.
+ *
+ * If the parser previously failed because of a timeout or a cancellation, then
+ * by default, it will resume where it left off on the next call to
+ * `ts_parser_parse` or other parsing functions. If you don't want to resume,
+ * and instead intend to use this parser to parse some other document, you must
+ * call `ts_parser_reset` first.
+ */
+void ts_parser_reset(TSParser *self);
+
+/**
+ * Set the maximum duration in microseconds that parsing should be allowed to
+ * take before halting.
+ *
+ * If parsing takes longer than this, it will halt early, returning NULL.
+ * See `ts_parser_parse` for more information.
+ */
+void ts_parser_set_timeout_micros(TSParser *self, uint64_t timeout);
+
+/**
+ * Get the duration in microseconds that parsing is allowed to take.
+ */
+uint64_t ts_parser_timeout_micros(const TSParser *self);
+
+/**
+ * Set the parser's current cancellation flag pointer.
+ *
+ * If a non-null pointer is assigned, then the parser will periodically read
+ * from this pointer during parsing. If it reads a non-zero value, it will
+ * halt early, returning NULL. See `ts_parser_parse` for more information.
+ */
+void ts_parser_set_cancellation_flag(TSParser *self, const size_t *flag);
+
+/**
+ * Get the parser's current cancellation flag pointer.
+ */
+const size_t *ts_parser_cancellation_flag(const TSParser *self);
+
+/**
+ * Set the logger that a parser should use during parsing.
+ *
+ * The parser does not take ownership over the logger payload. If a logger was
+ * previously assigned, the caller is responsible for releasing any memory
+ * owned by the previous logger.
+ */
+void ts_parser_set_logger(TSParser *self, TSLogger logger);
+
+/**
+ * Get the parser's current logger.
+ */
+TSLogger ts_parser_logger(const TSParser *self);
+
+/**
+ * Set the file descriptor to which the parser should write debugging graphs
+ * during parsing. The graphs are formatted in the DOT language. You may want
+ * to pipe these graphs directly to a `dot(1)` process in order to generate
+ * SVG output. You can turn off this logging by passing a negative number.
+ */
+void ts_parser_print_dot_graphs(TSParser *self, int file);
+
+/******************/
+/* Section - Tree */
+/******************/
+
+/**
+ * Create a shallow copy of the syntax tree. This is very fast.
+ *
+ * You need to copy a syntax tree in order to use it on more than one thread at
+ * a time, as syntax trees are not thread safe.
+ */
+TSTree *ts_tree_copy(const TSTree *self);
+
+/**
+ * Delete the syntax tree, freeing all of the memory that it used.
+ */
+void ts_tree_delete(TSTree *self);
+
+/**
+ * Get the root node of the syntax tree.
+ */
+TSNode ts_tree_root_node(const TSTree *self);
+
+/**
+ * Get the language that was used to parse the syntax tree.
+ */
+const TSLanguage *ts_tree_language(const TSTree *);
+
+/**
+ * Edit the syntax tree to keep it in sync with source code that has been
+ * edited.
+ *
+ * You must describe the edit both in terms of byte offsets and in terms of
+ * (row, column) coordinates.
+ */
+void ts_tree_edit(TSTree *self, const TSInputEdit *edit);
+
+/**
+ * Compare an old edited syntax tree to a new syntax tree representing the same
+ * document, returning an array of ranges whose syntactic structure has changed.
+ *
+ * For this to work correctly, the old syntax tree must have been edited such
+ * that its ranges match up to the new tree. Generally, you'll want to call
+ * this function right after calling one of the `ts_parser_parse` functions.
+ * You need to pass the old tree that was passed to parse, as well as the new
+ * tree that was returned from that function.
+ *
+ * The returned array is allocated using `malloc` and the caller is responsible
+ * for freeing it using `free`. The length of the array will be written to the
+ * given `length` pointer.
+ */
+TSRange *ts_tree_get_changed_ranges(
+ const TSTree *old_tree,
+ const TSTree *new_tree,
+ uint32_t *length
+);
+
+/**
+ * Write a DOT graph describing the syntax tree to the given file.
+ */
+void ts_tree_print_dot_graph(const TSTree *, FILE *);
+
+/******************/
+/* Section - Node */
+/******************/
+
+/**
+ * Get the node's type as a null-terminated string.
+ */
+const char *ts_node_type(TSNode);
+
+/**
+ * Get the node's type as a numerical id.
+ */
+TSSymbol ts_node_symbol(TSNode);
+
+/**
+ * Get the node's start byte.
+ */
+uint32_t ts_node_start_byte(TSNode);
+
+/**
+ * Get the node's start position in terms of rows and columns.
+ */
+TSPoint ts_node_start_point(TSNode);
+
+/**
+ * Get the node's end byte.
+ */
+uint32_t ts_node_end_byte(TSNode);
+
+/**
+ * Get the node's end position in terms of rows and columns.
+ */
+TSPoint ts_node_end_point(TSNode);
+
+/**
+ * Get an S-expression representing the node as a string.
+ *
+ * This string is allocated with `malloc` and the caller is responsible for
+ * freeing it using `free`.
+ */
+char *ts_node_string(TSNode);
+
+/**
+ * Check if the node is null. Functions like `ts_node_child` and
+ * `ts_node_next_sibling` will return a null node to indicate that no such node
+ * was found.
+ */
+bool ts_node_is_null(TSNode);
+
+/**
+ * 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.
+ */
+bool ts_node_is_named(TSNode);
+
+/**
+ * Check if the node is *missing*. Missing nodes are inserted by the parser in
+ * order to recover from certain kinds of syntax errors.
+ */
+bool ts_node_is_missing(TSNode);
+
+/**
+ * Check if the node is *extra*. Extra nodes represent things like comments,
+ * which are not required the grammar, but can appear anywhere.
+ */
+bool ts_node_is_extra(TSNode);
+
+/**
+ * Check if a syntax node has been edited.
+ */
+bool ts_node_has_changes(TSNode);
+
+/**
+ * Check if the node is a syntax error or contains any syntax errors.
+ */
+bool ts_node_has_error(TSNode);
+
+/**
+ * Get the node's immediate parent.
+ */
+TSNode ts_node_parent(TSNode);
+
+/**
+ * Get the node's child at the given index, where zero represents the first
+ * child.
+ */
+TSNode ts_node_child(TSNode, uint32_t);
+
+/**
+ * Get the node's number of children.
+ */
+uint32_t ts_node_child_count(TSNode);
+
+/**
+ * Get the node's *named* child at the given index.
+ *
+ * See also `ts_node_is_named`.
+ */
+TSNode ts_node_named_child(TSNode, uint32_t);
+
+/**
+ * Get the node's number of *named* children.
+ *
+ * See also `ts_node_is_named`.
+ */
+uint32_t ts_node_named_child_count(TSNode);
+
+/**
+ * Get the node's child with the given field name.
+ */
+TSNode ts_node_child_by_field_name(
+ TSNode self,
+ const char *field_name,
+ uint32_t field_name_length
+);
+
+/**
+ * Get the node's child with the given numerical field id.
+ *
+ * You can convert a field name to an id using the
+ * `ts_language_field_id_for_name` function.
+ */
+TSNode ts_node_child_by_field_id(TSNode, TSFieldId);
+
+/**
+ * Get the node's next / previous sibling.
+ */
+TSNode ts_node_next_sibling(TSNode);
+TSNode ts_node_prev_sibling(TSNode);
+
+/**
+ * Get the node's next / previous *named* sibling.
+ */
+TSNode ts_node_next_named_sibling(TSNode);
+TSNode ts_node_prev_named_sibling(TSNode);
+
+/**
+ * Get the node's first child that extends beyond the given byte offset.
+ */
+TSNode ts_node_first_child_for_byte(TSNode, uint32_t);
+
+/**
+ * Get the node's first named child that extends beyond the given byte offset.
+ */
+TSNode ts_node_first_named_child_for_byte(TSNode, uint32_t);
+
+/**
+ * Get the smallest node within this node that spans the given range of bytes
+ * or (row, column) positions.
+ */
+TSNode ts_node_descendant_for_byte_range(TSNode, uint32_t, uint32_t);
+TSNode ts_node_descendant_for_point_range(TSNode, TSPoint, TSPoint);
+
+/**
+ * Get the smallest named node within this node that spans the given range of
+ * bytes or (row, column) positions.
+ */
+TSNode ts_node_named_descendant_for_byte_range(TSNode, uint32_t, uint32_t);
+TSNode ts_node_named_descendant_for_point_range(TSNode, TSPoint, TSPoint);
+
+/**
+ * Edit the node to keep it in-sync with source code that has been edited.
+ *
+ * This function is only rarely needed. When you edit a syntax tree with the
+ * `ts_tree_edit` function, all of the nodes that you retrieve from the tree
+ * afterward will already reflect the edit. You only need to use `ts_node_edit`
+ * when you have a `TSNode` instance that you want to keep and continue to use
+ * after an edit.
+ */
+void ts_node_edit(TSNode *, const TSInputEdit *);
+
+/**
+ * Check if two nodes are identical.
+ */
+bool ts_node_eq(TSNode, TSNode);
+
+/************************/
+/* Section - TreeCursor */
+/************************/
+
+/**
+ * Create a new tree cursor starting from the given node.
+ *
+ * A tree cursor allows you to walk a syntax tree more efficiently than is
+ * possible using the `TSNode` functions. It is a mutable object that is always
+ * on a certain syntax node, and can be moved imperatively to different nodes.
+ */
+TSTreeCursor ts_tree_cursor_new(TSNode);
+
+/**
+ * Delete a tree cursor, freeing all of the memory that it used.
+ */
+void ts_tree_cursor_delete(TSTreeCursor *);
+
+/**
+ * Re-initialize a tree cursor to start at a different node.
+ */
+void ts_tree_cursor_reset(TSTreeCursor *, TSNode);
+
+/**
+ * Get the tree cursor's current node.
+ */
+TSNode ts_tree_cursor_current_node(const TSTreeCursor *);
+
+/**
+ * Get the field name of the tree cursor's current node.
+ *
+ * This returns `NULL` if the current node doesn't have a field.
+ * See also `ts_node_child_by_field_name`.
+ */
+const char *ts_tree_cursor_current_field_name(const TSTreeCursor *);
+
+/**
+ * Get the field name of the tree cursor's current node.
+ *
+ * This returns zero if the current node doesn't have a field.
+ * See also `ts_node_child_by_field_id`, `ts_language_field_id_for_name`.
+ */
+TSFieldId ts_tree_cursor_current_field_id(const TSTreeCursor *);
+
+/**
+ * Move the cursor to the parent of its current node.
+ *
+ * This returns `true` if the cursor successfully moved, and returns `false`
+ * if there was no parent node (the cursor was already on the root node).
+ */
+bool ts_tree_cursor_goto_parent(TSTreeCursor *);
+
+/**
+ * Move the cursor to the next sibling of its current node.
+ *
+ * This returns `true` if the cursor successfully moved, and returns `false`
+ * if there was no next sibling node.
+ */
+bool ts_tree_cursor_goto_next_sibling(TSTreeCursor *);
+
+/**
+ * Move the cursor to the first child of its current node.
+ *
+ * This returns `true` if the cursor successfully moved, and returns `false`
+ * if there were no children.
+ */
+bool ts_tree_cursor_goto_first_child(TSTreeCursor *);
+
+/**
+ * Move the cursor to the first child of its current node that extends beyond
+ * the given byte offset.
+ *
+ * This returns the index of the child node if one was found, and returns -1
+ * if no such child was found.
+ */
+int64_t ts_tree_cursor_goto_first_child_for_byte(TSTreeCursor *, uint32_t);
+
+TSTreeCursor ts_tree_cursor_copy(const TSTreeCursor *);
+
+/*******************/
+/* Section - Query */
+/*******************/
+
+/**
+ * Create a new query from a string containing one or more S-expression
+ * patterns. The query is associated with a particular language, and can
+ * only be run on syntax nodes parsed with that language.
+ *
+ * If all of the given patterns are valid, this returns a `TSQuery`.
+ * If a pattern is invalid, this returns `NULL`, and provides two pieces
+ * of information about the problem:
+ * 1. The byte offset of the error is written to the `error_offset` parameter.
+ * 2. The type of error is written to the `error_type` parameter.
+ */
+TSQuery *ts_query_new(
+ const TSLanguage *language,
+ const char *source,
+ uint32_t source_len,
+ uint32_t *error_offset,
+ TSQueryError *error_type
+);
+
+/**
+ * Delete a query, freeing all of the memory that it used.
+ */
+void ts_query_delete(TSQuery *);
+
+/**
+ * Get the number of patterns, captures, or string literals in the query.
+ */
+uint32_t ts_query_pattern_count(const TSQuery *);
+uint32_t ts_query_capture_count(const TSQuery *);
+uint32_t ts_query_string_count(const TSQuery *);
+
+/**
+ * Get the byte offset where the given pattern starts in the query's source.
+ *
+ * This can be useful when combining queries by concatenating their source
+ * code strings.
+ */
+uint32_t ts_query_start_byte_for_pattern(const TSQuery *, uint32_t);
+
+/**
+ * Get all of the predicates for the given pattern in the query.
+ *
+ * The predicates are represented as a single array of steps. There are three
+ * types of steps in this array, which correspond to the three legal values for
+ * the `type` field:
+ * - `TSQueryPredicateStepTypeCapture` - Steps with this type represent names
+ * of captures. Their `value_id` can be used with the
+ * `ts_query_capture_name_for_id` function to obtain the name of the capture.
+ * - `TSQueryPredicateStepTypeString` - Steps with this type represent literal
+ * strings. Their `value_id` can be used with the
+ * `ts_query_string_value_for_id` function to obtain their string value.
+ * - `TSQueryPredicateStepTypeDone` - Steps with this type are *sentinels*
+ * that represent the end of an individual predicate. If a pattern has two
+ * predicates, then there will be two steps with this `type` in the array.
+ */
+const TSQueryPredicateStep *ts_query_predicates_for_pattern(
+ const TSQuery *self,
+ uint32_t pattern_index,
+ uint32_t *length
+);
+
+/**
+ * Get the name and length of one of the query's captures, or one of the
+ * query's string literals. Each capture and string is associated with a
+ * numeric id based on the order that it appeared in the query's source.
+ */
+const char *ts_query_capture_name_for_id(
+ const TSQuery *,
+ uint32_t id,
+ uint32_t *length
+);
+const char *ts_query_string_value_for_id(
+ const TSQuery *,
+ uint32_t id,
+ uint32_t *length
+);
+
+/**
+ * Disable a certain capture within a query.
+ *
+ * This prevents the capture from being returned in matches, and also avoids
+ * any resource usage associated with recording the capture. Currently, there
+ * is no way to undo this.
+ */
+void ts_query_disable_capture(TSQuery *, const char *, uint32_t);
+
+/**
+ * Disable a certain pattern within a query.
+ *
+ * This prevents the pattern from matching and removes most of the overhead
+ * associated with the pattern. Currently, there is no way to undo this.
+ */
+void ts_query_disable_pattern(TSQuery *, uint32_t);
+
+/**
+ * Create a new cursor for executing a given query.
+ *
+ * The cursor stores the state that is needed to iteratively search
+ * for matches. To use the query cursor, first call `ts_query_cursor_exec`
+ * to start running a given query on a given syntax node. Then, there are
+ * two options for consuming the results of the query:
+ * 1. Repeatedly call `ts_query_cursor_next_match` to iterate over all of the
+ * the *matches* in the order that they were found. Each match contains the
+ * index of the pattern that matched, and an array of captures. Because
+ * multiple patterns can match the same set of nodes, one match may contain
+ * captures that appear *before* some of the captures from a previous match.
+ * 2. Repeatedly call `ts_query_cursor_next_capture` to iterate over all of the
+ * individual *captures* in the order that they appear. This is useful if
+ * don't care about which pattern matched, and just want a single ordered
+ * sequence of captures.
+ *
+ * If you don't care about consuming all of the results, you can stop calling
+ * `ts_query_cursor_next_match` or `ts_query_cursor_next_capture` at any point.
+ * You can then start executing another query on another node by calling
+ * `ts_query_cursor_exec` again.
+ */
+TSQueryCursor *ts_query_cursor_new(void);
+
+/**
+ * Delete a query cursor, freeing all of the memory that it used.
+ */
+void ts_query_cursor_delete(TSQueryCursor *);
+
+/**
+ * Start running a given query on a given node.
+ */
+void ts_query_cursor_exec(TSQueryCursor *, const TSQuery *, TSNode);
+
+/**
+ * Set the range of bytes or (row, column) positions in which the query
+ * will be executed.
+ */
+void ts_query_cursor_set_byte_range(TSQueryCursor *, uint32_t, uint32_t);
+void ts_query_cursor_set_point_range(TSQueryCursor *, TSPoint, TSPoint);
+
+/**
+ * Advance to the next match of the currently running query.
+ *
+ * If there is a match, write it to `*match` and return `true`.
+ * Otherwise, return `false`.
+ */
+bool ts_query_cursor_next_match(TSQueryCursor *, TSQueryMatch *match);
+void ts_query_cursor_remove_match(TSQueryCursor *, uint32_t id);
+
+/**
+ * Advance to the next capture of the currently running query.
+ *
+ * If there is a capture, write its match to `*match` and its index within
+ * the matche's capture list to `*capture_index`. Otherwise, return `false`.
+ */
+bool ts_query_cursor_next_capture(
+ TSQueryCursor *,
+ TSQueryMatch *match,
+ uint32_t *capture_index
+);
+
+/**********************/
+/* Section - Language */
+/**********************/
+
+/**
+ * Get the number of distinct node types in the language.
+ */
+uint32_t ts_language_symbol_count(const TSLanguage *);
+
+/**
+ * Get a node type string for the given numerical id.
+ */
+const char *ts_language_symbol_name(const TSLanguage *, TSSymbol);
+
+/**
+ * Get the numerical id for the given node type string.
+ */
+TSSymbol ts_language_symbol_for_name(
+ const TSLanguage *self,
+ const char *string,
+ uint32_t length,
+ bool is_named
+);
+
+/**
+ * Get the number of distinct field names in the language.
+ */
+uint32_t ts_language_field_count(const TSLanguage *);
+
+/**
+ * Get the field name string for the given numerical id.
+ */
+const char *ts_language_field_name_for_id(const TSLanguage *, TSFieldId);
+
+/**
+ * Get the numerical id for the given field name string.
+ */
+TSFieldId ts_language_field_id_for_name(const TSLanguage *, const char *, uint32_t);
+
+/**
+ * Check whether the given node type id belongs to named nodes, anonymous nodes,
+ * or a hidden nodes.
+ *
+ * See also `ts_node_is_named`. Hidden nodes are never returned from the API.
+ */
+TSSymbolType ts_language_symbol_type(const TSLanguage *, TSSymbol);
+
+/**
+ * Get the ABI version number for this language. This version number is used
+ * to ensure that languages were generated by a compatible version of
+ * Tree-sitter.
+ *
+ * See also `ts_parser_set_language`.
+ */
+uint32_t ts_language_version(const TSLanguage *);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // TREE_SITTER_API_H_
diff --git a/src/tree_sitter/array.h b/src/tree_sitter/array.h
new file mode 100644
index 0000000000..26cb8448f1
--- /dev/null
+++ b/src/tree_sitter/array.h
@@ -0,0 +1,158 @@
+#ifndef TREE_SITTER_ARRAY_H_
+#define TREE_SITTER_ARRAY_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <assert.h>
+#include <stdbool.h>
+#include "./alloc.h"
+
+#define Array(T) \
+ struct { \
+ T *contents; \
+ uint32_t size; \
+ uint32_t capacity; \
+ }
+
+#define array_init(self) \
+ ((self)->size = 0, (self)->capacity = 0, (self)->contents = NULL)
+
+#define array_new() \
+ { NULL, 0, 0 }
+
+#define array_get(self, index) \
+ (assert((uint32_t)index < (self)->size), &(self)->contents[index])
+
+#define array_front(self) array_get(self, 0)
+
+#define array_back(self) array_get(self, (self)->size - 1)
+
+#define array_clear(self) ((self)->size = 0)
+
+#define array_reserve(self, new_capacity) \
+ array__reserve((VoidArray *)(self), array__elem_size(self), new_capacity)
+
+#define array_erase(self, index) \
+ array__erase((VoidArray *)(self), array__elem_size(self), index)
+
+#define array_delete(self) array__delete((VoidArray *)self)
+
+#define array_push(self, element) \
+ (array__grow((VoidArray *)(self), 1, array__elem_size(self)), \
+ (self)->contents[(self)->size++] = (element))
+
+#define array_grow_by(self, count) \
+ (array__grow((VoidArray *)(self), count, array__elem_size(self)), \
+ memset((self)->contents + (self)->size, 0, (count) * array__elem_size(self)), \
+ (self)->size += (count))
+
+#define array_push_all(self, other) \
+ array_splice((self), (self)->size, 0, (other)->size, (other)->contents)
+
+#define array_splice(self, index, old_count, new_count, new_contents) \
+ array__splice((VoidArray *)(self), array__elem_size(self), index, old_count, \
+ new_count, new_contents)
+
+#define array_insert(self, index, element) \
+ array__splice((VoidArray *)(self), array__elem_size(self), index, 0, 1, &element)
+
+#define array_pop(self) ((self)->contents[--(self)->size])
+
+#define array_assign(self, other) \
+ array__assign((VoidArray *)(self), (const VoidArray *)(other), array__elem_size(self))
+
+// Private
+
+typedef Array(void) VoidArray;
+
+#define array__elem_size(self) sizeof(*(self)->contents)
+
+static inline void array__delete(VoidArray *self) {
+ ts_free(self->contents);
+ self->contents = NULL;
+ self->size = 0;
+ self->capacity = 0;
+}
+
+static inline void array__erase(VoidArray *self, size_t element_size,
+ uint32_t index) {
+ assert(index < self->size);
+ char *contents = (char *)self->contents;
+ memmove(contents + index * element_size, contents + (index + 1) * element_size,
+ (self->size - index - 1) * element_size);
+ self->size--;
+}
+
+static inline void array__reserve(VoidArray *self, size_t element_size, uint32_t new_capacity) {
+ if (new_capacity > self->capacity) {
+ if (self->contents) {
+ self->contents = ts_realloc(self->contents, new_capacity * element_size);
+ } else {
+ self->contents = ts_calloc(new_capacity, element_size);
+ }
+ self->capacity = new_capacity;
+ }
+}
+
+static inline void array__assign(VoidArray *self, const VoidArray *other, size_t element_size) {
+ array__reserve(self, element_size, other->size);
+ self->size = other->size;
+ memcpy(self->contents, other->contents, self->size * element_size);
+}
+
+static inline void array__grow(VoidArray *self, size_t count, size_t element_size) {
+ size_t new_size = self->size + count;
+ if (new_size > self->capacity) {
+ size_t new_capacity = self->capacity * 2;
+ if (new_capacity < 8) new_capacity = 8;
+ if (new_capacity < new_size) new_capacity = new_size;
+ array__reserve(self, element_size, new_capacity);
+ }
+}
+
+static inline void array__splice(VoidArray *self, size_t element_size,
+ uint32_t index, uint32_t old_count,
+ uint32_t new_count, const void *elements) {
+ uint32_t new_size = self->size + new_count - old_count;
+ uint32_t old_end = index + old_count;
+ uint32_t new_end = index + new_count;
+ assert(old_end <= self->size);
+
+ array__reserve(self, element_size, new_size);
+
+ char *contents = (char *)self->contents;
+ if (self->size > old_end) {
+ memmove(
+ contents + new_end * element_size,
+ contents + old_end * element_size,
+ (self->size - old_end) * element_size
+ );
+ }
+ if (new_count > 0) {
+ if (elements) {
+ memcpy(
+ (contents + index * element_size),
+ elements,
+ new_count * element_size
+ );
+ } else {
+ memset(
+ (contents + index * element_size),
+ 0,
+ new_count * element_size
+ );
+ }
+ }
+ self->size += new_count - old_count;
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // TREE_SITTER_ARRAY_H_
diff --git a/src/tree_sitter/atomic.h b/src/tree_sitter/atomic.h
new file mode 100644
index 0000000000..7bd0e850a9
--- /dev/null
+++ b/src/tree_sitter/atomic.h
@@ -0,0 +1,42 @@
+#ifndef TREE_SITTER_ATOMIC_H_
+#define TREE_SITTER_ATOMIC_H_
+
+#include <stdint.h>
+
+#ifdef _WIN32
+
+#include <windows.h>
+
+static inline size_t atomic_load(const volatile size_t *p) {
+ return *p;
+}
+
+static inline uint32_t atomic_inc(volatile uint32_t *p) {
+ return InterlockedIncrement((long volatile *)p);
+}
+
+static inline uint32_t atomic_dec(volatile uint32_t *p) {
+ return InterlockedDecrement((long volatile *)p);
+}
+
+#else
+
+static inline size_t atomic_load(const volatile size_t *p) {
+#ifdef __ATOMIC_RELAXED
+ return __atomic_load_n(p, __ATOMIC_RELAXED);
+#else
+ return __sync_fetch_and_add((volatile size_t *)p, 0);
+#endif
+}
+
+static inline uint32_t atomic_inc(volatile uint32_t *p) {
+ return __sync_add_and_fetch(p, 1u);
+}
+
+static inline uint32_t atomic_dec(volatile uint32_t *p) {
+ return __sync_sub_and_fetch(p, 1u);
+}
+
+#endif
+
+#endif // TREE_SITTER_ATOMIC_H_
diff --git a/src/tree_sitter/bits.h b/src/tree_sitter/bits.h
new file mode 100644
index 0000000000..ce7a715567
--- /dev/null
+++ b/src/tree_sitter/bits.h
@@ -0,0 +1,29 @@
+#ifndef TREE_SITTER_BITS_H_
+#define TREE_SITTER_BITS_H_
+
+#include <stdint.h>
+
+static inline uint32_t bitmask_for_index(uint16_t id) {
+ return (1u << (31 - id));
+}
+
+#if defined _WIN32 && !defined __GNUC__
+
+#include <intrin.h>
+
+static inline uint32_t count_leading_zeros(uint32_t x) {
+ if (x == 0) return 32;
+ uint32_t result;
+ _BitScanReverse(&result, x);
+ return 31 - result;
+}
+
+#else
+
+static inline uint32_t count_leading_zeros(uint32_t x) {
+ if (x == 0) return 32;
+ return __builtin_clz(x);
+}
+
+#endif
+#endif // TREE_SITTER_BITS_H_
diff --git a/src/tree_sitter/clock.h b/src/tree_sitter/clock.h
new file mode 100644
index 0000000000..94545f3566
--- /dev/null
+++ b/src/tree_sitter/clock.h
@@ -0,0 +1,141 @@
+#ifndef TREE_SITTER_CLOCK_H_
+#define TREE_SITTER_CLOCK_H_
+
+#include <stdint.h>
+
+typedef uint64_t TSDuration;
+
+#ifdef _WIN32
+
+// Windows:
+// * Represent a time as a performance counter value.
+// * Represent a duration as a number of performance counter ticks.
+
+#include <windows.h>
+typedef uint64_t TSClock;
+
+static inline TSDuration duration_from_micros(uint64_t micros) {
+ LARGE_INTEGER frequency;
+ QueryPerformanceFrequency(&frequency);
+ return micros * (uint64_t)frequency.QuadPart / 1000000;
+}
+
+static inline uint64_t duration_to_micros(TSDuration self) {
+ LARGE_INTEGER frequency;
+ QueryPerformanceFrequency(&frequency);
+ return self * 1000000 / (uint64_t)frequency.QuadPart;
+}
+
+static inline TSClock clock_null(void) {
+ return 0;
+}
+
+static inline TSClock clock_now(void) {
+ LARGE_INTEGER result;
+ QueryPerformanceCounter(&result);
+ return (uint64_t)result.QuadPart;
+}
+
+static inline TSClock clock_after(TSClock base, TSDuration duration) {
+ return base + duration;
+}
+
+static inline bool clock_is_null(TSClock self) {
+ return !self;
+}
+
+static inline bool clock_is_gt(TSClock self, TSClock other) {
+ return self > other;
+}
+
+#elif defined(CLOCK_MONOTONIC) && !defined(__APPLE__)
+
+// POSIX with monotonic clock support (Linux)
+// * Represent a time as a monotonic (seconds, nanoseconds) pair.
+// * Represent a duration as a number of microseconds.
+//
+// On these platforms, parse timeouts will correspond accurately to
+// real time, regardless of what other processes are running.
+
+#include <time.h>
+typedef struct timespec TSClock;
+
+static inline TSDuration duration_from_micros(uint64_t micros) {
+ return micros;
+}
+
+static inline uint64_t duration_to_micros(TSDuration self) {
+ return self;
+}
+
+static inline TSClock clock_now(void) {
+ TSClock result;
+ clock_gettime(CLOCK_MONOTONIC, &result);
+ return result;
+}
+
+static inline TSClock clock_null(void) {
+ return (TSClock) {0, 0};
+}
+
+static inline TSClock clock_after(TSClock base, TSDuration duration) {
+ TSClock result = base;
+ result.tv_sec += duration / 1000000;
+ result.tv_nsec += (duration % 1000000) * 1000;
+ return result;
+}
+
+static inline bool clock_is_null(TSClock self) {
+ return !self.tv_sec;
+}
+
+static inline bool clock_is_gt(TSClock self, TSClock other) {
+ if (self.tv_sec > other.tv_sec) return true;
+ if (self.tv_sec < other.tv_sec) return false;
+ return self.tv_nsec > other.tv_nsec;
+}
+
+#else
+
+// macOS or POSIX without monotonic clock support
+// * Represent a time as a process clock value.
+// * Represent a duration as a number of process clock ticks.
+//
+// On these platforms, parse timeouts may be affected by other processes,
+// which is not ideal, but is better than using a non-monotonic time API
+// like `gettimeofday`.
+
+#include <time.h>
+typedef uint64_t TSClock;
+
+static inline TSDuration duration_from_micros(uint64_t micros) {
+ return micros * (uint64_t)CLOCKS_PER_SEC / 1000000;
+}
+
+static inline uint64_t duration_to_micros(TSDuration self) {
+ return self * 1000000 / (uint64_t)CLOCKS_PER_SEC;
+}
+
+static inline TSClock clock_null(void) {
+ return 0;
+}
+
+static inline TSClock clock_now(void) {
+ return (uint64_t)clock();
+}
+
+static inline TSClock clock_after(TSClock base, TSDuration duration) {
+ return base + duration;
+}
+
+static inline bool clock_is_null(TSClock self) {
+ return !self;
+}
+
+static inline bool clock_is_gt(TSClock self, TSClock other) {
+ return self > other;
+}
+
+#endif
+
+#endif // TREE_SITTER_CLOCK_H_
diff --git a/src/tree_sitter/error_costs.h b/src/tree_sitter/error_costs.h
new file mode 100644
index 0000000000..32d3666a66
--- /dev/null
+++ b/src/tree_sitter/error_costs.h
@@ -0,0 +1,11 @@
+#ifndef TREE_SITTER_ERROR_COSTS_H_
+#define TREE_SITTER_ERROR_COSTS_H_
+
+#define ERROR_STATE 0
+#define ERROR_COST_PER_RECOVERY 500
+#define ERROR_COST_PER_MISSING_TREE 110
+#define ERROR_COST_PER_SKIPPED_TREE 100
+#define ERROR_COST_PER_SKIPPED_LINE 30
+#define ERROR_COST_PER_SKIPPED_CHAR 1
+
+#endif
diff --git a/src/tree_sitter/get_changed_ranges.c b/src/tree_sitter/get_changed_ranges.c
new file mode 100644
index 0000000000..5bd1d814bd
--- /dev/null
+++ b/src/tree_sitter/get_changed_ranges.c
@@ -0,0 +1,482 @@
+#include "./get_changed_ranges.h"
+#include "./subtree.h"
+#include "./language.h"
+#include "./error_costs.h"
+#include "./tree_cursor.h"
+#include <assert.h>
+
+// #define DEBUG_GET_CHANGED_RANGES
+
+static void ts_range_array_add(TSRangeArray *self, Length start, Length end) {
+ if (self->size > 0) {
+ TSRange *last_range = array_back(self);
+ if (start.bytes <= last_range->end_byte) {
+ last_range->end_byte = end.bytes;
+ last_range->end_point = end.extent;
+ return;
+ }
+ }
+
+ if (start.bytes < end.bytes) {
+ TSRange range = { start.extent, end.extent, start.bytes, end.bytes };
+ array_push(self, range);
+ }
+}
+
+bool ts_range_array_intersects(const TSRangeArray *self, unsigned start_index,
+ uint32_t start_byte, uint32_t end_byte) {
+ for (unsigned i = start_index; i < self->size; i++) {
+ TSRange *range = &self->contents[i];
+ if (range->end_byte > start_byte) {
+ if (range->start_byte >= end_byte) break;
+ return true;
+ }
+ }
+ return false;
+}
+
+void ts_range_array_get_changed_ranges(
+ const TSRange *old_ranges, unsigned old_range_count,
+ const TSRange *new_ranges, unsigned new_range_count,
+ TSRangeArray *differences
+) {
+ unsigned new_index = 0;
+ unsigned old_index = 0;
+ Length current_position = length_zero();
+ bool in_old_range = false;
+ bool in_new_range = false;
+
+ while (old_index < old_range_count || new_index < new_range_count) {
+ const TSRange *old_range = &old_ranges[old_index];
+ const TSRange *new_range = &new_ranges[new_index];
+
+ Length next_old_position;
+ if (in_old_range) {
+ next_old_position = (Length) {old_range->end_byte, old_range->end_point};
+ } else if (old_index < old_range_count) {
+ next_old_position = (Length) {old_range->start_byte, old_range->start_point};
+ } else {
+ next_old_position = LENGTH_MAX;
+ }
+
+ Length next_new_position;
+ if (in_new_range) {
+ next_new_position = (Length) {new_range->end_byte, new_range->end_point};
+ } else if (new_index < new_range_count) {
+ next_new_position = (Length) {new_range->start_byte, new_range->start_point};
+ } else {
+ next_new_position = LENGTH_MAX;
+ }
+
+ if (next_old_position.bytes < next_new_position.bytes) {
+ if (in_old_range != in_new_range) {
+ ts_range_array_add(differences, current_position, next_old_position);
+ }
+ if (in_old_range) old_index++;
+ current_position = next_old_position;
+ in_old_range = !in_old_range;
+ } else if (next_new_position.bytes < next_old_position.bytes) {
+ if (in_old_range != in_new_range) {
+ ts_range_array_add(differences, current_position, next_new_position);
+ }
+ if (in_new_range) new_index++;
+ current_position = next_new_position;
+ in_new_range = !in_new_range;
+ } else {
+ if (in_old_range != in_new_range) {
+ ts_range_array_add(differences, current_position, next_new_position);
+ }
+ if (in_old_range) old_index++;
+ if (in_new_range) new_index++;
+ in_old_range = !in_old_range;
+ in_new_range = !in_new_range;
+ current_position = next_new_position;
+ }
+ }
+}
+
+typedef struct {
+ TreeCursor cursor;
+ const TSLanguage *language;
+ unsigned visible_depth;
+ bool in_padding;
+} Iterator;
+
+static Iterator iterator_new(TreeCursor *cursor, const Subtree *tree, const TSLanguage *language) {
+ array_clear(&cursor->stack);
+ array_push(&cursor->stack, ((TreeCursorEntry){
+ .subtree = tree,
+ .position = length_zero(),
+ .child_index = 0,
+ .structural_child_index = 0,
+ }));
+ return (Iterator) {
+ .cursor = *cursor,
+ .language = language,
+ .visible_depth = 1,
+ .in_padding = false,
+ };
+}
+
+static bool iterator_done(Iterator *self) {
+ return self->cursor.stack.size == 0;
+}
+
+static Length iterator_start_position(Iterator *self) {
+ TreeCursorEntry entry = *array_back(&self->cursor.stack);
+ if (self->in_padding) {
+ return entry.position;
+ } else {
+ return length_add(entry.position, ts_subtree_padding(*entry.subtree));
+ }
+}
+
+static Length iterator_end_position(Iterator *self) {
+ TreeCursorEntry entry = *array_back(&self->cursor.stack);
+ Length result = length_add(entry.position, ts_subtree_padding(*entry.subtree));
+ if (self->in_padding) {
+ return result;
+ } else {
+ return length_add(result, ts_subtree_size(*entry.subtree));
+ }
+}
+
+static bool iterator_tree_is_visible(const Iterator *self) {
+ TreeCursorEntry entry = *array_back(&self->cursor.stack);
+ if (ts_subtree_visible(*entry.subtree)) return true;
+ if (self->cursor.stack.size > 1) {
+ Subtree parent = *self->cursor.stack.contents[self->cursor.stack.size - 2].subtree;
+ const TSSymbol *alias_sequence = ts_language_alias_sequence(
+ self->language,
+ parent.ptr->production_id
+ );
+ return alias_sequence && alias_sequence[entry.structural_child_index] != 0;
+ }
+ return false;
+}
+
+static void iterator_get_visible_state(const Iterator *self, Subtree *tree,
+ TSSymbol *alias_symbol, uint32_t *start_byte) {
+ uint32_t i = self->cursor.stack.size - 1;
+
+ if (self->in_padding) {
+ if (i == 0) return;
+ i--;
+ }
+
+ for (; i + 1 > 0; i--) {
+ TreeCursorEntry entry = self->cursor.stack.contents[i];
+
+ if (i > 0) {
+ const Subtree *parent = self->cursor.stack.contents[i - 1].subtree;
+ const TSSymbol *alias_sequence = ts_language_alias_sequence(
+ self->language,
+ parent->ptr->production_id
+ );
+ if (alias_sequence) {
+ *alias_symbol = alias_sequence[entry.structural_child_index];
+ }
+ }
+
+ if (ts_subtree_visible(*entry.subtree) || *alias_symbol) {
+ *tree = *entry.subtree;
+ *start_byte = entry.position.bytes;
+ break;
+ }
+ }
+}
+
+static void iterator_ascend(Iterator *self) {
+ if (iterator_done(self)) return;
+ if (iterator_tree_is_visible(self) && !self->in_padding) self->visible_depth--;
+ if (array_back(&self->cursor.stack)->child_index > 0) self->in_padding = false;
+ self->cursor.stack.size--;
+}
+
+static bool iterator_descend(Iterator *self, uint32_t goal_position) {
+ if (self->in_padding) return false;
+
+ bool did_descend;
+ do {
+ did_descend = false;
+ TreeCursorEntry entry = *array_back(&self->cursor.stack);
+ Length position = entry.position;
+ uint32_t structural_child_index = 0;
+ for (uint32_t i = 0, n = ts_subtree_child_count(*entry.subtree); i < n; i++) {
+ const Subtree *child = &entry.subtree->ptr->children[i];
+ Length child_left = length_add(position, ts_subtree_padding(*child));
+ Length child_right = length_add(child_left, ts_subtree_size(*child));
+
+ if (child_right.bytes > goal_position) {
+ array_push(&self->cursor.stack, ((TreeCursorEntry){
+ .subtree = child,
+ .position = position,
+ .child_index = i,
+ .structural_child_index = structural_child_index,
+ }));
+
+ if (iterator_tree_is_visible(self)) {
+ if (child_left.bytes > goal_position) {
+ self->in_padding = true;
+ } else {
+ self->visible_depth++;
+ }
+ return true;
+ }
+
+ did_descend = true;
+ break;
+ }
+
+ position = child_right;
+ if (!ts_subtree_extra(*child)) structural_child_index++;
+ }
+ } while (did_descend);
+
+ return false;
+}
+
+static void iterator_advance(Iterator *self) {
+ if (self->in_padding) {
+ self->in_padding = false;
+ if (iterator_tree_is_visible(self)) {
+ self->visible_depth++;
+ } else {
+ iterator_descend(self, 0);
+ }
+ return;
+ }
+
+ for (;;) {
+ if (iterator_tree_is_visible(self)) self->visible_depth--;
+ TreeCursorEntry entry = array_pop(&self->cursor.stack);
+ if (iterator_done(self)) return;
+
+ const Subtree *parent = array_back(&self->cursor.stack)->subtree;
+ uint32_t child_index = entry.child_index + 1;
+ if (ts_subtree_child_count(*parent) > child_index) {
+ Length position = length_add(entry.position, ts_subtree_total_size(*entry.subtree));
+ uint32_t structural_child_index = entry.structural_child_index;
+ if (!ts_subtree_extra(*entry.subtree)) structural_child_index++;
+ const Subtree *next_child = &parent->ptr->children[child_index];
+
+ array_push(&self->cursor.stack, ((TreeCursorEntry){
+ .subtree = next_child,
+ .position = position,
+ .child_index = child_index,
+ .structural_child_index = structural_child_index,
+ }));
+
+ if (iterator_tree_is_visible(self)) {
+ if (ts_subtree_padding(*next_child).bytes > 0) {
+ self->in_padding = true;
+ } else {
+ self->visible_depth++;
+ }
+ } else {
+ iterator_descend(self, 0);
+ }
+ break;
+ }
+ }
+}
+
+typedef enum {
+ IteratorDiffers,
+ IteratorMayDiffer,
+ IteratorMatches,
+} IteratorComparison;
+
+static IteratorComparison iterator_compare(const Iterator *old_iter, const Iterator *new_iter) {
+ Subtree old_tree = NULL_SUBTREE;
+ Subtree new_tree = NULL_SUBTREE;
+ uint32_t old_start = 0;
+ uint32_t new_start = 0;
+ TSSymbol old_alias_symbol = 0;
+ TSSymbol new_alias_symbol = 0;
+ iterator_get_visible_state(old_iter, &old_tree, &old_alias_symbol, &old_start);
+ iterator_get_visible_state(new_iter, &new_tree, &new_alias_symbol, &new_start);
+
+ if (!old_tree.ptr && !new_tree.ptr) return IteratorMatches;
+ if (!old_tree.ptr || !new_tree.ptr) return IteratorDiffers;
+
+ if (
+ old_alias_symbol == new_alias_symbol &&
+ ts_subtree_symbol(old_tree) == ts_subtree_symbol(new_tree)
+ ) {
+ if (old_start == new_start &&
+ !ts_subtree_has_changes(old_tree) &&
+ ts_subtree_symbol(old_tree) != ts_builtin_sym_error &&
+ ts_subtree_size(old_tree).bytes == ts_subtree_size(new_tree).bytes &&
+ ts_subtree_parse_state(old_tree) != TS_TREE_STATE_NONE &&
+ ts_subtree_parse_state(new_tree) != TS_TREE_STATE_NONE &&
+ (ts_subtree_parse_state(old_tree) == ERROR_STATE) ==
+ (ts_subtree_parse_state(new_tree) == ERROR_STATE)) {
+ return IteratorMatches;
+ } else {
+ return IteratorMayDiffer;
+ }
+ }
+
+ return IteratorDiffers;
+}
+
+#ifdef DEBUG_GET_CHANGED_RANGES
+static inline void iterator_print_state(Iterator *self) {
+ TreeCursorEntry entry = *array_back(&self->cursor.stack);
+ TSPoint start = iterator_start_position(self).extent;
+ TSPoint end = iterator_end_position(self).extent;
+ const char *name = ts_language_symbol_name(self->language, ts_subtree_symbol(*entry.subtree));
+ printf(
+ "(%-25s %s\t depth:%u [%u, %u] - [%u, %u])",
+ name, self->in_padding ? "(p)" : " ",
+ self->visible_depth,
+ start.row + 1, start.column,
+ end.row + 1, end.column
+ );
+}
+#endif
+
+unsigned ts_subtree_get_changed_ranges(const Subtree *old_tree, const Subtree *new_tree,
+ TreeCursor *cursor1, TreeCursor *cursor2,
+ const TSLanguage *language,
+ const TSRangeArray *included_range_differences,
+ TSRange **ranges) {
+ TSRangeArray results = array_new();
+
+ Iterator old_iter = iterator_new(cursor1, old_tree, language);
+ Iterator new_iter = iterator_new(cursor2, new_tree, language);
+
+ unsigned included_range_difference_index = 0;
+
+ Length position = iterator_start_position(&old_iter);
+ Length next_position = iterator_start_position(&new_iter);
+ if (position.bytes < next_position.bytes) {
+ ts_range_array_add(&results, position, next_position);
+ position = next_position;
+ } else if (position.bytes > next_position.bytes) {
+ ts_range_array_add(&results, next_position, position);
+ next_position = position;
+ }
+
+ do {
+ #ifdef DEBUG_GET_CHANGED_RANGES
+ printf("At [%-2u, %-2u] Compare ", position.extent.row + 1, position.extent.column);
+ iterator_print_state(&old_iter);
+ printf("\tvs\t");
+ iterator_print_state(&new_iter);
+ puts("");
+ #endif
+
+ // Compare the old and new subtrees.
+ IteratorComparison comparison = iterator_compare(&old_iter, &new_iter);
+
+ // Even if the two subtrees appear to be identical, they could differ
+ // internally if they contain a range of text that was previously
+ // excluded from the parse, and is now included, or vice-versa.
+ if (comparison == IteratorMatches && ts_range_array_intersects(
+ included_range_differences,
+ included_range_difference_index,
+ position.bytes,
+ iterator_end_position(&old_iter).bytes
+ )) {
+ comparison = IteratorMayDiffer;
+ }
+
+ bool is_changed = false;
+ switch (comparison) {
+ // If the subtrees are definitely identical, move to the end
+ // of both subtrees.
+ case IteratorMatches:
+ next_position = iterator_end_position(&old_iter);
+ break;
+
+ // If the subtrees might differ internally, descend into both
+ // subtrees, finding the first child that spans the current position.
+ case IteratorMayDiffer:
+ if (iterator_descend(&old_iter, position.bytes)) {
+ if (!iterator_descend(&new_iter, position.bytes)) {
+ is_changed = true;
+ next_position = iterator_end_position(&old_iter);
+ }
+ } else if (iterator_descend(&new_iter, position.bytes)) {
+ is_changed = true;
+ next_position = iterator_end_position(&new_iter);
+ } else {
+ next_position = length_min(
+ iterator_end_position(&old_iter),
+ iterator_end_position(&new_iter)
+ );
+ }
+ break;
+
+ // If the subtrees are different, record a change and then move
+ // to the end of both subtrees.
+ case IteratorDiffers:
+ is_changed = true;
+ next_position = length_min(
+ iterator_end_position(&old_iter),
+ iterator_end_position(&new_iter)
+ );
+ break;
+ }
+
+ // Ensure that both iterators are caught up to the current position.
+ while (
+ !iterator_done(&old_iter) &&
+ iterator_end_position(&old_iter).bytes <= next_position.bytes
+ ) iterator_advance(&old_iter);
+ while (
+ !iterator_done(&new_iter) &&
+ iterator_end_position(&new_iter).bytes <= next_position.bytes
+ ) iterator_advance(&new_iter);
+
+ // Ensure that both iterators are at the same depth in the tree.
+ while (old_iter.visible_depth > new_iter.visible_depth) {
+ iterator_ascend(&old_iter);
+ }
+ while (new_iter.visible_depth > old_iter.visible_depth) {
+ iterator_ascend(&new_iter);
+ }
+
+ if (is_changed) {
+ #ifdef DEBUG_GET_CHANGED_RANGES
+ printf(
+ " change: [[%u, %u] - [%u, %u]]\n",
+ position.extent.row + 1, position.extent.column,
+ next_position.extent.row + 1, next_position.extent.column
+ );
+ #endif
+
+ ts_range_array_add(&results, position, next_position);
+ }
+
+ position = next_position;
+
+ // Keep track of the current position in the included range differences
+ // array in order to avoid scanning the entire array on each iteration.
+ while (included_range_difference_index < included_range_differences->size) {
+ const TSRange *range = &included_range_differences->contents[
+ included_range_difference_index
+ ];
+ if (range->end_byte <= position.bytes) {
+ included_range_difference_index++;
+ } else {
+ break;
+ }
+ }
+ } while (!iterator_done(&old_iter) && !iterator_done(&new_iter));
+
+ Length old_size = ts_subtree_total_size(*old_tree);
+ Length new_size = ts_subtree_total_size(*new_tree);
+ if (old_size.bytes < new_size.bytes) {
+ ts_range_array_add(&results, old_size, new_size);
+ } else if (new_size.bytes < old_size.bytes) {
+ ts_range_array_add(&results, new_size, old_size);
+ }
+
+ *cursor1 = old_iter.cursor;
+ *cursor2 = new_iter.cursor;
+ *ranges = results.contents;
+ return results.size;
+}
diff --git a/src/tree_sitter/get_changed_ranges.h b/src/tree_sitter/get_changed_ranges.h
new file mode 100644
index 0000000000..a1f1dbb430
--- /dev/null
+++ b/src/tree_sitter/get_changed_ranges.h
@@ -0,0 +1,36 @@
+#ifndef TREE_SITTER_GET_CHANGED_RANGES_H_
+#define TREE_SITTER_GET_CHANGED_RANGES_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "./tree_cursor.h"
+#include "./subtree.h"
+
+typedef Array(TSRange) TSRangeArray;
+
+void ts_range_array_get_changed_ranges(
+ const TSRange *old_ranges, unsigned old_range_count,
+ const TSRange *new_ranges, unsigned new_range_count,
+ TSRangeArray *differences
+);
+
+bool ts_range_array_intersects(
+ const TSRangeArray *self, unsigned start_index,
+ uint32_t start_byte, uint32_t end_byte
+);
+
+unsigned ts_subtree_get_changed_ranges(
+ const Subtree *old_tree, const Subtree *new_tree,
+ TreeCursor *cursor1, TreeCursor *cursor2,
+ const TSLanguage *language,
+ const TSRangeArray *included_range_differences,
+ TSRange **ranges
+);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // TREE_SITTER_GET_CHANGED_RANGES_H_
diff --git a/src/tree_sitter/language.c b/src/tree_sitter/language.c
new file mode 100644
index 0000000000..c00c49e3c0
--- /dev/null
+++ b/src/tree_sitter/language.c
@@ -0,0 +1,149 @@
+#include "./language.h"
+#include "./subtree.h"
+#include "./error_costs.h"
+#include <string.h>
+
+uint32_t ts_language_symbol_count(const TSLanguage *self) {
+ return self->symbol_count + self->alias_count;
+}
+
+uint32_t ts_language_version(const TSLanguage *self) {
+ return self->version;
+}
+
+uint32_t ts_language_field_count(const TSLanguage *self) {
+ if (self->version >= TREE_SITTER_LANGUAGE_VERSION_WITH_FIELDS) {
+ return self->field_count;
+ } else {
+ return 0;
+ }
+}
+
+void ts_language_table_entry(
+ const TSLanguage *self,
+ TSStateId state,
+ TSSymbol symbol,
+ TableEntry *result
+) {
+ if (symbol == ts_builtin_sym_error || symbol == ts_builtin_sym_error_repeat) {
+ result->action_count = 0;
+ result->is_reusable = false;
+ result->actions = NULL;
+ } else {
+ assert(symbol < self->token_count);
+ uint32_t action_index = ts_language_lookup(self, state, symbol);
+ const TSParseActionEntry *entry = &self->parse_actions[action_index];
+ result->action_count = entry->entry.count;
+ result->is_reusable = entry->entry.reusable;
+ result->actions = (const TSParseAction *)(entry + 1);
+ }
+}
+
+TSSymbolMetadata ts_language_symbol_metadata(
+ const TSLanguage *self,
+ TSSymbol symbol
+) {
+ if (symbol == ts_builtin_sym_error) {
+ return (TSSymbolMetadata){.visible = true, .named = true};
+ } else if (symbol == ts_builtin_sym_error_repeat) {
+ return (TSSymbolMetadata){.visible = false, .named = false};
+ } else {
+ return self->symbol_metadata[symbol];
+ }
+}
+
+TSSymbol ts_language_public_symbol(
+ const TSLanguage *self,
+ TSSymbol symbol
+) {
+ if (symbol == ts_builtin_sym_error) return symbol;
+ if (self->version >= TREE_SITTER_LANGUAGE_VERSION_WITH_SYMBOL_DEDUPING) {
+ return self->public_symbol_map[symbol];
+ } else {
+ return symbol;
+ }
+}
+
+const char *ts_language_symbol_name(
+ const TSLanguage *self,
+ TSSymbol symbol
+) {
+ if (symbol == ts_builtin_sym_error) {
+ return "ERROR";
+ } else if (symbol == ts_builtin_sym_error_repeat) {
+ return "_ERROR";
+ } else if (symbol < ts_language_symbol_count(self)) {
+ return self->symbol_names[symbol];
+ } else {
+ return NULL;
+ }
+}
+
+TSSymbol ts_language_symbol_for_name(
+ const TSLanguage *self,
+ const char *string,
+ uint32_t length,
+ bool is_named
+) {
+ if (!strncmp(string, "ERROR", length)) return ts_builtin_sym_error;
+ uint32_t count = ts_language_symbol_count(self);
+ for (TSSymbol i = 0; i < count; i++) {
+ TSSymbolMetadata metadata = ts_language_symbol_metadata(self, i);
+ if (!metadata.visible || metadata.named != is_named) continue;
+ const char *symbol_name = self->symbol_names[i];
+ if (!strncmp(symbol_name, string, length) && !symbol_name[length]) {
+ if (self->version >= TREE_SITTER_LANGUAGE_VERSION_WITH_SYMBOL_DEDUPING) {
+ return self->public_symbol_map[i];
+ } else {
+ return i;
+ }
+ }
+ }
+ return 0;
+}
+
+TSSymbolType ts_language_symbol_type(
+ const TSLanguage *self,
+ TSSymbol symbol
+) {
+ TSSymbolMetadata metadata = ts_language_symbol_metadata(self, symbol);
+ if (metadata.named) {
+ return TSSymbolTypeRegular;
+ } else if (metadata.visible) {
+ return TSSymbolTypeAnonymous;
+ } else {
+ return TSSymbolTypeAuxiliary;
+ }
+}
+
+const char *ts_language_field_name_for_id(
+ const TSLanguage *self,
+ TSFieldId id
+) {
+ uint32_t count = ts_language_field_count(self);
+ if (count && id <= count) {
+ return self->field_names[id];
+ } else {
+ return NULL;
+ }
+}
+
+TSFieldId ts_language_field_id_for_name(
+ const TSLanguage *self,
+ const char *name,
+ uint32_t name_length
+) {
+ uint32_t count = ts_language_field_count(self);
+ for (TSSymbol i = 1; i < count + 1; i++) {
+ switch (strncmp(name, self->field_names[i], name_length)) {
+ case 0:
+ if (self->field_names[i][name_length] == 0) return i;
+ break;
+ case -1:
+ return 0;
+ default:
+ break;
+ }
+ }
+ return 0;
+}
diff --git a/src/tree_sitter/language.h b/src/tree_sitter/language.h
new file mode 100644
index 0000000000..341f0f85af
--- /dev/null
+++ b/src/tree_sitter/language.h
@@ -0,0 +1,143 @@
+#ifndef TREE_SITTER_LANGUAGE_H_
+#define TREE_SITTER_LANGUAGE_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "./subtree.h"
+#include "tree_sitter/parser.h"
+
+#define ts_builtin_sym_error_repeat (ts_builtin_sym_error - 1)
+#define TREE_SITTER_LANGUAGE_VERSION_WITH_FIELDS 10
+#define TREE_SITTER_LANGUAGE_VERSION_WITH_SYMBOL_DEDUPING 11
+#define TREE_SITTER_LANGUAGE_VERSION_WITH_SMALL_STATES 11
+
+typedef struct {
+ const TSParseAction *actions;
+ uint32_t action_count;
+ bool is_reusable;
+} TableEntry;
+
+void ts_language_table_entry(const TSLanguage *, TSStateId, TSSymbol, TableEntry *);
+
+TSSymbolMetadata ts_language_symbol_metadata(const TSLanguage *, TSSymbol);
+
+TSSymbol ts_language_public_symbol(const TSLanguage *, TSSymbol);
+
+static inline bool ts_language_is_symbol_external(const TSLanguage *self, TSSymbol symbol) {
+ return 0 < symbol && symbol < self->external_token_count + 1;
+}
+
+static inline const TSParseAction *ts_language_actions(
+ const TSLanguage *self,
+ TSStateId state,
+ TSSymbol symbol,
+ uint32_t *count
+) {
+ TableEntry entry;
+ ts_language_table_entry(self, state, symbol, &entry);
+ *count = entry.action_count;
+ return entry.actions;
+}
+
+static inline bool ts_language_has_actions(const TSLanguage *self,
+ TSStateId state,
+ TSSymbol symbol) {
+ TableEntry entry;
+ ts_language_table_entry(self, state, symbol, &entry);
+ return entry.action_count > 0;
+}
+
+static inline bool ts_language_has_reduce_action(const TSLanguage *self,
+ TSStateId state,
+ TSSymbol symbol) {
+ TableEntry entry;
+ ts_language_table_entry(self, state, symbol, &entry);
+ return entry.action_count > 0 && entry.actions[0].type == TSParseActionTypeReduce;
+}
+
+static inline uint16_t ts_language_lookup(
+ const TSLanguage *self,
+ TSStateId state,
+ TSSymbol symbol
+) {
+ if (
+ self->version >= TREE_SITTER_LANGUAGE_VERSION_WITH_SMALL_STATES &&
+ state >= self->large_state_count
+ ) {
+ uint32_t index = self->small_parse_table_map[state - self->large_state_count];
+ const uint16_t *data = &self->small_parse_table[index];
+ uint16_t section_count = *(data++);
+ for (unsigned i = 0; i < section_count; i++) {
+ uint16_t section_value = *(data++);
+ uint16_t symbol_count = *(data++);
+ for (unsigned i = 0; i < symbol_count; i++) {
+ if (*(data++) == symbol) return section_value;
+ }
+ }
+ return 0;
+ } else {
+ return self->parse_table[state * self->symbol_count + symbol];
+ }
+}
+
+static inline TSStateId ts_language_next_state(const TSLanguage *self,
+ TSStateId state,
+ TSSymbol symbol) {
+ if (symbol == ts_builtin_sym_error || symbol == ts_builtin_sym_error_repeat) {
+ return 0;
+ } else if (symbol < self->token_count) {
+ uint32_t count;
+ const TSParseAction *actions = ts_language_actions(self, state, symbol, &count);
+ if (count > 0) {
+ TSParseAction action = actions[count - 1];
+ if (action.type == TSParseActionTypeShift) {
+ return action.params.shift.extra ? state : action.params.shift.state;
+ }
+ }
+ return 0;
+ } else {
+ return ts_language_lookup(self, state, symbol);
+ }
+}
+
+static inline const bool *
+ts_language_enabled_external_tokens(const TSLanguage *self,
+ unsigned external_scanner_state) {
+ if (external_scanner_state == 0) {
+ return NULL;
+ } else {
+ return self->external_scanner.states + self->external_token_count * external_scanner_state;
+ }
+}
+
+static inline const TSSymbol *
+ts_language_alias_sequence(const TSLanguage *self, uint32_t production_id) {
+ return production_id > 0 ?
+ self->alias_sequences + production_id * self->max_alias_sequence_length :
+ NULL;
+}
+
+static inline void ts_language_field_map(
+ const TSLanguage *self,
+ uint32_t production_id,
+ const TSFieldMapEntry **start,
+ const TSFieldMapEntry **end
+) {
+ if (self->version < TREE_SITTER_LANGUAGE_VERSION_WITH_FIELDS || self->field_count == 0) {
+ *start = NULL;
+ *end = NULL;
+ return;
+ }
+
+ TSFieldMapSlice slice = self->field_map_slices[production_id];
+ *start = &self->field_map_entries[slice.index];
+ *end = &self->field_map_entries[slice.index] + slice.length;
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // TREE_SITTER_LANGUAGE_H_
diff --git a/src/tree_sitter/length.h b/src/tree_sitter/length.h
new file mode 100644
index 0000000000..61de9fc1d5
--- /dev/null
+++ b/src/tree_sitter/length.h
@@ -0,0 +1,44 @@
+#ifndef TREE_SITTER_LENGTH_H_
+#define TREE_SITTER_LENGTH_H_
+
+#include <stdlib.h>
+#include <stdbool.h>
+#include "./point.h"
+#include "tree_sitter/api.h"
+
+typedef struct {
+ uint32_t bytes;
+ TSPoint extent;
+} Length;
+
+static const Length LENGTH_UNDEFINED = {0, {0, 1}};
+static const Length LENGTH_MAX = {UINT32_MAX, {UINT32_MAX, UINT32_MAX}};
+
+static inline bool length_is_undefined(Length length) {
+ return length.bytes == 0 && length.extent.column != 0;
+}
+
+static inline Length length_min(Length len1, Length len2) {
+ return (len1.bytes < len2.bytes) ? len1 : len2;
+}
+
+static inline Length length_add(Length len1, Length len2) {
+ Length result;
+ result.bytes = len1.bytes + len2.bytes;
+ result.extent = point_add(len1.extent, len2.extent);
+ return result;
+}
+
+static inline Length length_sub(Length len1, Length len2) {
+ Length result;
+ result.bytes = len1.bytes - len2.bytes;
+ result.extent = point_sub(len1.extent, len2.extent);
+ return result;
+}
+
+static inline Length length_zero(void) {
+ Length result = {0, {0, 0}};
+ return result;
+}
+
+#endif
diff --git a/src/tree_sitter/lexer.c b/src/tree_sitter/lexer.c
new file mode 100644
index 0000000000..3f8a4c0ae8
--- /dev/null
+++ b/src/tree_sitter/lexer.c
@@ -0,0 +1,391 @@
+#include <stdio.h>
+#include "./lexer.h"
+#include "./subtree.h"
+#include "./length.h"
+#include "./unicode.h"
+
+#define LOG(message, character) \
+ if (self->logger.log) { \
+ snprintf( \
+ self->debug_buffer, \
+ TREE_SITTER_SERIALIZATION_BUFFER_SIZE, \
+ 32 <= character && character < 127 ? \
+ message " character:'%c'" : \
+ message " character:%d", \
+ character \
+ ); \
+ self->logger.log( \
+ self->logger.payload, \
+ TSLogTypeLex, \
+ self->debug_buffer \
+ ); \
+ }
+
+static const int32_t BYTE_ORDER_MARK = 0xFEFF;
+
+static const TSRange DEFAULT_RANGE = {
+ .start_point = {
+ .row = 0,
+ .column = 0,
+ },
+ .end_point = {
+ .row = UINT32_MAX,
+ .column = UINT32_MAX,
+ },
+ .start_byte = 0,
+ .end_byte = UINT32_MAX
+};
+
+// Check if the lexer has reached EOF. This state is stored
+// by setting the lexer's `current_included_range_index` such that
+// it has consumed all of its available ranges.
+static bool ts_lexer__eof(const TSLexer *_self) {
+ Lexer *self = (Lexer *)_self;
+ return self->current_included_range_index == self->included_range_count;
+}
+
+// Clear the currently stored chunk of source code, because the lexer's
+// position has changed.
+static void ts_lexer__clear_chunk(Lexer *self) {
+ self->chunk = NULL;
+ self->chunk_size = 0;
+ self->chunk_start = 0;
+}
+
+// Call the lexer's input callback to obtain a new chunk of source code
+// for the current position.
+static void ts_lexer__get_chunk(Lexer *self) {
+ self->chunk_start = self->current_position.bytes;
+ self->chunk = self->input.read(
+ self->input.payload,
+ self->current_position.bytes,
+ self->current_position.extent,
+ &self->chunk_size
+ );
+ if (!self->chunk_size) {
+ self->current_included_range_index = self->included_range_count;
+ self->chunk = NULL;
+ }
+}
+
+// Decode the next unicode character in the current chunk of source code.
+// This assumes that the lexer has already retrieved a chunk of source
+// code that spans the current position.
+static void ts_lexer__get_lookahead(Lexer *self) {
+ uint32_t position_in_chunk = self->current_position.bytes - self->chunk_start;
+ const uint8_t *chunk = (const uint8_t *)self->chunk + position_in_chunk;
+ uint32_t size = self->chunk_size - position_in_chunk;
+
+ if (size == 0) {
+ self->lookahead_size = 1;
+ self->data.lookahead = '\0';
+ return;
+ }
+
+ UnicodeDecodeFunction decode = self->input.encoding == TSInputEncodingUTF8
+ ? ts_decode_utf8
+ : ts_decode_utf16;
+
+ self->lookahead_size = decode(chunk, size, &self->data.lookahead);
+
+ // If this chunk ended in the middle of a multi-byte character,
+ // try again with a fresh chunk.
+ if (self->data.lookahead == TS_DECODE_ERROR && size < 4) {
+ ts_lexer__get_chunk(self);
+ chunk = (const uint8_t *)self->chunk;
+ size = self->chunk_size;
+ self->lookahead_size = decode(chunk, size, &self->data.lookahead);
+ }
+
+ if (self->data.lookahead == TS_DECODE_ERROR) {
+ self->lookahead_size = 1;
+ }
+}
+
+// Advance to the next character in the source code, retrieving a new
+// chunk of source code if needed.
+static void ts_lexer__advance(TSLexer *_self, bool skip) {
+ Lexer *self = (Lexer *)_self;
+ if (!self->chunk) return;
+
+ if (skip) {
+ LOG("skip", self->data.lookahead);
+ } else {
+ LOG("consume", self->data.lookahead);
+ }
+
+ if (self->lookahead_size) {
+ self->current_position.bytes += self->lookahead_size;
+ if (self->data.lookahead == '\n') {
+ self->current_position.extent.row++;
+ self->current_position.extent.column = 0;
+ } else {
+ self->current_position.extent.column += self->lookahead_size;
+ }
+ }
+
+ const TSRange *current_range = NULL;
+ if (self->current_included_range_index < self->included_range_count) {
+ current_range = &self->included_ranges[self->current_included_range_index];
+ if (self->current_position.bytes == current_range->end_byte) {
+ self->current_included_range_index++;
+ if (self->current_included_range_index < self->included_range_count) {
+ current_range++;
+ self->current_position = (Length) {
+ current_range->start_byte,
+ current_range->start_point,
+ };
+ } else {
+ current_range = NULL;
+ }
+ }
+ }
+
+ if (skip) self->token_start_position = self->current_position;
+
+ if (current_range) {
+ if (self->current_position.bytes >= self->chunk_start + self->chunk_size) {
+ ts_lexer__get_chunk(self);
+ }
+ ts_lexer__get_lookahead(self);
+ } else {
+ ts_lexer__clear_chunk(self);
+ self->data.lookahead = '\0';
+ self->lookahead_size = 1;
+ }
+}
+
+// Mark that a token match has completed. This can be called multiple
+// times if a longer match is found later.
+static void ts_lexer__mark_end(TSLexer *_self) {
+ Lexer *self = (Lexer *)_self;
+ if (!ts_lexer__eof(&self->data)) {
+ // If the lexer is right at the beginning of included range,
+ // then the token should be considered to end at the *end* of the
+ // previous included range, rather than here.
+ TSRange *current_included_range = &self->included_ranges[
+ self->current_included_range_index
+ ];
+ if (
+ self->current_included_range_index > 0 &&
+ self->current_position.bytes == current_included_range->start_byte
+ ) {
+ TSRange *previous_included_range = current_included_range - 1;
+ self->token_end_position = (Length) {
+ previous_included_range->end_byte,
+ previous_included_range->end_point,
+ };
+ return;
+ }
+ }
+ self->token_end_position = self->current_position;
+}
+
+static uint32_t ts_lexer__get_column(TSLexer *_self) {
+ Lexer *self = (Lexer *)_self;
+ uint32_t goal_byte = self->current_position.bytes;
+
+ self->current_position.bytes -= self->current_position.extent.column;
+ self->current_position.extent.column = 0;
+
+ if (self->current_position.bytes < self->chunk_start) {
+ ts_lexer__get_chunk(self);
+ }
+
+ uint32_t result = 0;
+ while (self->current_position.bytes < goal_byte) {
+ ts_lexer__advance(&self->data, false);
+ result++;
+ }
+
+ return result;
+}
+
+// Is the lexer at a boundary between two disjoint included ranges of
+// source code? This is exposed as an API because some languages' external
+// scanners need to perform custom actions at these bounaries.
+static bool ts_lexer__is_at_included_range_start(const TSLexer *_self) {
+ const Lexer *self = (const Lexer *)_self;
+ if (self->current_included_range_index < self->included_range_count) {
+ TSRange *current_range = &self->included_ranges[self->current_included_range_index];
+ return self->current_position.bytes == current_range->start_byte;
+ } else {
+ return false;
+ }
+}
+
+void ts_lexer_init(Lexer *self) {
+ *self = (Lexer) {
+ .data = {
+ // The lexer's methods are stored as struct fields so that generated
+ // parsers can call them without needing to be linked against this
+ // library.
+ .advance = ts_lexer__advance,
+ .mark_end = ts_lexer__mark_end,
+ .get_column = ts_lexer__get_column,
+ .is_at_included_range_start = ts_lexer__is_at_included_range_start,
+ .eof = ts_lexer__eof,
+ .lookahead = 0,
+ .result_symbol = 0,
+ },
+ .chunk = NULL,
+ .chunk_size = 0,
+ .chunk_start = 0,
+ .current_position = {0, {0, 0}},
+ .logger = {
+ .payload = NULL,
+ .log = NULL
+ },
+ .included_ranges = NULL,
+ .included_range_count = 0,
+ .current_included_range_index = 0,
+ };
+ ts_lexer_set_included_ranges(self, NULL, 0);
+}
+
+void ts_lexer_delete(Lexer *self) {
+ ts_free(self->included_ranges);
+}
+
+static void ts_lexer_goto(Lexer *self, Length position) {
+ self->current_position = position;
+ bool found_included_range = false;
+
+ // Move to the first valid position at or after the given position.
+ for (unsigned i = 0; i < self->included_range_count; i++) {
+ TSRange *included_range = &self->included_ranges[i];
+ if (included_range->end_byte > position.bytes) {
+ if (included_range->start_byte > position.bytes) {
+ self->current_position = (Length) {
+ .bytes = included_range->start_byte,
+ .extent = included_range->start_point,
+ };
+ }
+
+ self->current_included_range_index = i;
+ found_included_range = true;
+ break;
+ }
+ }
+
+ if (found_included_range) {
+ // If the current position is outside of the current chunk of text,
+ // then clear out the current chunk of text.
+ if (self->chunk && (
+ position.bytes < self->chunk_start ||
+ position.bytes >= self->chunk_start + self->chunk_size
+ )) {
+ ts_lexer__clear_chunk(self);
+ }
+
+ self->lookahead_size = 0;
+ self->data.lookahead = '\0';
+ }
+
+ // If the given position is beyond any of included ranges, move to the EOF
+ // state - past the end of the included ranges.
+ else {
+ self->current_included_range_index = self->included_range_count;
+ TSRange *last_included_range = &self->included_ranges[self->included_range_count - 1];
+ self->current_position = (Length) {
+ .bytes = last_included_range->end_byte,
+ .extent = last_included_range->end_point,
+ };
+ ts_lexer__clear_chunk(self);
+ self->lookahead_size = 1;
+ self->data.lookahead = '\0';
+ }
+}
+
+void ts_lexer_set_input(Lexer *self, TSInput input) {
+ self->input = input;
+ ts_lexer__clear_chunk(self);
+ ts_lexer_goto(self, self->current_position);
+}
+
+// Move the lexer to the given position. This doesn't do any work
+// if the parser is already at the given position.
+void ts_lexer_reset(Lexer *self, Length position) {
+ if (position.bytes != self->current_position.bytes) {
+ ts_lexer_goto(self, position);
+ }
+}
+
+void ts_lexer_start(Lexer *self) {
+ self->token_start_position = self->current_position;
+ self->token_end_position = LENGTH_UNDEFINED;
+ self->data.result_symbol = 0;
+ if (!ts_lexer__eof(&self->data)) {
+ if (!self->chunk_size) ts_lexer__get_chunk(self);
+ if (!self->lookahead_size) ts_lexer__get_lookahead(self);
+ if (
+ self->current_position.bytes == 0 &&
+ self->data.lookahead == BYTE_ORDER_MARK
+ ) ts_lexer__advance(&self->data, true);
+ }
+}
+
+void ts_lexer_finish(Lexer *self, uint32_t *lookahead_end_byte) {
+ if (length_is_undefined(self->token_end_position)) {
+ ts_lexer__mark_end(&self->data);
+ }
+
+ uint32_t current_lookahead_end_byte = self->current_position.bytes + 1;
+
+ // In order to determine that a byte sequence is invalid UTF8 or UTF16,
+ // the character decoding algorithm may have looked at the following byte.
+ // Therefore, the next byte *after* the current (invalid) character
+ // affects the interpretation of the current character.
+ if (self->data.lookahead == TS_DECODE_ERROR) {
+ current_lookahead_end_byte++;
+ }
+
+ if (current_lookahead_end_byte > *lookahead_end_byte) {
+ *lookahead_end_byte = current_lookahead_end_byte;
+ }
+}
+
+void ts_lexer_advance_to_end(Lexer *self) {
+ while (self->chunk) {
+ ts_lexer__advance(&self->data, false);
+ }
+}
+
+void ts_lexer_mark_end(Lexer *self) {
+ ts_lexer__mark_end(&self->data);
+}
+
+bool ts_lexer_set_included_ranges(
+ Lexer *self,
+ const TSRange *ranges,
+ uint32_t count
+) {
+ if (count == 0 || !ranges) {
+ ranges = &DEFAULT_RANGE;
+ count = 1;
+ } else {
+ uint32_t previous_byte = 0;
+ for (unsigned i = 0; i < count; i++) {
+ const TSRange *range = &ranges[i];
+ if (
+ range->start_byte < previous_byte ||
+ range->end_byte < range->start_byte
+ ) return false;
+ previous_byte = range->end_byte;
+ }
+ }
+
+ size_t size = count * sizeof(TSRange);
+ self->included_ranges = ts_realloc(self->included_ranges, size);
+ memcpy(self->included_ranges, ranges, size);
+ self->included_range_count = count;
+ ts_lexer_goto(self, self->current_position);
+ return true;
+}
+
+TSRange *ts_lexer_included_ranges(const Lexer *self, uint32_t *count) {
+ *count = self->included_range_count;
+ return self->included_ranges;
+}
+
+#undef LOG
diff --git a/src/tree_sitter/lexer.h b/src/tree_sitter/lexer.h
new file mode 100644
index 0000000000..5e39294529
--- /dev/null
+++ b/src/tree_sitter/lexer.h
@@ -0,0 +1,48 @@
+#ifndef TREE_SITTER_LEXER_H_
+#define TREE_SITTER_LEXER_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "./length.h"
+#include "./subtree.h"
+#include "tree_sitter/api.h"
+#include "tree_sitter/parser.h"
+
+typedef struct {
+ TSLexer data;
+ Length current_position;
+ Length token_start_position;
+ Length token_end_position;
+
+ TSRange *included_ranges;
+ size_t included_range_count;
+ size_t current_included_range_index;
+
+ const char *chunk;
+ uint32_t chunk_start;
+ uint32_t chunk_size;
+ uint32_t lookahead_size;
+
+ TSInput input;
+ TSLogger logger;
+ char debug_buffer[TREE_SITTER_SERIALIZATION_BUFFER_SIZE];
+} Lexer;
+
+void ts_lexer_init(Lexer *);
+void ts_lexer_delete(Lexer *);
+void ts_lexer_set_input(Lexer *, TSInput);
+void ts_lexer_reset(Lexer *, Length);
+void ts_lexer_start(Lexer *);
+void ts_lexer_finish(Lexer *, uint32_t *);
+void ts_lexer_advance_to_end(Lexer *);
+void ts_lexer_mark_end(Lexer *);
+bool ts_lexer_set_included_ranges(Lexer *self, const TSRange *ranges, uint32_t count);
+TSRange *ts_lexer_included_ranges(const Lexer *self, uint32_t *count);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // TREE_SITTER_LEXER_H_
diff --git a/src/tree_sitter/lib.c b/src/tree_sitter/lib.c
new file mode 100644
index 0000000000..289d32f4c5
--- /dev/null
+++ b/src/tree_sitter/lib.c
@@ -0,0 +1,17 @@
+// The Tree-sitter library can be built by compiling this one source file.
+//
+// The following directories must be added to the include path:
+// - include
+
+#define _POSIX_C_SOURCE 200112L
+
+#include "./get_changed_ranges.c"
+#include "./language.c"
+#include "./lexer.c"
+#include "./node.c"
+#include "./parser.c"
+#include "./query.c"
+#include "./stack.c"
+#include "./subtree.c"
+#include "./tree_cursor.c"
+#include "./tree.c"
diff --git a/src/tree_sitter/node.c b/src/tree_sitter/node.c
new file mode 100644
index 0000000000..576f3ef38e
--- /dev/null
+++ b/src/tree_sitter/node.c
@@ -0,0 +1,677 @@
+#include <stdbool.h>
+#include "./subtree.h"
+#include "./tree.h"
+#include "./language.h"
+
+typedef struct {
+ Subtree parent;
+ const TSTree *tree;
+ Length position;
+ uint32_t child_index;
+ uint32_t structural_child_index;
+ const TSSymbol *alias_sequence;
+} NodeChildIterator;
+
+// TSNode - constructors
+
+TSNode ts_node_new(
+ const TSTree *tree,
+ const Subtree *subtree,
+ Length position,
+ TSSymbol alias
+) {
+ return (TSNode) {
+ {position.bytes, position.extent.row, position.extent.column, alias},
+ subtree,
+ tree,
+ };
+}
+
+static inline TSNode ts_node__null(void) {
+ return ts_node_new(NULL, NULL, length_zero(), 0);
+}
+
+// TSNode - accessors
+
+uint32_t ts_node_start_byte(TSNode self) {
+ return self.context[0];
+}
+
+TSPoint ts_node_start_point(TSNode self) {
+ return (TSPoint) {self.context[1], self.context[2]};
+}
+
+static inline uint32_t ts_node__alias(const TSNode *self) {
+ return self->context[3];
+}
+
+static inline Subtree ts_node__subtree(TSNode self) {
+ return *(const Subtree *)self.id;
+}
+
+// NodeChildIterator
+
+static inline NodeChildIterator ts_node_iterate_children(const TSNode *node) {
+ Subtree subtree = ts_node__subtree(*node);
+ if (ts_subtree_child_count(subtree) == 0) {
+ return (NodeChildIterator) {NULL_SUBTREE, node->tree, length_zero(), 0, 0, NULL};
+ }
+ const TSSymbol *alias_sequence = ts_language_alias_sequence(
+ node->tree->language,
+ subtree.ptr->production_id
+ );
+ return (NodeChildIterator) {
+ .tree = node->tree,
+ .parent = subtree,
+ .position = {ts_node_start_byte(*node), ts_node_start_point(*node)},
+ .child_index = 0,
+ .structural_child_index = 0,
+ .alias_sequence = alias_sequence,
+ };
+}
+
+static inline bool ts_node_child_iterator_done(NodeChildIterator *self) {
+ return self->child_index == self->parent.ptr->child_count;
+}
+
+static inline bool ts_node_child_iterator_next(
+ NodeChildIterator *self,
+ TSNode *result
+) {
+ if (!self->parent.ptr || ts_node_child_iterator_done(self)) return false;
+ const Subtree *child = &self->parent.ptr->children[self->child_index];
+ TSSymbol alias_symbol = 0;
+ if (!ts_subtree_extra(*child)) {
+ if (self->alias_sequence) {
+ alias_symbol = self->alias_sequence[self->structural_child_index];
+ }
+ self->structural_child_index++;
+ }
+ if (self->child_index > 0) {
+ self->position = length_add(self->position, ts_subtree_padding(*child));
+ }
+ *result = ts_node_new(
+ self->tree,
+ child,
+ self->position,
+ alias_symbol
+ );
+ self->position = length_add(self->position, ts_subtree_size(*child));
+ self->child_index++;
+ return true;
+}
+
+// TSNode - private
+
+static inline bool ts_node__is_relevant(TSNode self, bool include_anonymous) {
+ Subtree tree = ts_node__subtree(self);
+ if (include_anonymous) {
+ return ts_subtree_visible(tree) || ts_node__alias(&self);
+ } else {
+ TSSymbol alias = ts_node__alias(&self);
+ if (alias) {
+ return ts_language_symbol_metadata(self.tree->language, alias).named;
+ } else {
+ return ts_subtree_visible(tree) && ts_subtree_named(tree);
+ }
+ }
+}
+
+static inline uint32_t ts_node__relevant_child_count(
+ TSNode self,
+ bool include_anonymous
+) {
+ Subtree tree = ts_node__subtree(self);
+ if (ts_subtree_child_count(tree) > 0) {
+ if (include_anonymous) {
+ return tree.ptr->visible_child_count;
+ } else {
+ return tree.ptr->named_child_count;
+ }
+ } else {
+ return 0;
+ }
+}
+
+static inline TSNode ts_node__child(
+ TSNode self,
+ uint32_t child_index,
+ bool include_anonymous
+) {
+ TSNode result = self;
+ bool did_descend = true;
+
+ while (did_descend) {
+ did_descend = false;
+
+ TSNode child;
+ uint32_t index = 0;
+ NodeChildIterator iterator = ts_node_iterate_children(&result);
+ while (ts_node_child_iterator_next(&iterator, &child)) {
+ if (ts_node__is_relevant(child, include_anonymous)) {
+ if (index == child_index) {
+ if (ts_node__is_relevant(self, true)) {
+ ts_tree_set_cached_parent(self.tree, &child, &self);
+ }
+ return child;
+ }
+ index++;
+ } else {
+ uint32_t grandchild_index = child_index - index;
+ uint32_t grandchild_count = ts_node__relevant_child_count(child, include_anonymous);
+ if (grandchild_index < grandchild_count) {
+ did_descend = true;
+ result = child;
+ child_index = grandchild_index;
+ break;
+ }
+ index += grandchild_count;
+ }
+ }
+ }
+
+ return ts_node__null();
+}
+
+static bool ts_subtree_has_trailing_empty_descendant(
+ Subtree self,
+ Subtree other
+) {
+ for (unsigned i = ts_subtree_child_count(self) - 1; i + 1 > 0; i--) {
+ Subtree child = self.ptr->children[i];
+ if (ts_subtree_total_bytes(child) > 0) break;
+ if (child.ptr == other.ptr || ts_subtree_has_trailing_empty_descendant(child, other)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+static inline TSNode ts_node__prev_sibling(TSNode self, bool include_anonymous) {
+ Subtree self_subtree = ts_node__subtree(self);
+ bool self_is_empty = ts_subtree_total_bytes(self_subtree) == 0;
+ uint32_t target_end_byte = ts_node_end_byte(self);
+
+ TSNode node = ts_node_parent(self);
+ TSNode earlier_node = ts_node__null();
+ bool earlier_node_is_relevant = false;
+
+ while (!ts_node_is_null(node)) {
+ TSNode earlier_child = ts_node__null();
+ bool earlier_child_is_relevant = false;
+ bool found_child_containing_target = false;
+
+ TSNode child;
+ NodeChildIterator iterator = ts_node_iterate_children(&node);
+ while (ts_node_child_iterator_next(&iterator, &child)) {
+ if (child.id == self.id) break;
+ if (iterator.position.bytes > target_end_byte) {
+ found_child_containing_target = true;
+ break;
+ }
+
+ if (iterator.position.bytes == target_end_byte &&
+ (!self_is_empty ||
+ ts_subtree_has_trailing_empty_descendant(ts_node__subtree(child), self_subtree))) {
+ found_child_containing_target = true;
+ break;
+ }
+
+ if (ts_node__is_relevant(child, include_anonymous)) {
+ earlier_child = child;
+ earlier_child_is_relevant = true;
+ } else if (ts_node__relevant_child_count(child, include_anonymous) > 0) {
+ earlier_child = child;
+ earlier_child_is_relevant = false;
+ }
+ }
+
+ if (found_child_containing_target) {
+ if (!ts_node_is_null(earlier_child)) {
+ earlier_node = earlier_child;
+ earlier_node_is_relevant = earlier_child_is_relevant;
+ }
+ node = child;
+ } else if (earlier_child_is_relevant) {
+ return earlier_child;
+ } else if (!ts_node_is_null(earlier_child)) {
+ node = earlier_child;
+ } else if (earlier_node_is_relevant) {
+ return earlier_node;
+ } else {
+ node = earlier_node;
+ }
+ }
+
+ return ts_node__null();
+}
+
+static inline TSNode ts_node__next_sibling(TSNode self, bool include_anonymous) {
+ uint32_t target_end_byte = ts_node_end_byte(self);
+
+ TSNode node = ts_node_parent(self);
+ TSNode later_node = ts_node__null();
+ bool later_node_is_relevant = false;
+
+ while (!ts_node_is_null(node)) {
+ TSNode later_child = ts_node__null();
+ bool later_child_is_relevant = false;
+ TSNode child_containing_target = ts_node__null();
+
+ TSNode child;
+ NodeChildIterator iterator = ts_node_iterate_children(&node);
+ while (ts_node_child_iterator_next(&iterator, &child)) {
+ if (iterator.position.bytes < target_end_byte) continue;
+ if (ts_node_start_byte(child) <= ts_node_start_byte(self)) {
+ if (ts_node__subtree(child).ptr != ts_node__subtree(self).ptr) {
+ child_containing_target = child;
+ }
+ } else if (ts_node__is_relevant(child, include_anonymous)) {
+ later_child = child;
+ later_child_is_relevant = true;
+ break;
+ } else if (ts_node__relevant_child_count(child, include_anonymous) > 0) {
+ later_child = child;
+ later_child_is_relevant = false;
+ break;
+ }
+ }
+
+ if (!ts_node_is_null(child_containing_target)) {
+ if (!ts_node_is_null(later_child)) {
+ later_node = later_child;
+ later_node_is_relevant = later_child_is_relevant;
+ }
+ node = child_containing_target;
+ } else if (later_child_is_relevant) {
+ return later_child;
+ } else if (!ts_node_is_null(later_child)) {
+ node = later_child;
+ } else if (later_node_is_relevant) {
+ return later_node;
+ } else {
+ node = later_node;
+ }
+ }
+
+ return ts_node__null();
+}
+
+static inline TSNode ts_node__first_child_for_byte(
+ TSNode self,
+ uint32_t goal,
+ bool include_anonymous
+) {
+ TSNode node = self;
+ bool did_descend = true;
+
+ while (did_descend) {
+ did_descend = false;
+
+ TSNode child;
+ NodeChildIterator iterator = ts_node_iterate_children(&node);
+ while (ts_node_child_iterator_next(&iterator, &child)) {
+ if (ts_node_end_byte(child) > goal) {
+ if (ts_node__is_relevant(child, include_anonymous)) {
+ return child;
+ } else if (ts_node_child_count(child) > 0) {
+ did_descend = true;
+ node = child;
+ break;
+ }
+ }
+ }
+ }
+
+ return ts_node__null();
+}
+
+static inline TSNode ts_node__descendant_for_byte_range(
+ TSNode self,
+ uint32_t range_start,
+ uint32_t range_end,
+ bool include_anonymous
+) {
+ TSNode node = self;
+ TSNode last_visible_node = self;
+
+ bool did_descend = true;
+ while (did_descend) {
+ did_descend = false;
+
+ TSNode child;
+ NodeChildIterator iterator = ts_node_iterate_children(&node);
+ while (ts_node_child_iterator_next(&iterator, &child)) {
+ uint32_t node_end = iterator.position.bytes;
+
+ // The end of this node must extend far enough forward to touch
+ // the end of the range and exceed the start of the range.
+ if (node_end < range_end) continue;
+ if (node_end <= range_start) continue;
+
+ // The start of this node must extend far enough backward to
+ // touch the start of the range.
+ if (range_start < ts_node_start_byte(child)) break;
+
+ node = child;
+ if (ts_node__is_relevant(node, include_anonymous)) {
+ ts_tree_set_cached_parent(self.tree, &child, &last_visible_node);
+ last_visible_node = node;
+ }
+ did_descend = true;
+ break;
+ }
+ }
+
+ return last_visible_node;
+}
+
+static inline TSNode ts_node__descendant_for_point_range(
+ TSNode self,
+ TSPoint range_start,
+ TSPoint range_end,
+ bool include_anonymous
+) {
+ TSNode node = self;
+ TSNode last_visible_node = self;
+
+ bool did_descend = true;
+ while (did_descend) {
+ did_descend = false;
+
+ TSNode child;
+ NodeChildIterator iterator = ts_node_iterate_children(&node);
+ while (ts_node_child_iterator_next(&iterator, &child)) {
+ TSPoint node_end = iterator.position.extent;
+
+ // The end of this node must extend far enough forward to touch
+ // the end of the range and exceed the start of the range.
+ if (point_lt(node_end, range_end)) continue;
+ if (point_lte(node_end, range_start)) continue;
+
+ // The start of this node must extend far enough backward to
+ // touch the start of the range.
+ if (point_lt(range_start, ts_node_start_point(child))) break;
+
+ node = child;
+ if (ts_node__is_relevant(node, include_anonymous)) {
+ ts_tree_set_cached_parent(self.tree, &child, &last_visible_node);
+ last_visible_node = node;
+ }
+ did_descend = true;
+ break;
+ }
+ }
+
+ return last_visible_node;
+}
+
+// TSNode - public
+
+uint32_t ts_node_end_byte(TSNode self) {
+ return ts_node_start_byte(self) + ts_subtree_size(ts_node__subtree(self)).bytes;
+}
+
+TSPoint ts_node_end_point(TSNode self) {
+ return point_add(ts_node_start_point(self), ts_subtree_size(ts_node__subtree(self)).extent);
+}
+
+TSSymbol ts_node_symbol(TSNode self) {
+ TSSymbol symbol = ts_node__alias(&self);
+ if (!symbol) symbol = ts_subtree_symbol(ts_node__subtree(self));
+ return ts_language_public_symbol(self.tree->language, symbol);
+}
+
+const char *ts_node_type(TSNode self) {
+ TSSymbol symbol = ts_node__alias(&self);
+ if (!symbol) symbol = ts_subtree_symbol(ts_node__subtree(self));
+ return ts_language_symbol_name(self.tree->language, symbol);
+}
+
+char *ts_node_string(TSNode self) {
+ return ts_subtree_string(ts_node__subtree(self), self.tree->language, false);
+}
+
+bool ts_node_eq(TSNode self, TSNode other) {
+ return self.tree == other.tree && self.id == other.id;
+}
+
+bool ts_node_is_null(TSNode self) {
+ return self.id == 0;
+}
+
+bool ts_node_is_extra(TSNode self) {
+ return ts_subtree_extra(ts_node__subtree(self));
+}
+
+bool ts_node_is_named(TSNode self) {
+ TSSymbol alias = ts_node__alias(&self);
+ return alias
+ ? ts_language_symbol_metadata(self.tree->language, alias).named
+ : ts_subtree_named(ts_node__subtree(self));
+}
+
+bool ts_node_is_missing(TSNode self) {
+ return ts_subtree_missing(ts_node__subtree(self));
+}
+
+bool ts_node_has_changes(TSNode self) {
+ return ts_subtree_has_changes(ts_node__subtree(self));
+}
+
+bool ts_node_has_error(TSNode self) {
+ return ts_subtree_error_cost(ts_node__subtree(self)) > 0;
+}
+
+TSNode ts_node_parent(TSNode self) {
+ TSNode node = ts_tree_get_cached_parent(self.tree, &self);
+ if (node.id) return node;
+
+ node = ts_tree_root_node(self.tree);
+ uint32_t end_byte = ts_node_end_byte(self);
+ if (node.id == self.id) return ts_node__null();
+
+ TSNode last_visible_node = node;
+ bool did_descend = true;
+ while (did_descend) {
+ did_descend = false;
+
+ TSNode child;
+ NodeChildIterator iterator = ts_node_iterate_children(&node);
+ while (ts_node_child_iterator_next(&iterator, &child)) {
+ if (
+ ts_node_start_byte(child) > ts_node_start_byte(self) ||
+ child.id == self.id
+ ) break;
+ if (iterator.position.bytes >= end_byte) {
+ node = child;
+ if (ts_node__is_relevant(child, true)) {
+ ts_tree_set_cached_parent(self.tree, &node, &last_visible_node);
+ last_visible_node = node;
+ }
+ did_descend = true;
+ break;
+ }
+ }
+ }
+
+ return last_visible_node;
+}
+
+TSNode ts_node_child(TSNode self, uint32_t child_index) {
+ return ts_node__child(self, child_index, true);
+}
+
+TSNode ts_node_named_child(TSNode self, uint32_t child_index) {
+ return ts_node__child(self, child_index, false);
+}
+
+TSNode ts_node_child_by_field_id(TSNode self, TSFieldId field_id) {
+recur:
+ if (!field_id || ts_node_child_count(self) == 0) return ts_node__null();
+
+ const TSFieldMapEntry *field_map, *field_map_end;
+ ts_language_field_map(
+ self.tree->language,
+ ts_node__subtree(self).ptr->production_id,
+ &field_map,
+ &field_map_end
+ );
+ if (field_map == field_map_end) return ts_node__null();
+
+ // The field mappings are sorted by their field id. Scan all
+ // the mappings to find the ones for the given field id.
+ while (field_map->field_id < field_id) {
+ field_map++;
+ if (field_map == field_map_end) return ts_node__null();
+ }
+ while (field_map_end[-1].field_id > field_id) {
+ field_map_end--;
+ if (field_map == field_map_end) return ts_node__null();
+ }
+
+ TSNode child;
+ NodeChildIterator iterator = ts_node_iterate_children(&self);
+ while (ts_node_child_iterator_next(&iterator, &child)) {
+ if (!ts_subtree_extra(ts_node__subtree(child))) {
+ uint32_t index = iterator.structural_child_index - 1;
+ if (index < field_map->child_index) continue;
+
+ // Hidden nodes' fields are "inherited" by their visible parent.
+ if (field_map->inherited) {
+
+ // If this is the *last* possible child node for this field,
+ // then perform a tail call to avoid recursion.
+ if (field_map + 1 == field_map_end) {
+ self = child;
+ goto recur;
+ }
+
+ // Otherwise, descend into this child, but if it doesn't contain
+ // the field, continue searching subsequent children.
+ else {
+ TSNode result = ts_node_child_by_field_id(child, field_id);
+ if (result.id) return result;
+ field_map++;
+ if (field_map == field_map_end) return ts_node__null();
+ }
+ }
+
+ else if (ts_node__is_relevant(child, true)) {
+ return child;
+ }
+
+ // If the field refers to a hidden node, return its first visible
+ // child.
+ else {
+ return ts_node_child(child, 0);
+ }
+ }
+ }
+
+ return ts_node__null();
+}
+
+TSNode ts_node_child_by_field_name(
+ TSNode self,
+ const char *name,
+ uint32_t name_length
+) {
+ TSFieldId field_id = ts_language_field_id_for_name(
+ self.tree->language,
+ name,
+ name_length
+ );
+ return ts_node_child_by_field_id(self, field_id);
+}
+
+uint32_t ts_node_child_count(TSNode self) {
+ Subtree tree = ts_node__subtree(self);
+ if (ts_subtree_child_count(tree) > 0) {
+ return tree.ptr->visible_child_count;
+ } else {
+ return 0;
+ }
+}
+
+uint32_t ts_node_named_child_count(TSNode self) {
+ Subtree tree = ts_node__subtree(self);
+ if (ts_subtree_child_count(tree) > 0) {
+ return tree.ptr->named_child_count;
+ } else {
+ return 0;
+ }
+}
+
+TSNode ts_node_next_sibling(TSNode self) {
+ return ts_node__next_sibling(self, true);
+}
+
+TSNode ts_node_next_named_sibling(TSNode self) {
+ return ts_node__next_sibling(self, false);
+}
+
+TSNode ts_node_prev_sibling(TSNode self) {
+ return ts_node__prev_sibling(self, true);
+}
+
+TSNode ts_node_prev_named_sibling(TSNode self) {
+ return ts_node__prev_sibling(self, false);
+}
+
+TSNode ts_node_first_child_for_byte(TSNode self, uint32_t byte) {
+ return ts_node__first_child_for_byte(self, byte, true);
+}
+
+TSNode ts_node_first_named_child_for_byte(TSNode self, uint32_t byte) {
+ return ts_node__first_child_for_byte(self, byte, false);
+}
+
+TSNode ts_node_descendant_for_byte_range(
+ TSNode self,
+ uint32_t start,
+ uint32_t end
+) {
+ return ts_node__descendant_for_byte_range(self, start, end, true);
+}
+
+TSNode ts_node_named_descendant_for_byte_range(
+ TSNode self,
+ uint32_t start,
+ uint32_t end
+) {
+ return ts_node__descendant_for_byte_range(self, start, end, false);
+}
+
+TSNode ts_node_descendant_for_point_range(
+ TSNode self,
+ TSPoint start,
+ TSPoint end
+) {
+ return ts_node__descendant_for_point_range(self, start, end, true);
+}
+
+TSNode ts_node_named_descendant_for_point_range(
+ TSNode self,
+ TSPoint start,
+ TSPoint end
+) {
+ return ts_node__descendant_for_point_range(self, start, end, false);
+}
+
+void ts_node_edit(TSNode *self, const TSInputEdit *edit) {
+ uint32_t start_byte = ts_node_start_byte(*self);
+ TSPoint start_point = ts_node_start_point(*self);
+
+ if (start_byte >= edit->old_end_byte) {
+ start_byte = edit->new_end_byte + (start_byte - edit->old_end_byte);
+ start_point = point_add(edit->new_end_point, point_sub(start_point, edit->old_end_point));
+ } else if (start_byte > edit->start_byte) {
+ start_byte = edit->new_end_byte;
+ start_point = edit->new_end_point;
+ }
+
+ self->context[0] = start_byte;
+ self->context[1] = start_point.row;
+ self->context[2] = start_point.column;
+}
diff --git a/src/tree_sitter/parser.c b/src/tree_sitter/parser.c
new file mode 100644
index 0000000000..dd222cd3c4
--- /dev/null
+++ b/src/tree_sitter/parser.c
@@ -0,0 +1,1879 @@
+#include <time.h>
+#include <assert.h>
+#include <stdio.h>
+#include <limits.h>
+#include <stdbool.h>
+#include "tree_sitter/api.h"
+#include "./alloc.h"
+#include "./array.h"
+#include "./atomic.h"
+#include "./clock.h"
+#include "./error_costs.h"
+#include "./get_changed_ranges.h"
+#include "./language.h"
+#include "./length.h"
+#include "./lexer.h"
+#include "./reduce_action.h"
+#include "./reusable_node.h"
+#include "./stack.h"
+#include "./subtree.h"
+#include "./tree.h"
+
+#define LOG(...) \
+ if (self->lexer.logger.log || self->dot_graph_file) { \
+ snprintf(self->lexer.debug_buffer, TREE_SITTER_SERIALIZATION_BUFFER_SIZE, __VA_ARGS__); \
+ ts_parser__log(self); \
+ }
+
+#define LOG_STACK() \
+ if (self->dot_graph_file) { \
+ ts_stack_print_dot_graph(self->stack, self->language, self->dot_graph_file); \
+ fputs("\n\n", self->dot_graph_file); \
+ }
+
+#define LOG_TREE(tree) \
+ if (self->dot_graph_file) { \
+ ts_subtree_print_dot_graph(tree, self->language, self->dot_graph_file); \
+ fputs("\n", self->dot_graph_file); \
+ }
+
+#define SYM_NAME(symbol) ts_language_symbol_name(self->language, symbol)
+
+#define TREE_NAME(tree) SYM_NAME(ts_subtree_symbol(tree))
+
+static const unsigned MAX_VERSION_COUNT = 6;
+static const unsigned MAX_VERSION_COUNT_OVERFLOW = 4;
+static const unsigned MAX_SUMMARY_DEPTH = 16;
+static const unsigned MAX_COST_DIFFERENCE = 16 * ERROR_COST_PER_SKIPPED_TREE;
+static const unsigned OP_COUNT_PER_TIMEOUT_CHECK = 100;
+
+typedef struct {
+ Subtree token;
+ Subtree last_external_token;
+ uint32_t byte_index;
+} TokenCache;
+
+struct TSParser {
+ Lexer lexer;
+ Stack *stack;
+ SubtreePool tree_pool;
+ const TSLanguage *language;
+ ReduceActionSet reduce_actions;
+ Subtree finished_tree;
+ SubtreeHeapData scratch_tree_data;
+ MutableSubtree scratch_tree;
+ TokenCache token_cache;
+ ReusableNode reusable_node;
+ void *external_scanner_payload;
+ FILE *dot_graph_file;
+ TSClock end_clock;
+ TSDuration timeout_duration;
+ unsigned accept_count;
+ unsigned operation_count;
+ const volatile size_t *cancellation_flag;
+ Subtree old_tree;
+ TSRangeArray included_range_differences;
+ unsigned included_range_difference_index;
+};
+
+typedef struct {
+ unsigned cost;
+ unsigned node_count;
+ int dynamic_precedence;
+ bool is_in_error;
+} ErrorStatus;
+
+typedef enum {
+ ErrorComparisonTakeLeft,
+ ErrorComparisonPreferLeft,
+ ErrorComparisonNone,
+ ErrorComparisonPreferRight,
+ ErrorComparisonTakeRight,
+} ErrorComparison;
+
+typedef struct {
+ const char *string;
+ uint32_t length;
+} TSStringInput;
+
+// StringInput
+
+static const char *ts_string_input_read(
+ void *_self,
+ uint32_t byte,
+ TSPoint pt,
+ uint32_t *length
+) {
+ (void)pt;
+ TSStringInput *self = (TSStringInput *)_self;
+ if (byte >= self->length) {
+ *length = 0;
+ return "";
+ } else {
+ *length = self->length - byte;
+ return self->string + byte;
+ }
+}
+
+// Parser - Private
+
+static void ts_parser__log(TSParser *self) {
+ if (self->lexer.logger.log) {
+ self->lexer.logger.log(
+ self->lexer.logger.payload,
+ TSLogTypeParse,
+ self->lexer.debug_buffer
+ );
+ }
+
+ if (self->dot_graph_file) {
+ fprintf(self->dot_graph_file, "graph {\nlabel=\"");
+ for (char *c = &self->lexer.debug_buffer[0]; *c != 0; c++) {
+ if (*c == '"') fputc('\\', self->dot_graph_file);
+ fputc(*c, self->dot_graph_file);
+ }
+ fprintf(self->dot_graph_file, "\"\n}\n\n");
+ }
+}
+
+static bool ts_parser__breakdown_top_of_stack(
+ TSParser *self,
+ StackVersion version
+) {
+ bool did_break_down = false;
+ bool pending = false;
+
+ do {
+ StackSliceArray pop = ts_stack_pop_pending(self->stack, version);
+ if (!pop.size) break;
+
+ did_break_down = true;
+ pending = false;
+ for (uint32_t i = 0; i < pop.size; i++) {
+ StackSlice slice = pop.contents[i];
+ TSStateId state = ts_stack_state(self->stack, slice.version);
+ Subtree parent = *array_front(&slice.subtrees);
+
+ for (uint32_t j = 0, n = ts_subtree_child_count(parent); j < n; j++) {
+ Subtree child = parent.ptr->children[j];
+ pending = ts_subtree_child_count(child) > 0;
+
+ if (ts_subtree_is_error(child)) {
+ state = ERROR_STATE;
+ } else if (!ts_subtree_extra(child)) {
+ state = ts_language_next_state(self->language, state, ts_subtree_symbol(child));
+ }
+
+ ts_subtree_retain(child);
+ ts_stack_push(self->stack, slice.version, child, pending, state);
+ }
+
+ for (uint32_t j = 1; j < slice.subtrees.size; j++) {
+ Subtree tree = slice.subtrees.contents[j];
+ ts_stack_push(self->stack, slice.version, tree, false, state);
+ }
+
+ ts_subtree_release(&self->tree_pool, parent);
+ array_delete(&slice.subtrees);
+
+ LOG("breakdown_top_of_stack tree:%s", TREE_NAME(parent));
+ LOG_STACK();
+ }
+ } while (pending);
+
+ return did_break_down;
+}
+
+static void ts_parser__breakdown_lookahead(
+ TSParser *self,
+ Subtree *lookahead,
+ TSStateId state,
+ ReusableNode *reusable_node
+) {
+ bool did_descend = false;
+ Subtree tree = reusable_node_tree(reusable_node);
+ while (ts_subtree_child_count(tree) > 0 && ts_subtree_parse_state(tree) != state) {
+ LOG("state_mismatch sym:%s", TREE_NAME(tree));
+ reusable_node_descend(reusable_node);
+ tree = reusable_node_tree(reusable_node);
+ did_descend = true;
+ }
+
+ if (did_descend) {
+ ts_subtree_release(&self->tree_pool, *lookahead);
+ *lookahead = tree;
+ ts_subtree_retain(*lookahead);
+ }
+}
+
+static ErrorComparison ts_parser__compare_versions(
+ TSParser *self,
+ ErrorStatus a,
+ ErrorStatus b
+) {
+ (void)self;
+ if (!a.is_in_error && b.is_in_error) {
+ if (a.cost < b.cost) {
+ return ErrorComparisonTakeLeft;
+ } else {
+ return ErrorComparisonPreferLeft;
+ }
+ }
+
+ if (a.is_in_error && !b.is_in_error) {
+ if (b.cost < a.cost) {
+ return ErrorComparisonTakeRight;
+ } else {
+ return ErrorComparisonPreferRight;
+ }
+ }
+
+ if (a.cost < b.cost) {
+ if ((b.cost - a.cost) * (1 + a.node_count) > MAX_COST_DIFFERENCE) {
+ return ErrorComparisonTakeLeft;
+ } else {
+ return ErrorComparisonPreferLeft;
+ }
+ }
+
+ if (b.cost < a.cost) {
+ if ((a.cost - b.cost) * (1 + b.node_count) > MAX_COST_DIFFERENCE) {
+ return ErrorComparisonTakeRight;
+ } else {
+ return ErrorComparisonPreferRight;
+ }
+ }
+
+ if (a.dynamic_precedence > b.dynamic_precedence) return ErrorComparisonPreferLeft;
+ if (b.dynamic_precedence > a.dynamic_precedence) return ErrorComparisonPreferRight;
+ return ErrorComparisonNone;
+}
+
+static ErrorStatus ts_parser__version_status(
+ TSParser *self,
+ StackVersion version
+) {
+ unsigned cost = ts_stack_error_cost(self->stack, version);
+ bool is_paused = ts_stack_is_paused(self->stack, version);
+ if (is_paused) cost += ERROR_COST_PER_SKIPPED_TREE;
+ return (ErrorStatus) {
+ .cost = cost,
+ .node_count = ts_stack_node_count_since_error(self->stack, version),
+ .dynamic_precedence = ts_stack_dynamic_precedence(self->stack, version),
+ .is_in_error = is_paused || ts_stack_state(self->stack, version) == ERROR_STATE
+ };
+}
+
+static bool ts_parser__better_version_exists(
+ TSParser *self,
+ StackVersion version,
+ bool is_in_error,
+ unsigned cost
+) {
+ if (self->finished_tree.ptr && ts_subtree_error_cost(self->finished_tree) <= cost) {
+ return true;
+ }
+
+ Length position = ts_stack_position(self->stack, version);
+ ErrorStatus status = {
+ .cost = cost,
+ .is_in_error = is_in_error,
+ .dynamic_precedence = ts_stack_dynamic_precedence(self->stack, version),
+ .node_count = ts_stack_node_count_since_error(self->stack, version),
+ };
+
+ for (StackVersion i = 0, n = ts_stack_version_count(self->stack); i < n; i++) {
+ if (i == version ||
+ !ts_stack_is_active(self->stack, i) ||
+ ts_stack_position(self->stack, i).bytes < position.bytes) continue;
+ ErrorStatus status_i = ts_parser__version_status(self, i);
+ switch (ts_parser__compare_versions(self, status, status_i)) {
+ case ErrorComparisonTakeRight:
+ return true;
+ case ErrorComparisonPreferRight:
+ if (ts_stack_can_merge(self->stack, i, version)) return true;
+ default:
+ break;
+ }
+ }
+
+ return false;
+}
+
+static void ts_parser__restore_external_scanner(
+ TSParser *self,
+ Subtree external_token
+) {
+ if (external_token.ptr) {
+ self->language->external_scanner.deserialize(
+ self->external_scanner_payload,
+ ts_external_scanner_state_data(&external_token.ptr->external_scanner_state),
+ external_token.ptr->external_scanner_state.length
+ );
+ } else {
+ self->language->external_scanner.deserialize(self->external_scanner_payload, NULL, 0);
+ }
+}
+
+static bool ts_parser__can_reuse_first_leaf(
+ TSParser *self,
+ TSStateId state,
+ Subtree tree,
+ TableEntry *table_entry
+) {
+ TSLexMode current_lex_mode = self->language->lex_modes[state];
+ TSSymbol leaf_symbol = ts_subtree_leaf_symbol(tree);
+ TSStateId leaf_state = ts_subtree_leaf_parse_state(tree);
+ TSLexMode leaf_lex_mode = self->language->lex_modes[leaf_state];
+
+ // At the end of a non-terminal extra node, the lexer normally returns
+ // NULL, which indicates that the parser should look for a reduce action
+ // at symbol `0`. Avoid reusing tokens in this situation to ensure that
+ // the same thing happens when incrementally reparsing.
+ if (current_lex_mode.lex_state == (uint16_t)(-1)) return false;
+
+ // If the token was created in a state with the same set of lookaheads, it is reusable.
+ if (
+ table_entry->action_count > 0 &&
+ memcmp(&leaf_lex_mode, &current_lex_mode, sizeof(TSLexMode)) == 0 &&
+ (
+ leaf_symbol != self->language->keyword_capture_token ||
+ (!ts_subtree_is_keyword(tree) && ts_subtree_parse_state(tree) == state)
+ )
+ ) return true;
+
+ // Empty tokens are not reusable in states with different lookaheads.
+ if (ts_subtree_size(tree).bytes == 0 && leaf_symbol != ts_builtin_sym_end) return false;
+
+ // If the current state allows external tokens or other tokens that conflict with this
+ // token, this token is not reusable.
+ return current_lex_mode.external_lex_state == 0 && table_entry->is_reusable;
+}
+
+static Subtree ts_parser__lex(
+ TSParser *self,
+ StackVersion version,
+ TSStateId parse_state
+) {
+ Length start_position = ts_stack_position(self->stack, version);
+ Subtree external_token = ts_stack_last_external_token(self->stack, version);
+ TSLexMode lex_mode = self->language->lex_modes[parse_state];
+ if (lex_mode.lex_state == (uint16_t)-1) return NULL_SUBTREE;
+ const bool *valid_external_tokens = ts_language_enabled_external_tokens(
+ self->language,
+ lex_mode.external_lex_state
+ );
+
+ bool found_external_token = false;
+ bool error_mode = parse_state == ERROR_STATE;
+ bool skipped_error = false;
+ int32_t first_error_character = 0;
+ Length error_start_position = length_zero();
+ Length error_end_position = length_zero();
+ uint32_t lookahead_end_byte = 0;
+ ts_lexer_reset(&self->lexer, start_position);
+
+ for (;;) {
+ Length current_position = self->lexer.current_position;
+
+ if (valid_external_tokens) {
+ LOG(
+ "lex_external state:%d, row:%u, column:%u",
+ lex_mode.external_lex_state,
+ current_position.extent.row + 1,
+ current_position.extent.column
+ );
+ ts_lexer_start(&self->lexer);
+ ts_parser__restore_external_scanner(self, external_token);
+ bool found_token = self->language->external_scanner.scan(
+ self->external_scanner_payload,
+ &self->lexer.data,
+ valid_external_tokens
+ );
+ ts_lexer_finish(&self->lexer, &lookahead_end_byte);
+
+ // Zero-length external tokens are generally allowed, but they're not
+ // allowed right after a syntax error. This is for two reasons:
+ // 1. After a syntax error, the lexer is looking for any possible token,
+ // as opposed to the specific set of tokens that are valid in some
+ // parse state. In this situation, it's very easy for an external
+ // scanner to produce unwanted zero-length tokens.
+ // 2. The parser sometimes inserts *missing* tokens to recover from
+ // errors. These tokens are also zero-length. If we allow more
+ // zero-length tokens to be created after missing tokens, it
+ // can lead to infinite loops. Forbidding zero-length tokens
+ // right at the point of error recovery is a conservative strategy
+ // for preventing this kind of infinite loop.
+ if (found_token && (
+ self->lexer.token_end_position.bytes > current_position.bytes ||
+ (!error_mode && ts_stack_has_advanced_since_error(self->stack, version))
+ )) {
+ found_external_token = true;
+ break;
+ }
+
+ ts_lexer_reset(&self->lexer, current_position);
+ }
+
+ LOG(
+ "lex_internal state:%d, row:%u, column:%u",
+ lex_mode.lex_state,
+ current_position.extent.row + 1,
+ current_position.extent.column
+ );
+ ts_lexer_start(&self->lexer);
+ bool found_token = self->language->lex_fn(&self->lexer.data, lex_mode.lex_state);
+ ts_lexer_finish(&self->lexer, &lookahead_end_byte);
+ if (found_token) break;
+
+ if (!error_mode) {
+ error_mode = true;
+ lex_mode = self->language->lex_modes[ERROR_STATE];
+ valid_external_tokens = ts_language_enabled_external_tokens(
+ self->language,
+ lex_mode.external_lex_state
+ );
+ ts_lexer_reset(&self->lexer, start_position);
+ continue;
+ }
+
+ if (!skipped_error) {
+ LOG("skip_unrecognized_character");
+ skipped_error = true;
+ error_start_position = self->lexer.token_start_position;
+ error_end_position = self->lexer.token_start_position;
+ first_error_character = self->lexer.data.lookahead;
+ }
+
+ if (self->lexer.current_position.bytes == error_end_position.bytes) {
+ if (self->lexer.data.eof(&self->lexer.data)) {
+ self->lexer.data.result_symbol = ts_builtin_sym_error;
+ break;
+ }
+ self->lexer.data.advance(&self->lexer.data, false);
+ }
+
+ error_end_position = self->lexer.current_position;
+ }
+
+ Subtree result;
+ if (skipped_error) {
+ Length padding = length_sub(error_start_position, start_position);
+ Length size = length_sub(error_end_position, error_start_position);
+ uint32_t lookahead_bytes = lookahead_end_byte - error_end_position.bytes;
+ result = ts_subtree_new_error(
+ &self->tree_pool,
+ first_error_character,
+ padding,
+ size,
+ lookahead_bytes,
+ parse_state,
+ self->language
+ );
+
+ LOG(
+ "lexed_lookahead sym:%s, size:%u, character:'%c'",
+ SYM_NAME(ts_subtree_symbol(result)),
+ ts_subtree_total_size(result).bytes,
+ first_error_character
+ );
+ } else {
+ if (self->lexer.token_end_position.bytes < self->lexer.token_start_position.bytes) {
+ self->lexer.token_start_position = self->lexer.token_end_position;
+ }
+
+ bool is_keyword = false;
+ TSSymbol symbol = self->lexer.data.result_symbol;
+ Length padding = length_sub(self->lexer.token_start_position, start_position);
+ Length size = length_sub(self->lexer.token_end_position, self->lexer.token_start_position);
+ uint32_t lookahead_bytes = lookahead_end_byte - self->lexer.token_end_position.bytes;
+
+ if (found_external_token) {
+ symbol = self->language->external_scanner.symbol_map[symbol];
+ } else if (symbol == self->language->keyword_capture_token && symbol != 0) {
+ uint32_t end_byte = self->lexer.token_end_position.bytes;
+ ts_lexer_reset(&self->lexer, self->lexer.token_start_position);
+ ts_lexer_start(&self->lexer);
+ if (
+ self->language->keyword_lex_fn(&self->lexer.data, 0) &&
+ self->lexer.token_end_position.bytes == end_byte &&
+ ts_language_has_actions(self->language, parse_state, self->lexer.data.result_symbol)
+ ) {
+ is_keyword = true;
+ symbol = self->lexer.data.result_symbol;
+ }
+ }
+
+ result = ts_subtree_new_leaf(
+ &self->tree_pool,
+ symbol,
+ padding,
+ size,
+ lookahead_bytes,
+ parse_state,
+ found_external_token,
+ is_keyword,
+ self->language
+ );
+
+ if (found_external_token) {
+ unsigned length = self->language->external_scanner.serialize(
+ self->external_scanner_payload,
+ self->lexer.debug_buffer
+ );
+ ts_external_scanner_state_init(
+ &((SubtreeHeapData *)result.ptr)->external_scanner_state,
+ self->lexer.debug_buffer,
+ length
+ );
+ }
+
+ LOG(
+ "lexed_lookahead sym:%s, size:%u",
+ SYM_NAME(ts_subtree_symbol(result)),
+ ts_subtree_total_size(result).bytes
+ );
+ }
+
+ return result;
+}
+
+static Subtree ts_parser__get_cached_token(
+ TSParser *self,
+ TSStateId state,
+ size_t position,
+ Subtree last_external_token,
+ TableEntry *table_entry
+) {
+ TokenCache *cache = &self->token_cache;
+ if (
+ cache->token.ptr && cache->byte_index == position &&
+ ts_subtree_external_scanner_state_eq(cache->last_external_token, last_external_token)
+ ) {
+ ts_language_table_entry(self->language, state, ts_subtree_symbol(cache->token), table_entry);
+ if (ts_parser__can_reuse_first_leaf(self, state, cache->token, table_entry)) {
+ ts_subtree_retain(cache->token);
+ return cache->token;
+ }
+ }
+ return NULL_SUBTREE;
+}
+
+static void ts_parser__set_cached_token(
+ TSParser *self,
+ size_t byte_index,
+ Subtree last_external_token,
+ Subtree token
+) {
+ TokenCache *cache = &self->token_cache;
+ if (token.ptr) ts_subtree_retain(token);
+ if (last_external_token.ptr) ts_subtree_retain(last_external_token);
+ if (cache->token.ptr) ts_subtree_release(&self->tree_pool, cache->token);
+ if (cache->last_external_token.ptr) ts_subtree_release(&self->tree_pool, cache->last_external_token);
+ cache->token = token;
+ cache->byte_index = byte_index;
+ cache->last_external_token = last_external_token;
+}
+
+static bool ts_parser__has_included_range_difference(
+ const TSParser *self,
+ uint32_t start_position,
+ uint32_t end_position
+) {
+ return ts_range_array_intersects(
+ &self->included_range_differences,
+ self->included_range_difference_index,
+ start_position,
+ end_position
+ );
+}
+
+static Subtree ts_parser__reuse_node(
+ TSParser *self,
+ StackVersion version,
+ TSStateId *state,
+ uint32_t position,
+ Subtree last_external_token,
+ TableEntry *table_entry
+) {
+ Subtree result;
+ while ((result = reusable_node_tree(&self->reusable_node)).ptr) {
+ uint32_t byte_offset = reusable_node_byte_offset(&self->reusable_node);
+ uint32_t end_byte_offset = byte_offset + ts_subtree_total_bytes(result);
+
+ // Do not reuse an EOF node if the included ranges array has changes
+ // later on in the file.
+ if (ts_subtree_is_eof(result)) end_byte_offset = UINT32_MAX;
+
+ if (byte_offset > position) {
+ LOG("before_reusable_node symbol:%s", TREE_NAME(result));
+ break;
+ }
+
+ if (byte_offset < position) {
+ LOG("past_reusable_node symbol:%s", TREE_NAME(result));
+ if (end_byte_offset <= position || !reusable_node_descend(&self->reusable_node)) {
+ reusable_node_advance(&self->reusable_node);
+ }
+ continue;
+ }
+
+ if (!ts_subtree_external_scanner_state_eq(self->reusable_node.last_external_token, last_external_token)) {
+ LOG("reusable_node_has_different_external_scanner_state symbol:%s", TREE_NAME(result));
+ reusable_node_advance(&self->reusable_node);
+ continue;
+ }
+
+ const char *reason = NULL;
+ if (ts_subtree_has_changes(result)) {
+ reason = "has_changes";
+ } else if (ts_subtree_is_error(result)) {
+ reason = "is_error";
+ } else if (ts_subtree_missing(result)) {
+ reason = "is_missing";
+ } else if (ts_subtree_is_fragile(result)) {
+ reason = "is_fragile";
+ } else if (ts_parser__has_included_range_difference(self, byte_offset, end_byte_offset)) {
+ reason = "contains_different_included_range";
+ }
+
+ if (reason) {
+ LOG("cant_reuse_node_%s tree:%s", reason, TREE_NAME(result));
+ if (!reusable_node_descend(&self->reusable_node)) {
+ reusable_node_advance(&self->reusable_node);
+ ts_parser__breakdown_top_of_stack(self, version);
+ *state = ts_stack_state(self->stack, version);
+ }
+ continue;
+ }
+
+ TSSymbol leaf_symbol = ts_subtree_leaf_symbol(result);
+ ts_language_table_entry(self->language, *state, leaf_symbol, table_entry);
+ if (!ts_parser__can_reuse_first_leaf(self, *state, result, table_entry)) {
+ LOG(
+ "cant_reuse_node symbol:%s, first_leaf_symbol:%s",
+ TREE_NAME(result),
+ SYM_NAME(leaf_symbol)
+ );
+ reusable_node_advance_past_leaf(&self->reusable_node);
+ break;
+ }
+
+ LOG("reuse_node symbol:%s", TREE_NAME(result));
+ ts_subtree_retain(result);
+ return result;
+ }
+
+ return NULL_SUBTREE;
+}
+
+static bool ts_parser__select_tree(TSParser *self, Subtree left, Subtree right) {
+ if (!left.ptr) return true;
+ if (!right.ptr) return false;
+
+ if (ts_subtree_error_cost(right) < ts_subtree_error_cost(left)) {
+ LOG("select_smaller_error symbol:%s, over_symbol:%s", TREE_NAME(right), TREE_NAME(left));
+ return true;
+ }
+
+ if (ts_subtree_error_cost(left) < ts_subtree_error_cost(right)) {
+ LOG("select_smaller_error symbol:%s, over_symbol:%s", TREE_NAME(left), TREE_NAME(right));
+ return false;
+ }
+
+ if (ts_subtree_dynamic_precedence(right) > ts_subtree_dynamic_precedence(left)) {
+ LOG("select_higher_precedence symbol:%s, prec:%u, over_symbol:%s, other_prec:%u",
+ TREE_NAME(right), ts_subtree_dynamic_precedence(right), TREE_NAME(left),
+ ts_subtree_dynamic_precedence(left));
+ return true;
+ }
+
+ if (ts_subtree_dynamic_precedence(left) > ts_subtree_dynamic_precedence(right)) {
+ LOG("select_higher_precedence symbol:%s, prec:%u, over_symbol:%s, other_prec:%u",
+ TREE_NAME(left), ts_subtree_dynamic_precedence(left), TREE_NAME(right),
+ ts_subtree_dynamic_precedence(right));
+ return false;
+ }
+
+ if (ts_subtree_error_cost(left) > 0) return true;
+
+ int comparison = ts_subtree_compare(left, right);
+ switch (comparison) {
+ case -1:
+ LOG("select_earlier symbol:%s, over_symbol:%s", TREE_NAME(left), TREE_NAME(right));
+ return false;
+ break;
+ case 1:
+ LOG("select_earlier symbol:%s, over_symbol:%s", TREE_NAME(right), TREE_NAME(left));
+ return true;
+ default:
+ LOG("select_existing symbol:%s, over_symbol:%s", TREE_NAME(left), TREE_NAME(right));
+ return false;
+ }
+}
+
+static void ts_parser__shift(
+ TSParser *self,
+ StackVersion version,
+ TSStateId state,
+ Subtree lookahead,
+ bool extra
+) {
+ Subtree subtree_to_push;
+ if (extra != ts_subtree_extra(lookahead)) {
+ MutableSubtree result = ts_subtree_make_mut(&self->tree_pool, lookahead);
+ ts_subtree_set_extra(&result);
+ subtree_to_push = ts_subtree_from_mut(result);
+ } else {
+ subtree_to_push = lookahead;
+ }
+
+ bool is_pending = ts_subtree_child_count(subtree_to_push) > 0;
+ ts_stack_push(self->stack, version, subtree_to_push, is_pending, state);
+ if (ts_subtree_has_external_tokens(subtree_to_push)) {
+ ts_stack_set_last_external_token(
+ self->stack, version, ts_subtree_last_external_token(subtree_to_push)
+ );
+ }
+}
+
+static bool ts_parser__replace_children(
+ TSParser *self,
+ MutableSubtree *tree,
+ SubtreeArray *children
+) {
+ *self->scratch_tree.ptr = *tree->ptr;
+ self->scratch_tree.ptr->child_count = 0;
+ ts_subtree_set_children(self->scratch_tree, children->contents, children->size, self->language);
+ if (ts_parser__select_tree(self, ts_subtree_from_mut(*tree), ts_subtree_from_mut(self->scratch_tree))) {
+ *tree->ptr = *self->scratch_tree.ptr;
+ return true;
+ } else {
+ return false;
+ }
+}
+
+static StackVersion ts_parser__reduce(
+ TSParser *self,
+ StackVersion version,
+ TSSymbol symbol,
+ uint32_t count,
+ int dynamic_precedence,
+ uint16_t production_id,
+ bool is_fragile,
+ bool is_extra
+) {
+ uint32_t initial_version_count = ts_stack_version_count(self->stack);
+ uint32_t removed_version_count = 0;
+ StackSliceArray pop = ts_stack_pop_count(self->stack, version, count);
+
+ for (uint32_t i = 0; i < pop.size; i++) {
+ StackSlice slice = pop.contents[i];
+ StackVersion slice_version = slice.version - removed_version_count;
+
+ // Error recovery can sometimes cause lots of stack versions to merge,
+ // such that a single pop operation can produce a lots of slices.
+ // Avoid creating too many stack versions in that situation.
+ if (i > 0 && slice_version > MAX_VERSION_COUNT + MAX_VERSION_COUNT_OVERFLOW) {
+ ts_stack_remove_version(self->stack, slice_version);
+ ts_subtree_array_delete(&self->tree_pool, &slice.subtrees);
+ removed_version_count++;
+ while (i + 1 < pop.size) {
+ StackSlice next_slice = pop.contents[i + 1];
+ if (next_slice.version != slice.version) break;
+ ts_subtree_array_delete(&self->tree_pool, &next_slice.subtrees);
+ i++;
+ }
+ continue;
+ }
+
+ // Extra tokens on top of the stack should not be included in this new parent
+ // node. They will be re-pushed onto the stack after the parent node is
+ // created and pushed.
+ SubtreeArray children = slice.subtrees;
+ while (children.size > 0 && ts_subtree_extra(children.contents[children.size - 1])) {
+ children.size--;
+ }
+
+ MutableSubtree parent = ts_subtree_new_node(&self->tree_pool,
+ symbol, &children, production_id, self->language
+ );
+
+ // This pop operation may have caused multiple stack versions to collapse
+ // into one, because they all diverged from a common state. In that case,
+ // choose one of the arrays of trees to be the parent node's children, and
+ // delete the rest of the tree arrays.
+ while (i + 1 < pop.size) {
+ StackSlice next_slice = pop.contents[i + 1];
+ if (next_slice.version != slice.version) break;
+ i++;
+
+ SubtreeArray children = next_slice.subtrees;
+ while (children.size > 0 && ts_subtree_extra(children.contents[children.size - 1])) {
+ children.size--;
+ }
+
+ if (ts_parser__replace_children(self, &parent, &children)) {
+ ts_subtree_array_delete(&self->tree_pool, &slice.subtrees);
+ slice = next_slice;
+ } else {
+ ts_subtree_array_delete(&self->tree_pool, &next_slice.subtrees);
+ }
+ }
+
+ parent.ptr->dynamic_precedence += dynamic_precedence;
+ parent.ptr->production_id = production_id;
+
+ TSStateId state = ts_stack_state(self->stack, slice_version);
+ TSStateId next_state = ts_language_next_state(self->language, state, symbol);
+ if (is_extra) parent.ptr->extra = true;
+ if (is_fragile || pop.size > 1 || initial_version_count > 1) {
+ parent.ptr->fragile_left = true;
+ parent.ptr->fragile_right = true;
+ parent.ptr->parse_state = TS_TREE_STATE_NONE;
+ } else {
+ parent.ptr->parse_state = state;
+ }
+
+ // Push the parent node onto the stack, along with any extra tokens that
+ // were previously on top of the stack.
+ ts_stack_push(self->stack, slice_version, ts_subtree_from_mut(parent), false, next_state);
+ for (uint32_t j = parent.ptr->child_count; j < slice.subtrees.size; j++) {
+ ts_stack_push(self->stack, slice_version, slice.subtrees.contents[j], false, next_state);
+ }
+
+ for (StackVersion j = 0; j < slice_version; j++) {
+ if (j == version) continue;
+ if (ts_stack_merge(self->stack, j, slice_version)) {
+ removed_version_count++;
+ break;
+ }
+ }
+ }
+
+ // Return the first new stack version that was created.
+ return ts_stack_version_count(self->stack) > initial_version_count
+ ? initial_version_count
+ : STACK_VERSION_NONE;
+}
+
+static void ts_parser__accept(
+ TSParser *self,
+ StackVersion version,
+ Subtree lookahead
+) {
+ assert(ts_subtree_is_eof(lookahead));
+ ts_stack_push(self->stack, version, lookahead, false, 1);
+
+ StackSliceArray pop = ts_stack_pop_all(self->stack, version);
+ for (uint32_t i = 0; i < pop.size; i++) {
+ SubtreeArray trees = pop.contents[i].subtrees;
+
+ Subtree root = NULL_SUBTREE;
+ for (uint32_t j = trees.size - 1; j + 1 > 0; j--) {
+ Subtree child = trees.contents[j];
+ if (!ts_subtree_extra(child)) {
+ assert(!child.data.is_inline);
+ uint32_t child_count = ts_subtree_child_count(child);
+ for (uint32_t k = 0; k < child_count; k++) {
+ ts_subtree_retain(child.ptr->children[k]);
+ }
+ array_splice(&trees, j, 1, child_count, child.ptr->children);
+ root = ts_subtree_from_mut(ts_subtree_new_node(
+ &self->tree_pool,
+ ts_subtree_symbol(child),
+ &trees,
+ child.ptr->production_id,
+ self->language
+ ));
+ ts_subtree_release(&self->tree_pool, child);
+ break;
+ }
+ }
+
+ assert(root.ptr);
+ self->accept_count++;
+
+ if (self->finished_tree.ptr) {
+ if (ts_parser__select_tree(self, self->finished_tree, root)) {
+ ts_subtree_release(&self->tree_pool, self->finished_tree);
+ self->finished_tree = root;
+ } else {
+ ts_subtree_release(&self->tree_pool, root);
+ }
+ } else {
+ self->finished_tree = root;
+ }
+ }
+
+ ts_stack_remove_version(self->stack, pop.contents[0].version);
+ ts_stack_halt(self->stack, version);
+}
+
+static bool ts_parser__do_all_potential_reductions(
+ TSParser *self,
+ StackVersion starting_version,
+ TSSymbol lookahead_symbol
+) {
+ uint32_t initial_version_count = ts_stack_version_count(self->stack);
+
+ bool can_shift_lookahead_symbol = false;
+ StackVersion version = starting_version;
+ for (unsigned i = 0; true; i++) {
+ uint32_t version_count = ts_stack_version_count(self->stack);
+ if (version >= version_count) break;
+
+ bool merged = false;
+ for (StackVersion i = initial_version_count; i < version; i++) {
+ if (ts_stack_merge(self->stack, i, version)) {
+ merged = true;
+ break;
+ }
+ }
+ if (merged) continue;
+
+ TSStateId state = ts_stack_state(self->stack, version);
+ bool has_shift_action = false;
+ array_clear(&self->reduce_actions);
+
+ TSSymbol first_symbol, end_symbol;
+ if (lookahead_symbol != 0) {
+ first_symbol = lookahead_symbol;
+ end_symbol = lookahead_symbol + 1;
+ } else {
+ first_symbol = 1;
+ end_symbol = self->language->token_count;
+ }
+
+ for (TSSymbol symbol = first_symbol; symbol < end_symbol; symbol++) {
+ TableEntry entry;
+ ts_language_table_entry(self->language, state, symbol, &entry);
+ for (uint32_t i = 0; i < entry.action_count; i++) {
+ TSParseAction action = entry.actions[i];
+ switch (action.type) {
+ case TSParseActionTypeShift:
+ case TSParseActionTypeRecover:
+ if (!action.params.shift.extra && !action.params.shift.repetition) has_shift_action = true;
+ break;
+ case TSParseActionTypeReduce:
+ if (action.params.reduce.child_count > 0)
+ ts_reduce_action_set_add(&self->reduce_actions, (ReduceAction){
+ .symbol = action.params.reduce.symbol,
+ .count = action.params.reduce.child_count,
+ .dynamic_precedence = action.params.reduce.dynamic_precedence,
+ .production_id = action.params.reduce.production_id,
+ });
+ default:
+ break;
+ }
+ }
+ }
+
+ StackVersion reduction_version = STACK_VERSION_NONE;
+ for (uint32_t i = 0; i < self->reduce_actions.size; i++) {
+ ReduceAction action = self->reduce_actions.contents[i];
+
+ reduction_version = ts_parser__reduce(
+ self, version, action.symbol, action.count,
+ action.dynamic_precedence, action.production_id,
+ true, false
+ );
+ }
+
+ if (has_shift_action) {
+ can_shift_lookahead_symbol = true;
+ } else if (reduction_version != STACK_VERSION_NONE && i < MAX_VERSION_COUNT) {
+ ts_stack_renumber_version(self->stack, reduction_version, version);
+ continue;
+ } else if (lookahead_symbol != 0) {
+ ts_stack_remove_version(self->stack, version);
+ }
+
+ if (version == starting_version) {
+ version = version_count;
+ } else {
+ version++;
+ }
+ }
+
+ return can_shift_lookahead_symbol;
+}
+
+static void ts_parser__handle_error(
+ TSParser *self,
+ StackVersion version,
+ TSSymbol lookahead_symbol
+) {
+ uint32_t previous_version_count = ts_stack_version_count(self->stack);
+
+ // Perform any reductions that can happen in this state, regardless of the lookahead. After
+ // skipping one or more invalid tokens, the parser might find a token that would have allowed
+ // a reduction to take place.
+ ts_parser__do_all_potential_reductions(self, version, 0);
+ uint32_t version_count = ts_stack_version_count(self->stack);
+ Length position = ts_stack_position(self->stack, version);
+
+ // Push a discontinuity onto the stack. Merge all of the stack versions that
+ // were created in the previous step.
+ bool did_insert_missing_token = false;
+ for (StackVersion v = version; v < version_count;) {
+ if (!did_insert_missing_token) {
+ TSStateId state = ts_stack_state(self->stack, v);
+ for (TSSymbol missing_symbol = 1;
+ missing_symbol < self->language->token_count;
+ missing_symbol++) {
+ TSStateId state_after_missing_symbol = ts_language_next_state(
+ self->language, state, missing_symbol
+ );
+ if (state_after_missing_symbol == 0 || state_after_missing_symbol == state) {
+ continue;
+ }
+
+ if (ts_language_has_reduce_action(
+ self->language,
+ state_after_missing_symbol,
+ lookahead_symbol
+ )) {
+ // In case the parser is currently outside of any included range, the lexer will
+ // snap to the beginning of the next included range. The missing token's padding
+ // must be assigned to position it within the next included range.
+ ts_lexer_reset(&self->lexer, position);
+ ts_lexer_mark_end(&self->lexer);
+ Length padding = length_sub(self->lexer.token_end_position, position);
+
+ StackVersion version_with_missing_tree = ts_stack_copy_version(self->stack, v);
+ Subtree missing_tree = ts_subtree_new_missing_leaf(
+ &self->tree_pool, missing_symbol, padding, self->language
+ );
+ ts_stack_push(
+ self->stack, version_with_missing_tree,
+ missing_tree, false,
+ state_after_missing_symbol
+ );
+
+ if (ts_parser__do_all_potential_reductions(
+ self, version_with_missing_tree,
+ lookahead_symbol
+ )) {
+ LOG(
+ "recover_with_missing symbol:%s, state:%u",
+ SYM_NAME(missing_symbol),
+ ts_stack_state(self->stack, version_with_missing_tree)
+ );
+ did_insert_missing_token = true;
+ break;
+ }
+ }
+ }
+ }
+
+ ts_stack_push(self->stack, v, NULL_SUBTREE, false, ERROR_STATE);
+ v = (v == version) ? previous_version_count : v + 1;
+ }
+
+ for (unsigned i = previous_version_count; i < version_count; i++) {
+ bool did_merge = ts_stack_merge(self->stack, version, previous_version_count);
+ assert(did_merge);
+ }
+
+ ts_stack_record_summary(self->stack, version, MAX_SUMMARY_DEPTH);
+ LOG_STACK();
+}
+
+static bool ts_parser__recover_to_state(
+ TSParser *self,
+ StackVersion version,
+ unsigned depth,
+ TSStateId goal_state
+) {
+ StackSliceArray pop = ts_stack_pop_count(self->stack, version, depth);
+ StackVersion previous_version = STACK_VERSION_NONE;
+
+ for (unsigned i = 0; i < pop.size; i++) {
+ StackSlice slice = pop.contents[i];
+
+ if (slice.version == previous_version) {
+ ts_subtree_array_delete(&self->tree_pool, &slice.subtrees);
+ array_erase(&pop, i--);
+ continue;
+ }
+
+ if (ts_stack_state(self->stack, slice.version) != goal_state) {
+ ts_stack_halt(self->stack, slice.version);
+ ts_subtree_array_delete(&self->tree_pool, &slice.subtrees);
+ array_erase(&pop, i--);
+ continue;
+ }
+
+ SubtreeArray error_trees = ts_stack_pop_error(self->stack, slice.version);
+ if (error_trees.size > 0) {
+ assert(error_trees.size == 1);
+ Subtree error_tree = error_trees.contents[0];
+ uint32_t error_child_count = ts_subtree_child_count(error_tree);
+ if (error_child_count > 0) {
+ array_splice(&slice.subtrees, 0, 0, error_child_count, error_tree.ptr->children);
+ for (unsigned j = 0; j < error_child_count; j++) {
+ ts_subtree_retain(slice.subtrees.contents[j]);
+ }
+ }
+ ts_subtree_array_delete(&self->tree_pool, &error_trees);
+ }
+
+ SubtreeArray trailing_extras = ts_subtree_array_remove_trailing_extras(&slice.subtrees);
+
+ if (slice.subtrees.size > 0) {
+ Subtree error = ts_subtree_new_error_node(&self->tree_pool, &slice.subtrees, true, self->language);
+ ts_stack_push(self->stack, slice.version, error, false, goal_state);
+ } else {
+ array_delete(&slice.subtrees);
+ }
+
+ for (unsigned j = 0; j < trailing_extras.size; j++) {
+ Subtree tree = trailing_extras.contents[j];
+ ts_stack_push(self->stack, slice.version, tree, false, goal_state);
+ }
+
+ previous_version = slice.version;
+ array_delete(&trailing_extras);
+ }
+
+ return previous_version != STACK_VERSION_NONE;
+}
+
+static void ts_parser__recover(
+ TSParser *self,
+ StackVersion version,
+ Subtree lookahead
+) {
+ bool did_recover = false;
+ unsigned previous_version_count = ts_stack_version_count(self->stack);
+ Length position = ts_stack_position(self->stack, version);
+ StackSummary *summary = ts_stack_get_summary(self->stack, version);
+ unsigned node_count_since_error = ts_stack_node_count_since_error(self->stack, version);
+ unsigned current_error_cost = ts_stack_error_cost(self->stack, version);
+
+ // When the parser is in the error state, there are two strategies for recovering with a
+ // given lookahead token:
+ // 1. Find a previous state on the stack in which that lookahead token would be valid. Then,
+ // create a new stack version that is in that state again. This entails popping all of the
+ // subtrees that have been pushed onto the stack since that previous state, and wrapping
+ // them in an ERROR node.
+ // 2. Wrap the lookahead token in an ERROR node, push that ERROR node onto the stack, and
+ // move on to the next lookahead token, remaining in the error state.
+ //
+ // First, try the strategy 1. Upon entering the error state, the parser recorded a summary
+ // of the previous parse states and their depths. Look at each state in the summary, to see
+ // if the current lookahead token would be valid in that state.
+ if (summary && !ts_subtree_is_error(lookahead)) {
+ for (unsigned i = 0; i < summary->size; i++) {
+ StackSummaryEntry entry = summary->contents[i];
+
+ if (entry.state == ERROR_STATE) continue;
+ if (entry.position.bytes == position.bytes) continue;
+ unsigned depth = entry.depth;
+ if (node_count_since_error > 0) depth++;
+
+ // Do not recover in ways that create redundant stack versions.
+ bool would_merge = false;
+ for (unsigned j = 0; j < previous_version_count; j++) {
+ if (
+ ts_stack_state(self->stack, j) == entry.state &&
+ ts_stack_position(self->stack, j).bytes == position.bytes
+ ) {
+ would_merge = true;
+ break;
+ }
+ }
+ if (would_merge) continue;
+
+ // Do not recover if the result would clearly be worse than some existing stack version.
+ unsigned new_cost =
+ current_error_cost +
+ entry.depth * ERROR_COST_PER_SKIPPED_TREE +
+ (position.bytes - entry.position.bytes) * ERROR_COST_PER_SKIPPED_CHAR +
+ (position.extent.row - entry.position.extent.row) * ERROR_COST_PER_SKIPPED_LINE;
+ if (ts_parser__better_version_exists(self, version, false, new_cost)) break;
+
+ // If the current lookahead token is valid in some previous state, recover to that state.
+ // Then stop looking for further recoveries.
+ if (ts_language_has_actions(self->language, entry.state, ts_subtree_symbol(lookahead))) {
+ if (ts_parser__recover_to_state(self, version, depth, entry.state)) {
+ did_recover = true;
+ LOG("recover_to_previous state:%u, depth:%u", entry.state, depth);
+ LOG_STACK();
+ break;
+ }
+ }
+ }
+ }
+
+ // In the process of attemping to recover, some stack versions may have been created
+ // and subsequently halted. Remove those versions.
+ for (unsigned i = previous_version_count; i < ts_stack_version_count(self->stack); i++) {
+ if (!ts_stack_is_active(self->stack, i)) {
+ ts_stack_remove_version(self->stack, i--);
+ }
+ }
+
+ // If strategy 1 succeeded, a new stack version will have been created which is able to handle
+ // the current lookahead token. Now, in addition, try strategy 2 described above: skip the
+ // current lookahead token by wrapping it in an ERROR node.
+
+ // Don't pursue this additional strategy if there are already too many stack versions.
+ if (did_recover && ts_stack_version_count(self->stack) > MAX_VERSION_COUNT) {
+ ts_stack_halt(self->stack, version);
+ ts_subtree_release(&self->tree_pool, lookahead);
+ return;
+ }
+
+ // If the parser is still in the error state at the end of the file, just wrap everything
+ // in an ERROR node and terminate.
+ if (ts_subtree_is_eof(lookahead)) {
+ LOG("recover_eof");
+ SubtreeArray children = array_new();
+ Subtree parent = ts_subtree_new_error_node(&self->tree_pool, &children, false, self->language);
+ ts_stack_push(self->stack, version, parent, false, 1);
+ ts_parser__accept(self, version, lookahead);
+ return;
+ }
+
+ // Do not recover if the result would clearly be worse than some existing stack version.
+ unsigned new_cost =
+ current_error_cost + ERROR_COST_PER_SKIPPED_TREE +
+ ts_subtree_total_bytes(lookahead) * ERROR_COST_PER_SKIPPED_CHAR +
+ ts_subtree_total_size(lookahead).extent.row * ERROR_COST_PER_SKIPPED_LINE;
+ if (ts_parser__better_version_exists(self, version, false, new_cost)) {
+ ts_stack_halt(self->stack, version);
+ ts_subtree_release(&self->tree_pool, lookahead);
+ return;
+ }
+
+ // If the current lookahead token is an extra token, mark it as extra. This means it won't
+ // be counted in error cost calculations.
+ unsigned n;
+ const TSParseAction *actions = ts_language_actions(self->language, 1, ts_subtree_symbol(lookahead), &n);
+ if (n > 0 && actions[n - 1].type == TSParseActionTypeShift && actions[n - 1].params.shift.extra) {
+ MutableSubtree mutable_lookahead = ts_subtree_make_mut(&self->tree_pool, lookahead);
+ ts_subtree_set_extra(&mutable_lookahead);
+ lookahead = ts_subtree_from_mut(mutable_lookahead);
+ }
+
+ // Wrap the lookahead token in an ERROR.
+ LOG("skip_token symbol:%s", TREE_NAME(lookahead));
+ SubtreeArray children = array_new();
+ array_reserve(&children, 1);
+ array_push(&children, lookahead);
+ MutableSubtree error_repeat = ts_subtree_new_node(
+ &self->tree_pool,
+ ts_builtin_sym_error_repeat,
+ &children,
+ 0,
+ self->language
+ );
+
+ // If other tokens have already been skipped, so there is already an ERROR at the top of the
+ // stack, then pop that ERROR off the stack and wrap the two ERRORs together into one larger
+ // ERROR.
+ if (node_count_since_error > 0) {
+ StackSliceArray pop = ts_stack_pop_count(self->stack, version, 1);
+
+ // TODO: Figure out how to make this condition occur.
+ // See https://github.com/atom/atom/issues/18450#issuecomment-439579778
+ // If multiple stack versions have merged at this point, just pick one of the errors
+ // arbitrarily and discard the rest.
+ if (pop.size > 1) {
+ for (unsigned i = 1; i < pop.size; i++) {
+ ts_subtree_array_delete(&self->tree_pool, &pop.contents[i].subtrees);
+ }
+ while (ts_stack_version_count(self->stack) > pop.contents[0].version + 1) {
+ ts_stack_remove_version(self->stack, pop.contents[0].version + 1);
+ }
+ }
+
+ ts_stack_renumber_version(self->stack, pop.contents[0].version, version);
+ array_push(&pop.contents[0].subtrees, ts_subtree_from_mut(error_repeat));
+ error_repeat = ts_subtree_new_node(
+ &self->tree_pool,
+ ts_builtin_sym_error_repeat,
+ &pop.contents[0].subtrees,
+ 0,
+ self->language
+ );
+ }
+
+ // Push the new ERROR onto the stack.
+ ts_stack_push(self->stack, version, ts_subtree_from_mut(error_repeat), false, ERROR_STATE);
+ if (ts_subtree_has_external_tokens(lookahead)) {
+ ts_stack_set_last_external_token(
+ self->stack, version, ts_subtree_last_external_token(lookahead)
+ );
+ }
+}
+
+static bool ts_parser__advance(
+ TSParser *self,
+ StackVersion version,
+ bool allow_node_reuse
+) {
+ TSStateId state = ts_stack_state(self->stack, version);
+ uint32_t position = ts_stack_position(self->stack, version).bytes;
+ Subtree last_external_token = ts_stack_last_external_token(self->stack, version);
+
+ bool did_reuse = true;
+ Subtree lookahead = NULL_SUBTREE;
+ TableEntry table_entry = {.action_count = 0};
+
+ // If possible, reuse a node from the previous syntax tree.
+ if (allow_node_reuse) {
+ lookahead = ts_parser__reuse_node(
+ self, version, &state, position, last_external_token, &table_entry
+ );
+ }
+
+ // If no node from the previous syntax tree could be reused, then try to
+ // reuse the token previously returned by the lexer.
+ if (!lookahead.ptr) {
+ did_reuse = false;
+ lookahead = ts_parser__get_cached_token(
+ self, state, position, last_external_token, &table_entry
+ );
+ }
+
+ // Otherwise, re-run the lexer.
+ if (!lookahead.ptr) {
+ lookahead = ts_parser__lex(self, version, state);
+ if (lookahead.ptr) {
+ ts_parser__set_cached_token(self, position, last_external_token, lookahead);
+ ts_language_table_entry(self->language, state, ts_subtree_symbol(lookahead), &table_entry);
+ }
+
+ // When parsing a non-terminal extra, a null lookahead indicates the
+ // end of the rule. The reduction is stored in the EOF table entry.
+ // After the reduction, the lexer needs to be run again.
+ else {
+ ts_language_table_entry(self->language, state, ts_builtin_sym_end, &table_entry);
+ }
+ }
+
+ for (;;) {
+ // If a cancellation flag or a timeout was provided, then check every
+ // time a fixed number of parse actions has been processed.
+ if (++self->operation_count == OP_COUNT_PER_TIMEOUT_CHECK) {
+ self->operation_count = 0;
+ }
+ if (
+ self->operation_count == 0 &&
+ ((self->cancellation_flag && atomic_load(self->cancellation_flag)) ||
+ (!clock_is_null(self->end_clock) && clock_is_gt(clock_now(), self->end_clock)))
+ ) {
+ ts_subtree_release(&self->tree_pool, lookahead);
+ return false;
+ }
+
+ // Process each parse action for the current lookahead token in
+ // the current state. If there are multiple actions, then this is
+ // an ambiguous state. REDUCE actions always create a new stack
+ // version, whereas SHIFT actions update the existing stack version
+ // and terminate this loop.
+ StackVersion last_reduction_version = STACK_VERSION_NONE;
+ for (uint32_t i = 0; i < table_entry.action_count; i++) {
+ TSParseAction action = table_entry.actions[i];
+
+ switch (action.type) {
+ case TSParseActionTypeShift: {
+ if (action.params.shift.repetition) break;
+ TSStateId next_state;
+ if (action.params.shift.extra) {
+
+ // TODO: remove when TREE_SITTER_LANGUAGE_VERSION 9 is out.
+ if (state == ERROR_STATE) continue;
+
+ next_state = state;
+ LOG("shift_extra");
+ } else {
+ next_state = action.params.shift.state;
+ LOG("shift state:%u", next_state);
+ }
+
+ if (ts_subtree_child_count(lookahead) > 0) {
+ ts_parser__breakdown_lookahead(self, &lookahead, state, &self->reusable_node);
+ next_state = ts_language_next_state(self->language, state, ts_subtree_symbol(lookahead));
+ }
+
+ ts_parser__shift(self, version, next_state, lookahead, action.params.shift.extra);
+ if (did_reuse) reusable_node_advance(&self->reusable_node);
+ return true;
+ }
+
+ case TSParseActionTypeReduce: {
+ bool is_fragile = table_entry.action_count > 1;
+ bool is_extra = lookahead.ptr == NULL;
+ LOG("reduce sym:%s, child_count:%u", SYM_NAME(action.params.reduce.symbol), action.params.reduce.child_count);
+ StackVersion reduction_version = ts_parser__reduce(
+ self, version, action.params.reduce.symbol, action.params.reduce.child_count,
+ action.params.reduce.dynamic_precedence, action.params.reduce.production_id,
+ is_fragile, is_extra
+ );
+ if (reduction_version != STACK_VERSION_NONE) {
+ last_reduction_version = reduction_version;
+ }
+ break;
+ }
+
+ case TSParseActionTypeAccept: {
+ LOG("accept");
+ ts_parser__accept(self, version, lookahead);
+ return true;
+ }
+
+ case TSParseActionTypeRecover: {
+ if (ts_subtree_child_count(lookahead) > 0) {
+ ts_parser__breakdown_lookahead(self, &lookahead, ERROR_STATE, &self->reusable_node);
+ }
+
+ ts_parser__recover(self, version, lookahead);
+ if (did_reuse) reusable_node_advance(&self->reusable_node);
+ return true;
+ }
+ }
+ }
+
+ // If a reduction was performed, then replace the current stack version
+ // with one of the stack versions created by a reduction, and continue
+ // processing this version of the stack with the same lookahead symbol.
+ if (last_reduction_version != STACK_VERSION_NONE) {
+ ts_stack_renumber_version(self->stack, last_reduction_version, version);
+ LOG_STACK();
+ state = ts_stack_state(self->stack, version);
+
+ // At the end of a non-terminal extra rule, the lexer will return a
+ // null subtree, because the parser needs to perform a fixed reduction
+ // regardless of the lookahead node. After performing that reduction,
+ // (and completing the non-terminal extra rule) run the lexer again based
+ // on the current parse state.
+ if (!lookahead.ptr) {
+ lookahead = ts_parser__lex(self, version, state);
+ }
+ ts_language_table_entry(
+ self->language,
+ state,
+ ts_subtree_leaf_symbol(lookahead),
+ &table_entry
+ );
+ continue;
+ }
+
+ // If there were no parse actions for the current lookahead token, then
+ // it is not valid in this state. If the current lookahead token is a
+ // keyword, then switch to treating it as the normal word token if that
+ // token is valid in this state.
+ if (
+ ts_subtree_is_keyword(lookahead) &&
+ ts_subtree_symbol(lookahead) != self->language->keyword_capture_token
+ ) {
+ ts_language_table_entry(self->language, state, self->language->keyword_capture_token, &table_entry);
+ if (table_entry.action_count > 0) {
+ LOG(
+ "switch from_keyword:%s, to_word_token:%s",
+ TREE_NAME(lookahead),
+ SYM_NAME(self->language->keyword_capture_token)
+ );
+
+ MutableSubtree mutable_lookahead = ts_subtree_make_mut(&self->tree_pool, lookahead);
+ ts_subtree_set_symbol(&mutable_lookahead, self->language->keyword_capture_token, self->language);
+ lookahead = ts_subtree_from_mut(mutable_lookahead);
+ continue;
+ }
+ }
+
+ // If the current lookahead token is not valid and the parser is
+ // already in the error state, restart the error recovery process.
+ // TODO - can this be unified with the other `RECOVER` case above?
+ if (state == ERROR_STATE) {
+ ts_parser__recover(self, version, lookahead);
+ return true;
+ }
+
+ // If the current lookahead token is not valid and the previous
+ // subtree on the stack was reused from an old tree, it isn't actually
+ // valid to reuse it. Remove it from the stack, and in its place,
+ // push each of its children. Then try again to process the current
+ // lookahead.
+ if (ts_parser__breakdown_top_of_stack(self, version)) {
+ continue;
+ }
+
+ // At this point, the current lookahead token is definitely not valid
+ // for this parse stack version. Mark this version as paused and continue
+ // processing any other stack versions that might exist. If some other
+ // version advances successfully, then this version can simply be removed.
+ // But if all versions end up paused, then error recovery is needed.
+ LOG("detect_error");
+ ts_stack_pause(self->stack, version, ts_subtree_leaf_symbol(lookahead));
+ ts_subtree_release(&self->tree_pool, lookahead);
+ return true;
+ }
+}
+
+static unsigned ts_parser__condense_stack(TSParser *self) {
+ bool made_changes = false;
+ unsigned min_error_cost = UINT_MAX;
+ for (StackVersion i = 0; i < ts_stack_version_count(self->stack); i++) {
+ // Prune any versions that have been marked for removal.
+ if (ts_stack_is_halted(self->stack, i)) {
+ ts_stack_remove_version(self->stack, i);
+ i--;
+ continue;
+ }
+
+ // Keep track of the minimum error cost of any stack version so
+ // that it can be returned.
+ ErrorStatus status_i = ts_parser__version_status(self, i);
+ if (!status_i.is_in_error && status_i.cost < min_error_cost) {
+ min_error_cost = status_i.cost;
+ }
+
+ // Examine each pair of stack versions, removing any versions that
+ // are clearly worse than another version. Ensure that the versions
+ // are ordered from most promising to least promising.
+ for (StackVersion j = 0; j < i; j++) {
+ ErrorStatus status_j = ts_parser__version_status(self, j);
+
+ switch (ts_parser__compare_versions(self, status_j, status_i)) {
+ case ErrorComparisonTakeLeft:
+ made_changes = true;
+ ts_stack_remove_version(self->stack, i);
+ i--;
+ j = i;
+ break;
+
+ case ErrorComparisonPreferLeft:
+ case ErrorComparisonNone:
+ if (ts_stack_merge(self->stack, j, i)) {
+ made_changes = true;
+ i--;
+ j = i;
+ }
+ break;
+
+ case ErrorComparisonPreferRight:
+ made_changes = true;
+ if (ts_stack_merge(self->stack, j, i)) {
+ i--;
+ j = i;
+ } else {
+ ts_stack_swap_versions(self->stack, i, j);
+ }
+ break;
+
+ case ErrorComparisonTakeRight:
+ made_changes = true;
+ ts_stack_remove_version(self->stack, j);
+ i--;
+ j--;
+ break;
+ }
+ }
+ }
+
+ // Enfore a hard upper bound on the number of stack versions by
+ // discarding the least promising versions.
+ while (ts_stack_version_count(self->stack) > MAX_VERSION_COUNT) {
+ ts_stack_remove_version(self->stack, MAX_VERSION_COUNT);
+ made_changes = true;
+ }
+
+ // If the best-performing stack version is currently paused, or all
+ // versions are paused, then resume the best paused version and begin
+ // the error recovery process. Otherwise, remove the paused versions.
+ if (ts_stack_version_count(self->stack) > 0) {
+ bool has_unpaused_version = false;
+ for (StackVersion i = 0, n = ts_stack_version_count(self->stack); i < n; i++) {
+ if (ts_stack_is_paused(self->stack, i)) {
+ if (!has_unpaused_version && self->accept_count < MAX_VERSION_COUNT) {
+ LOG("resume version:%u", i);
+ min_error_cost = ts_stack_error_cost(self->stack, i);
+ TSSymbol lookahead_symbol = ts_stack_resume(self->stack, i);
+ ts_parser__handle_error(self, i, lookahead_symbol);
+ has_unpaused_version = true;
+ } else {
+ ts_stack_remove_version(self->stack, i);
+ i--;
+ n--;
+ }
+ } else {
+ has_unpaused_version = true;
+ }
+ }
+ }
+
+ if (made_changes) {
+ LOG("condense");
+ LOG_STACK();
+ }
+
+ return min_error_cost;
+}
+
+static bool ts_parser_has_outstanding_parse(TSParser *self) {
+ return (
+ ts_stack_state(self->stack, 0) != 1 ||
+ ts_stack_node_count_since_error(self->stack, 0) != 0
+ );
+}
+
+// Parser - Public
+
+TSParser *ts_parser_new(void) {
+ TSParser *self = ts_calloc(1, sizeof(TSParser));
+ ts_lexer_init(&self->lexer);
+ array_init(&self->reduce_actions);
+ array_reserve(&self->reduce_actions, 4);
+ self->tree_pool = ts_subtree_pool_new(32);
+ self->stack = ts_stack_new(&self->tree_pool);
+ self->finished_tree = NULL_SUBTREE;
+ self->reusable_node = reusable_node_new();
+ self->dot_graph_file = NULL;
+ self->cancellation_flag = NULL;
+ self->timeout_duration = 0;
+ self->end_clock = clock_null();
+ self->operation_count = 0;
+ self->old_tree = NULL_SUBTREE;
+ self->scratch_tree.ptr = &self->scratch_tree_data;
+ self->included_range_differences = (TSRangeArray) array_new();
+ self->included_range_difference_index = 0;
+ ts_parser__set_cached_token(self, 0, NULL_SUBTREE, NULL_SUBTREE);
+ return self;
+}
+
+void ts_parser_delete(TSParser *self) {
+ if (!self) return;
+
+ ts_parser_set_language(self, NULL);
+ ts_stack_delete(self->stack);
+ if (self->reduce_actions.contents) {
+ array_delete(&self->reduce_actions);
+ }
+ if (self->included_range_differences.contents) {
+ array_delete(&self->included_range_differences);
+ }
+ if (self->old_tree.ptr) {
+ ts_subtree_release(&self->tree_pool, self->old_tree);
+ self->old_tree = NULL_SUBTREE;
+ }
+ ts_lexer_delete(&self->lexer);
+ ts_parser__set_cached_token(self, 0, NULL_SUBTREE, NULL_SUBTREE);
+ ts_subtree_pool_delete(&self->tree_pool);
+ reusable_node_delete(&self->reusable_node);
+ ts_free(self);
+}
+
+const TSLanguage *ts_parser_language(const TSParser *self) {
+ return self->language;
+}
+
+bool ts_parser_set_language(TSParser *self, const TSLanguage *language) {
+ if (language) {
+ if (language->version > TREE_SITTER_LANGUAGE_VERSION) return false;
+ if (language->version < TREE_SITTER_MIN_COMPATIBLE_LANGUAGE_VERSION) return false;
+ }
+
+ if (self->external_scanner_payload && self->language->external_scanner.destroy) {
+ self->language->external_scanner.destroy(self->external_scanner_payload);
+ }
+
+ if (language && language->external_scanner.create) {
+ self->external_scanner_payload = language->external_scanner.create();
+ } else {
+ self->external_scanner_payload = NULL;
+ }
+
+ self->language = language;
+ ts_parser_reset(self);
+ return true;
+}
+
+TSLogger ts_parser_logger(const TSParser *self) {
+ return self->lexer.logger;
+}
+
+void ts_parser_set_logger(TSParser *self, TSLogger logger) {
+ self->lexer.logger = logger;
+}
+
+void ts_parser_print_dot_graphs(TSParser *self, int fd) {
+ if (self->dot_graph_file) {
+ fclose(self->dot_graph_file);
+ }
+
+ if (fd >= 0) {
+ self->dot_graph_file = fdopen(fd, "a");
+ } else {
+ self->dot_graph_file = NULL;
+ }
+}
+
+const size_t *ts_parser_cancellation_flag(const TSParser *self) {
+ return (const size_t *)self->cancellation_flag;
+}
+
+void ts_parser_set_cancellation_flag(TSParser *self, const size_t *flag) {
+ self->cancellation_flag = (const volatile size_t *)flag;
+}
+
+uint64_t ts_parser_timeout_micros(const TSParser *self) {
+ return duration_to_micros(self->timeout_duration);
+}
+
+void ts_parser_set_timeout_micros(TSParser *self, uint64_t timeout_micros) {
+ self->timeout_duration = duration_from_micros(timeout_micros);
+}
+
+bool ts_parser_set_included_ranges(
+ TSParser *self,
+ const TSRange *ranges,
+ uint32_t count
+) {
+ return ts_lexer_set_included_ranges(&self->lexer, ranges, count);
+}
+
+const TSRange *ts_parser_included_ranges(const TSParser *self, uint32_t *count) {
+ return ts_lexer_included_ranges(&self->lexer, count);
+}
+
+void ts_parser_reset(TSParser *self) {
+ if (self->language && self->language->external_scanner.deserialize) {
+ self->language->external_scanner.deserialize(self->external_scanner_payload, NULL, 0);
+ }
+
+ if (self->old_tree.ptr) {
+ ts_subtree_release(&self->tree_pool, self->old_tree);
+ self->old_tree = NULL_SUBTREE;
+ }
+
+ reusable_node_clear(&self->reusable_node);
+ ts_lexer_reset(&self->lexer, length_zero());
+ ts_stack_clear(self->stack);
+ ts_parser__set_cached_token(self, 0, NULL_SUBTREE, NULL_SUBTREE);
+ if (self->finished_tree.ptr) {
+ ts_subtree_release(&self->tree_pool, self->finished_tree);
+ self->finished_tree = NULL_SUBTREE;
+ }
+ self->accept_count = 0;
+}
+
+TSTree *ts_parser_parse(
+ TSParser *self,
+ const TSTree *old_tree,
+ TSInput input
+) {
+ if (!self->language || !input.read) return NULL;
+
+ ts_lexer_set_input(&self->lexer, input);
+
+ array_clear(&self->included_range_differences);
+ self->included_range_difference_index = 0;
+
+ if (ts_parser_has_outstanding_parse(self)) {
+ LOG("resume_parsing");
+ } else if (old_tree) {
+ ts_subtree_retain(old_tree->root);
+ self->old_tree = old_tree->root;
+ ts_range_array_get_changed_ranges(
+ old_tree->included_ranges, old_tree->included_range_count,
+ self->lexer.included_ranges, self->lexer.included_range_count,
+ &self->included_range_differences
+ );
+ reusable_node_reset(&self->reusable_node, old_tree->root);
+ LOG("parse_after_edit");
+ LOG_TREE(self->old_tree);
+ for (unsigned i = 0; i < self->included_range_differences.size; i++) {
+ TSRange *range = &self->included_range_differences.contents[i];
+ LOG("different_included_range %u - %u", range->start_byte, range->end_byte);
+ }
+ } else {
+ reusable_node_clear(&self->reusable_node);
+ LOG("new_parse");
+ }
+
+ uint32_t position = 0, last_position = 0, version_count = 0;
+ self->operation_count = 0;
+ if (self->timeout_duration) {
+ self->end_clock = clock_after(clock_now(), self->timeout_duration);
+ } else {
+ self->end_clock = clock_null();
+ }
+
+ do {
+ for (StackVersion version = 0;
+ version_count = ts_stack_version_count(self->stack), version < version_count;
+ version++) {
+ bool allow_node_reuse = version_count == 1;
+ while (ts_stack_is_active(self->stack, version)) {
+ LOG("process version:%d, version_count:%u, state:%d, row:%u, col:%u",
+ version, ts_stack_version_count(self->stack),
+ ts_stack_state(self->stack, version),
+ ts_stack_position(self->stack, version).extent.row + 1,
+ ts_stack_position(self->stack, version).extent.column);
+
+ if (!ts_parser__advance(self, version, allow_node_reuse)) return NULL;
+ LOG_STACK();
+
+ position = ts_stack_position(self->stack, version).bytes;
+ if (position > last_position || (version > 0 && position == last_position)) {
+ last_position = position;
+ break;
+ }
+ }
+ }
+
+ unsigned min_error_cost = ts_parser__condense_stack(self);
+ if (self->finished_tree.ptr && ts_subtree_error_cost(self->finished_tree) < min_error_cost) {
+ break;
+ }
+
+ while (self->included_range_difference_index < self->included_range_differences.size) {
+ TSRange *range = &self->included_range_differences.contents[self->included_range_difference_index];
+ if (range->end_byte <= position) {
+ self->included_range_difference_index++;
+ } else {
+ break;
+ }
+ }
+ } while (version_count != 0);
+
+ ts_subtree_balance(self->finished_tree, &self->tree_pool, self->language);
+ LOG("done");
+ LOG_TREE(self->finished_tree);
+
+ TSTree *result = ts_tree_new(
+ self->finished_tree,
+ self->language,
+ self->lexer.included_ranges,
+ self->lexer.included_range_count
+ );
+ self->finished_tree = NULL_SUBTREE;
+ ts_parser_reset(self);
+ return result;
+}
+
+TSTree *ts_parser_parse_string(
+ TSParser *self,
+ const TSTree *old_tree,
+ const char *string,
+ uint32_t length
+) {
+ return ts_parser_parse_string_encoding(self, old_tree, string, length, TSInputEncodingUTF8);
+}
+
+TSTree *ts_parser_parse_string_encoding(TSParser *self, const TSTree *old_tree,
+ const char *string, uint32_t length, TSInputEncoding encoding) {
+ TSStringInput input = {string, length};
+ return ts_parser_parse(self, old_tree, (TSInput) {
+ &input,
+ ts_string_input_read,
+ encoding,
+ });
+}
+
+#undef LOG
diff --git a/src/tree_sitter/parser.h b/src/tree_sitter/parser.h
new file mode 100644
index 0000000000..11bf4fc42a
--- /dev/null
+++ b/src/tree_sitter/parser.h
@@ -0,0 +1,235 @@
+#ifndef TREE_SITTER_PARSER_H_
+#define TREE_SITTER_PARSER_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#define ts_builtin_sym_error ((TSSymbol)-1)
+#define ts_builtin_sym_end 0
+#define TREE_SITTER_SERIALIZATION_BUFFER_SIZE 1024
+
+#ifndef TREE_SITTER_API_H_
+typedef uint16_t TSSymbol;
+typedef uint16_t TSFieldId;
+typedef struct TSLanguage TSLanguage;
+#endif
+
+typedef struct {
+ TSFieldId field_id;
+ uint8_t child_index;
+ bool inherited;
+} TSFieldMapEntry;
+
+typedef struct {
+ uint16_t index;
+ uint16_t length;
+} TSFieldMapSlice;
+
+typedef uint16_t TSStateId;
+
+typedef struct {
+ bool visible : 1;
+ bool named : 1;
+} TSSymbolMetadata;
+
+typedef struct TSLexer TSLexer;
+
+struct TSLexer {
+ int32_t lookahead;
+ TSSymbol result_symbol;
+ void (*advance)(TSLexer *, bool);
+ void (*mark_end)(TSLexer *);
+ uint32_t (*get_column)(TSLexer *);
+ bool (*is_at_included_range_start)(const TSLexer *);
+ bool (*eof)(const TSLexer *);
+};
+
+typedef enum {
+ TSParseActionTypeShift,
+ TSParseActionTypeReduce,
+ TSParseActionTypeAccept,
+ TSParseActionTypeRecover,
+} TSParseActionType;
+
+typedef struct {
+ union {
+ struct {
+ TSStateId state;
+ bool extra : 1;
+ bool repetition : 1;
+ } shift;
+ struct {
+ TSSymbol symbol;
+ int16_t dynamic_precedence;
+ uint8_t child_count;
+ uint8_t production_id;
+ } reduce;
+ } params;
+ TSParseActionType type : 4;
+} TSParseAction;
+
+typedef struct {
+ uint16_t lex_state;
+ uint16_t external_lex_state;
+} TSLexMode;
+
+typedef union {
+ TSParseAction action;
+ struct {
+ uint8_t count;
+ bool reusable : 1;
+ } entry;
+} TSParseActionEntry;
+
+struct TSLanguage {
+ uint32_t version;
+ uint32_t symbol_count;
+ uint32_t alias_count;
+ uint32_t token_count;
+ uint32_t external_token_count;
+ const char **symbol_names;
+ const TSSymbolMetadata *symbol_metadata;
+ const uint16_t *parse_table;
+ const TSParseActionEntry *parse_actions;
+ const TSLexMode *lex_modes;
+ const TSSymbol *alias_sequences;
+ uint16_t max_alias_sequence_length;
+ bool (*lex_fn)(TSLexer *, TSStateId);
+ bool (*keyword_lex_fn)(TSLexer *, TSStateId);
+ TSSymbol keyword_capture_token;
+ struct {
+ const bool *states;
+ const TSSymbol *symbol_map;
+ void *(*create)(void);
+ void (*destroy)(void *);
+ bool (*scan)(void *, TSLexer *, const bool *symbol_whitelist);
+ unsigned (*serialize)(void *, char *);
+ void (*deserialize)(void *, const char *, unsigned);
+ } external_scanner;
+ uint32_t field_count;
+ const TSFieldMapSlice *field_map_slices;
+ const TSFieldMapEntry *field_map_entries;
+ const char **field_names;
+ uint32_t large_state_count;
+ const uint16_t *small_parse_table;
+ const uint32_t *small_parse_table_map;
+ const TSSymbol *public_symbol_map;
+};
+
+/*
+ * Lexer Macros
+ */
+
+#define START_LEXER() \
+ bool result = false; \
+ bool skip = false; \
+ bool eof = false; \
+ int32_t lookahead; \
+ goto start; \
+ next_state: \
+ lexer->advance(lexer, skip); \
+ start: \
+ skip = false; \
+ lookahead = lexer->lookahead;
+
+#define ADVANCE(state_value) \
+ { \
+ state = state_value; \
+ goto next_state; \
+ }
+
+#define SKIP(state_value) \
+ { \
+ skip = true; \
+ state = state_value; \
+ goto next_state; \
+ }
+
+#define ACCEPT_TOKEN(symbol_value) \
+ result = true; \
+ lexer->result_symbol = symbol_value; \
+ lexer->mark_end(lexer);
+
+#define END_STATE() return result;
+
+/*
+ * Parse Table Macros
+ */
+
+#define SMALL_STATE(id) id - LARGE_STATE_COUNT
+
+#define STATE(id) id
+
+#define ACTIONS(id) id
+
+#define SHIFT(state_value) \
+ { \
+ { \
+ .params = { \
+ .shift = { \
+ .state = state_value \
+ } \
+ }, \
+ .type = TSParseActionTypeShift \
+ } \
+ }
+
+#define SHIFT_REPEAT(state_value) \
+ { \
+ { \
+ .params = { \
+ .shift = { \
+ .state = state_value, \
+ .repetition = true \
+ } \
+ }, \
+ .type = TSParseActionTypeShift \
+ } \
+ }
+
+#define RECOVER() \
+ { \
+ { .type = TSParseActionTypeRecover } \
+ }
+
+#define SHIFT_EXTRA() \
+ { \
+ { \
+ .params = { \
+ .shift = { \
+ .extra = true \
+ } \
+ }, \
+ .type = TSParseActionTypeShift \
+ } \
+ }
+
+#define REDUCE(symbol_val, child_count_val, ...) \
+ { \
+ { \
+ .params = { \
+ .reduce = { \
+ .symbol = symbol_val, \
+ .child_count = child_count_val, \
+ __VA_ARGS__ \
+ }, \
+ }, \
+ .type = TSParseActionTypeReduce \
+ } \
+ }
+
+#define ACCEPT_INPUT() \
+ { \
+ { .type = TSParseActionTypeAccept } \
+ }
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // TREE_SITTER_PARSER_H_
diff --git a/src/tree_sitter/point.h b/src/tree_sitter/point.h
new file mode 100644
index 0000000000..a50d20214b
--- /dev/null
+++ b/src/tree_sitter/point.h
@@ -0,0 +1,54 @@
+#ifndef TREE_SITTER_POINT_H_
+#define TREE_SITTER_POINT_H_
+
+#include "tree_sitter/api.h"
+
+#define POINT_ZERO ((TSPoint) {0, 0})
+#define POINT_MAX ((TSPoint) {UINT32_MAX, UINT32_MAX})
+
+static inline TSPoint point__new(unsigned row, unsigned column) {
+ TSPoint result = {row, column};
+ return result;
+}
+
+static inline TSPoint point_add(TSPoint a, TSPoint b) {
+ if (b.row > 0)
+ return point__new(a.row + b.row, b.column);
+ else
+ return point__new(a.row, a.column + b.column);
+}
+
+static inline TSPoint point_sub(TSPoint a, TSPoint b) {
+ if (a.row > b.row)
+ return point__new(a.row - b.row, a.column);
+ else
+ return point__new(0, a.column - b.column);
+}
+
+static inline bool point_lte(TSPoint a, TSPoint b) {
+ return (a.row < b.row) || (a.row == b.row && a.column <= b.column);
+}
+
+static inline bool point_lt(TSPoint a, TSPoint b) {
+ return (a.row < b.row) || (a.row == b.row && a.column < b.column);
+}
+
+static inline bool point_eq(TSPoint a, TSPoint b) {
+ return a.row == b.row && a.column == b.column;
+}
+
+static inline TSPoint point_min(TSPoint a, TSPoint b) {
+ if (a.row < b.row || (a.row == b.row && a.column < b.column))
+ return a;
+ else
+ return b;
+}
+
+static inline TSPoint point_max(TSPoint a, TSPoint b) {
+ if (a.row > b.row || (a.row == b.row && a.column > b.column))
+ return a;
+ else
+ return b;
+}
+
+#endif
diff --git a/src/tree_sitter/query.c b/src/tree_sitter/query.c
new file mode 100644
index 0000000000..59902dee3b
--- /dev/null
+++ b/src/tree_sitter/query.c
@@ -0,0 +1,2035 @@
+#include "tree_sitter/api.h"
+#include "./alloc.h"
+#include "./array.h"
+#include "./bits.h"
+#include "./language.h"
+#include "./point.h"
+#include "./tree_cursor.h"
+#include "./unicode.h"
+#include <wctype.h>
+
+// #define LOG(...) fprintf(stderr, __VA_ARGS__)
+#define LOG(...)
+
+#define MAX_STATE_COUNT 256
+#define MAX_CAPTURE_LIST_COUNT 32
+#define MAX_STEP_CAPTURE_COUNT 3
+
+/*
+ * Stream - A sequence of unicode characters derived from a UTF8 string.
+ * This struct is used in parsing queries from S-expressions.
+ */
+typedef struct {
+ const char *input;
+ const char *end;
+ int32_t next;
+ uint8_t next_size;
+} Stream;
+
+/*
+ * QueryStep - A step in the process of matching a query. Each node within
+ * a query S-expression maps to one of these steps. An entire pattern is
+ * represented as a sequence of these steps. Fields:
+ *
+ * - `symbol` - The grammar symbol to match. A zero value represents the
+ * wildcard symbol, '_'.
+ * - `field` - The field name to match. A zero value means that a field name
+ * was not specified.
+ * - `capture_ids` - An array of integers representing the names of captures
+ * associated with this node in the pattern, terminated by a `NONE` value.
+ * - `depth` - The depth where this node occurs in the pattern. The root node
+ * of the pattern has depth zero.
+ * - `alternative_index` - The index of a different query step that serves as
+ * an alternative to this step.
+ */
+typedef struct {
+ TSSymbol symbol;
+ TSFieldId field;
+ uint16_t capture_ids[MAX_STEP_CAPTURE_COUNT];
+ uint16_t alternative_index;
+ uint16_t depth;
+ bool contains_captures: 1;
+ bool is_pattern_start: 1;
+ bool is_immediate: 1;
+ bool is_last_child: 1;
+ bool is_pass_through: 1;
+ bool is_dead_end: 1;
+ bool alternative_is_immediate: 1;
+} QueryStep;
+
+/*
+ * Slice - A slice of an external array. Within a query, capture names,
+ * literal string values, and predicate step informations are stored in three
+ * contiguous arrays. Individual captures, string values, and predicates are
+ * represented as slices of these three arrays.
+ */
+typedef struct {
+ uint32_t offset;
+ uint32_t length;
+} Slice;
+
+/*
+ * SymbolTable - a two-way mapping of strings to ids.
+ */
+typedef struct {
+ Array(char) characters;
+ Array(Slice) slices;
+} SymbolTable;
+
+/*
+ * PatternEntry - Information about the starting point for matching a
+ * particular pattern, consisting of the index of the pattern within the query,
+ * and the index of the patter's first step in the shared `steps` array. These
+ * entries are stored in a 'pattern map' - a sorted array that makes it
+ * possible to efficiently lookup patterns based on the symbol for their first
+ * step.
+ */
+typedef struct {
+ uint16_t step_index;
+ uint16_t pattern_index;
+} PatternEntry;
+
+/*
+ * QueryState - The state of an in-progress match of a particular pattern
+ * in a query. While executing, a `TSQueryCursor` must keep track of a number
+ * of possible in-progress matches. Each of those possible matches is
+ * represented as one of these states. Fields:
+ * - `id` - A numeric id that is exposed to the public API. This allows the
+ * caller to remove a given match, preventing any more of its captures
+ * from being returned.
+ * - `start_depth` - The depth in the tree where the first step of the state's
+ * pattern was matched.
+ * - `pattern_index` - The pattern that the state is matching.
+ * - `consumed_capture_count` - The number of captures from this match that
+ * have already been returned.
+ * - `capture_list_id` - A numeric id that can be used to retrieve the state's
+ * list of captures from the `CaptureListPool`.
+ * - `seeking_immediate_match` - A flag that indicates that the state's next
+ * step must be matched by the very next sibling. This is used when
+ * processing repetitions.
+ * - `has_in_progress_alternatives` - A flag that indicates that there is are
+ * other states that have the same captures as this state, but are at
+ * different steps in their pattern. This means that in order to obey the
+ * 'longest-match' rule, this state should not be returned as a match until
+ * it is clear that there can be no longer match.
+ */
+typedef struct {
+ uint32_t id;
+ uint16_t start_depth;
+ uint16_t step_index;
+ uint16_t pattern_index;
+ uint16_t capture_list_id;
+ uint16_t consumed_capture_count: 14;
+ bool seeking_immediate_match: 1;
+ bool has_in_progress_alternatives: 1;
+} QueryState;
+
+typedef Array(TSQueryCapture) CaptureList;
+
+/*
+ * CaptureListPool - A collection of *lists* of captures. Each QueryState
+ * needs to maintain its own list of captures. To avoid repeated allocations,
+ * the reuses a fixed set of capture lists, and keeps track of which ones
+ * are currently in use.
+ */
+typedef struct {
+ CaptureList list[MAX_CAPTURE_LIST_COUNT];
+ CaptureList empty_list;
+ uint32_t usage_map;
+} CaptureListPool;
+
+/*
+ * TSQuery - A tree query, compiled from a string of S-expressions. The query
+ * itself is immutable. The mutable state used in the process of executing the
+ * query is stored in a `TSQueryCursor`.
+ */
+struct TSQuery {
+ SymbolTable captures;
+ SymbolTable predicate_values;
+ Array(QueryStep) steps;
+ Array(PatternEntry) pattern_map;
+ Array(TSQueryPredicateStep) predicate_steps;
+ Array(Slice) predicates_by_pattern;
+ Array(uint32_t) start_bytes_by_pattern;
+ const TSLanguage *language;
+ uint16_t wildcard_root_pattern_count;
+ TSSymbol *symbol_map;
+};
+
+/*
+ * TSQueryCursor - A stateful struct used to execute a query on a tree.
+ */
+struct TSQueryCursor {
+ const TSQuery *query;
+ TSTreeCursor cursor;
+ Array(QueryState) states;
+ Array(QueryState) finished_states;
+ CaptureListPool capture_list_pool;
+ uint32_t depth;
+ uint32_t start_byte;
+ uint32_t end_byte;
+ uint32_t next_state_id;
+ TSPoint start_point;
+ TSPoint end_point;
+ bool ascending;
+};
+
+static const TSQueryError PARENT_DONE = -1;
+static const uint16_t PATTERN_DONE_MARKER = UINT16_MAX;
+static const uint16_t NONE = UINT16_MAX;
+static const TSSymbol WILDCARD_SYMBOL = 0;
+static const TSSymbol NAMED_WILDCARD_SYMBOL = UINT16_MAX - 1;
+
+/**********
+ * Stream
+ **********/
+
+// Advance to the next unicode code point in the stream.
+static bool stream_advance(Stream *self) {
+ self->input += self->next_size;
+ if (self->input < self->end) {
+ uint32_t size = ts_decode_utf8(
+ (const uint8_t *)self->input,
+ self->end - self->input,
+ &self->next
+ );
+ if (size > 0) {
+ self->next_size = size;
+ return true;
+ }
+ } else {
+ self->next_size = 0;
+ self->next = '\0';
+ }
+ return false;
+}
+
+// Reset the stream to the given input position, represented as a pointer
+// into the input string.
+static void stream_reset(Stream *self, const char *input) {
+ self->input = input;
+ self->next_size = 0;
+ stream_advance(self);
+}
+
+static Stream stream_new(const char *string, uint32_t length) {
+ Stream self = {
+ .next = 0,
+ .input = string,
+ .end = string + length,
+ };
+ stream_advance(&self);
+ return self;
+}
+
+static void stream_skip_whitespace(Stream *stream) {
+ for (;;) {
+ if (iswspace(stream->next)) {
+ stream_advance(stream);
+ } else if (stream->next == ';') {
+ // skip over comments
+ stream_advance(stream);
+ while (stream->next && stream->next != '\n') {
+ if (!stream_advance(stream)) break;
+ }
+ } else {
+ break;
+ }
+ }
+}
+
+static bool stream_is_ident_start(Stream *stream) {
+ return iswalnum(stream->next) || stream->next == '_' || stream->next == '-';
+}
+
+static void stream_scan_identifier(Stream *stream) {
+ do {
+ stream_advance(stream);
+ } while (
+ iswalnum(stream->next) ||
+ stream->next == '_' ||
+ stream->next == '-' ||
+ stream->next == '.' ||
+ stream->next == '?' ||
+ stream->next == '!'
+ );
+}
+
+/******************
+ * CaptureListPool
+ ******************/
+
+static CaptureListPool capture_list_pool_new(void) {
+ return (CaptureListPool) {
+ .empty_list = array_new(),
+ .usage_map = UINT32_MAX,
+ };
+}
+
+static void capture_list_pool_reset(CaptureListPool *self) {
+ self->usage_map = UINT32_MAX;
+ for (unsigned i = 0; i < MAX_CAPTURE_LIST_COUNT; i++) {
+ array_clear(&self->list[i]);
+ }
+}
+
+static void capture_list_pool_delete(CaptureListPool *self) {
+ for (unsigned i = 0; i < MAX_CAPTURE_LIST_COUNT; i++) {
+ array_delete(&self->list[i]);
+ }
+}
+
+static const CaptureList *capture_list_pool_get(const CaptureListPool *self, uint16_t id) {
+ if (id >= MAX_CAPTURE_LIST_COUNT) return &self->empty_list;
+ return &self->list[id];
+}
+
+static CaptureList *capture_list_pool_get_mut(CaptureListPool *self, uint16_t id) {
+ assert(id < MAX_CAPTURE_LIST_COUNT);
+ return &self->list[id];
+}
+
+static bool capture_list_pool_is_empty(const CaptureListPool *self) {
+ return self->usage_map == 0;
+}
+
+static uint16_t capture_list_pool_acquire(CaptureListPool *self) {
+ // In the usage_map bitmask, ones represent free lists, and zeros represent
+ // lists that are in use. A free list id can quickly be found by counting
+ // the leading zeros in the usage map. An id of zero corresponds to the
+ // highest-order bit in the bitmask.
+ uint16_t id = count_leading_zeros(self->usage_map);
+ if (id >= MAX_CAPTURE_LIST_COUNT) return NONE;
+ self->usage_map &= ~bitmask_for_index(id);
+ array_clear(&self->list[id]);
+ return id;
+}
+
+static void capture_list_pool_release(CaptureListPool *self, uint16_t id) {
+ if (id >= MAX_CAPTURE_LIST_COUNT) return;
+ array_clear(&self->list[id]);
+ self->usage_map |= bitmask_for_index(id);
+}
+
+/**************
+ * SymbolTable
+ **************/
+
+static SymbolTable symbol_table_new(void) {
+ return (SymbolTable) {
+ .characters = array_new(),
+ .slices = array_new(),
+ };
+}
+
+static void symbol_table_delete(SymbolTable *self) {
+ array_delete(&self->characters);
+ array_delete(&self->slices);
+}
+
+static int symbol_table_id_for_name(
+ const SymbolTable *self,
+ const char *name,
+ uint32_t length
+) {
+ for (unsigned i = 0; i < self->slices.size; i++) {
+ Slice slice = self->slices.contents[i];
+ if (
+ slice.length == length &&
+ !strncmp(&self->characters.contents[slice.offset], name, length)
+ ) return i;
+ }
+ return -1;
+}
+
+static const char *symbol_table_name_for_id(
+ const SymbolTable *self,
+ uint16_t id,
+ uint32_t *length
+) {
+ Slice slice = self->slices.contents[id];
+ *length = slice.length;
+ return &self->characters.contents[slice.offset];
+}
+
+static uint16_t symbol_table_insert_name(
+ SymbolTable *self,
+ const char *name,
+ uint32_t length
+) {
+ int id = symbol_table_id_for_name(self, name, length);
+ if (id >= 0) return (uint16_t)id;
+ Slice slice = {
+ .offset = self->characters.size,
+ .length = length,
+ };
+ array_grow_by(&self->characters, length + 1);
+ memcpy(&self->characters.contents[slice.offset], name, length);
+ self->characters.contents[self->characters.size - 1] = 0;
+ array_push(&self->slices, slice);
+ return self->slices.size - 1;
+}
+
+static uint16_t symbol_table_insert_name_with_escapes(
+ SymbolTable *self,
+ const char *escaped_name,
+ uint32_t escaped_length
+) {
+ Slice slice = {
+ .offset = self->characters.size,
+ .length = 0,
+ };
+ array_grow_by(&self->characters, escaped_length + 1);
+
+ // Copy the contents of the literal into the characters buffer, processing escape
+ // sequences like \n and \". This needs to be done before checking if the literal
+ // is already present, in order to do the string comparison.
+ bool is_escaped = false;
+ for (unsigned i = 0; i < escaped_length; i++) {
+ const char *src = &escaped_name[i];
+ char *dest = &self->characters.contents[slice.offset + slice.length];
+ if (is_escaped) {
+ switch (*src) {
+ case 'n':
+ *dest = '\n';
+ break;
+ case 'r':
+ *dest = '\r';
+ break;
+ case 't':
+ *dest = '\t';
+ break;
+ case '0':
+ *dest = '\0';
+ break;
+ default:
+ *dest = *src;
+ break;
+ }
+ is_escaped = false;
+ slice.length++;
+ } else {
+ if (*src == '\\') {
+ is_escaped = true;
+ } else {
+ *dest = *src;
+ slice.length++;
+ }
+ }
+ }
+
+ // If the string is already present, remove the redundant content from the characters
+ // buffer and return the existing id.
+ int id = symbol_table_id_for_name(self, &self->characters.contents[slice.offset], slice.length);
+ if (id >= 0) {
+ self->characters.size -= (escaped_length + 1);
+ return id;
+ }
+
+ self->characters.contents[slice.offset + slice.length] = 0;
+ array_push(&self->slices, slice);
+ return self->slices.size - 1;
+}
+
+/************
+ * QueryStep
+ ************/
+
+static QueryStep query_step__new(
+ TSSymbol symbol,
+ uint16_t depth,
+ bool is_immediate
+) {
+ return (QueryStep) {
+ .symbol = symbol,
+ .depth = depth,
+ .field = 0,
+ .capture_ids = {NONE, NONE, NONE},
+ .alternative_index = NONE,
+ .contains_captures = false,
+ .is_last_child = false,
+ .is_pattern_start = false,
+ .is_pass_through = false,
+ .is_dead_end = false,
+ .is_immediate = is_immediate,
+ .alternative_is_immediate = false,
+ };
+}
+
+static void query_step__add_capture(QueryStep *self, uint16_t capture_id) {
+ for (unsigned i = 0; i < MAX_STEP_CAPTURE_COUNT; i++) {
+ if (self->capture_ids[i] == NONE) {
+ self->capture_ids[i] = capture_id;
+ break;
+ }
+ }
+}
+
+static void query_step__remove_capture(QueryStep *self, uint16_t capture_id) {
+ for (unsigned i = 0; i < MAX_STEP_CAPTURE_COUNT; i++) {
+ if (self->capture_ids[i] == capture_id) {
+ self->capture_ids[i] = NONE;
+ while (i + 1 < MAX_STEP_CAPTURE_COUNT) {
+ if (self->capture_ids[i + 1] == NONE) break;
+ self->capture_ids[i] = self->capture_ids[i + 1];
+ self->capture_ids[i + 1] = NONE;
+ i++;
+ }
+ break;
+ }
+ }
+}
+
+/*********
+ * Query
+ *********/
+
+// The `pattern_map` contains a mapping from TSSymbol values to indices in the
+// `steps` array. For a given syntax node, the `pattern_map` makes it possible
+// to quickly find the starting steps of all of the patterns whose root matches
+// that node. Each entry has two fields: a `pattern_index`, which identifies one
+// of the patterns in the query, and a `step_index`, which indicates the start
+// offset of that pattern's steps within the `steps` array.
+//
+// The entries are sorted by the patterns' root symbols, and lookups use a
+// binary search. This ensures that the cost of this initial lookup step
+// scales logarithmically with the number of patterns in the query.
+//
+// This returns `true` if the symbol is present and `false` otherwise.
+// If the symbol is not present `*result` is set to the index where the
+// symbol should be inserted.
+static inline bool ts_query__pattern_map_search(
+ const TSQuery *self,
+ TSSymbol needle,
+ uint32_t *result
+) {
+ uint32_t base_index = self->wildcard_root_pattern_count;
+ uint32_t size = self->pattern_map.size - base_index;
+ if (size == 0) {
+ *result = base_index;
+ return false;
+ }
+ while (size > 1) {
+ uint32_t half_size = size / 2;
+ uint32_t mid_index = base_index + half_size;
+ TSSymbol mid_symbol = self->steps.contents[
+ self->pattern_map.contents[mid_index].step_index
+ ].symbol;
+ if (needle > mid_symbol) base_index = mid_index;
+ size -= half_size;
+ }
+
+ TSSymbol symbol = self->steps.contents[
+ self->pattern_map.contents[base_index].step_index
+ ].symbol;
+
+ if (needle > symbol) {
+ base_index++;
+ if (base_index < self->pattern_map.size) {
+ symbol = self->steps.contents[
+ self->pattern_map.contents[base_index].step_index
+ ].symbol;
+ }
+ }
+
+ *result = base_index;
+ return needle == symbol;
+}
+
+// Insert a new pattern's start index into the pattern map, maintaining
+// the pattern map's ordering invariant.
+static inline void ts_query__pattern_map_insert(
+ TSQuery *self,
+ TSSymbol symbol,
+ uint32_t start_step_index,
+ uint32_t pattern_index
+) {
+ uint32_t index;
+ ts_query__pattern_map_search(self, symbol, &index);
+ array_insert(&self->pattern_map, index, ((PatternEntry) {
+ .step_index = start_step_index,
+ .pattern_index = pattern_index,
+ }));
+}
+
+static void ts_query__finalize_steps(TSQuery *self) {
+ for (unsigned i = 0; i < self->steps.size; i++) {
+ QueryStep *step = &self->steps.contents[i];
+ uint32_t depth = step->depth;
+ if (step->capture_ids[0] != NONE) {
+ step->contains_captures = true;
+ } else {
+ step->contains_captures = false;
+ for (unsigned j = i + 1; j < self->steps.size; j++) {
+ QueryStep *s = &self->steps.contents[j];
+ if (s->depth == PATTERN_DONE_MARKER || s->depth <= depth) break;
+ if (s->capture_ids[0] != NONE) step->contains_captures = true;
+ }
+ }
+ }
+}
+
+// Parse a single predicate associated with a pattern, adding it to the
+// query's internal `predicate_steps` array. Predicates are arbitrary
+// S-expressions associated with a pattern which are meant to be handled at
+// a higher level of abstraction, such as the Rust/JavaScript bindings. They
+// can contain '@'-prefixed capture names, double-quoted strings, and bare
+// symbols, which also represent strings.
+static TSQueryError ts_query__parse_predicate(
+ TSQuery *self,
+ Stream *stream
+) {
+ if (!stream_is_ident_start(stream)) return TSQueryErrorSyntax;
+ const char *predicate_name = stream->input;
+ stream_scan_identifier(stream);
+ uint32_t length = stream->input - predicate_name;
+ uint16_t id = symbol_table_insert_name(
+ &self->predicate_values,
+ predicate_name,
+ length
+ );
+ array_back(&self->predicates_by_pattern)->length++;
+ array_push(&self->predicate_steps, ((TSQueryPredicateStep) {
+ .type = TSQueryPredicateStepTypeString,
+ .value_id = id,
+ }));
+ stream_skip_whitespace(stream);
+
+ for (;;) {
+ if (stream->next == ')') {
+ stream_advance(stream);
+ stream_skip_whitespace(stream);
+ array_back(&self->predicates_by_pattern)->length++;
+ array_push(&self->predicate_steps, ((TSQueryPredicateStep) {
+ .type = TSQueryPredicateStepTypeDone,
+ .value_id = 0,
+ }));
+ break;
+ }
+
+ // Parse an '@'-prefixed capture name
+ else if (stream->next == '@') {
+ stream_advance(stream);
+
+ // Parse the capture name
+ if (!stream_is_ident_start(stream)) return TSQueryErrorSyntax;
+ const char *capture_name = stream->input;
+ stream_scan_identifier(stream);
+ uint32_t length = stream->input - capture_name;
+
+ // Add the capture id to the first step of the pattern
+ int capture_id = symbol_table_id_for_name(
+ &self->captures,
+ capture_name,
+ length
+ );
+ if (capture_id == -1) {
+ stream_reset(stream, capture_name);
+ return TSQueryErrorCapture;
+ }
+
+ array_back(&self->predicates_by_pattern)->length++;
+ array_push(&self->predicate_steps, ((TSQueryPredicateStep) {
+ .type = TSQueryPredicateStepTypeCapture,
+ .value_id = capture_id,
+ }));
+ }
+
+ // Parse a string literal
+ else if (stream->next == '"') {
+ stream_advance(stream);
+
+ // Parse the string content
+ bool is_escaped = false;
+ const char *string_content = stream->input;
+ for (;;) {
+ if (is_escaped) {
+ is_escaped = false;
+ } else {
+ if (stream->next == '\\') {
+ is_escaped = true;
+ } else if (stream->next == '"') {
+ break;
+ } else if (stream->next == '\n') {
+ stream_reset(stream, string_content - 1);
+ return TSQueryErrorSyntax;
+ }
+ }
+ if (!stream_advance(stream)) {
+ stream_reset(stream, string_content - 1);
+ return TSQueryErrorSyntax;
+ }
+ }
+ uint32_t length = stream->input - string_content;
+
+ // Add a step for the node
+ uint16_t id = symbol_table_insert_name_with_escapes(
+ &self->predicate_values,
+ string_content,
+ length
+ );
+ array_back(&self->predicates_by_pattern)->length++;
+ array_push(&self->predicate_steps, ((TSQueryPredicateStep) {
+ .type = TSQueryPredicateStepTypeString,
+ .value_id = id,
+ }));
+
+ if (stream->next != '"') return TSQueryErrorSyntax;
+ stream_advance(stream);
+ }
+
+ // Parse a bare symbol
+ else if (stream_is_ident_start(stream)) {
+ const char *symbol_start = stream->input;
+ stream_scan_identifier(stream);
+ uint32_t length = stream->input - symbol_start;
+ uint16_t id = symbol_table_insert_name(
+ &self->predicate_values,
+ symbol_start,
+ length
+ );
+ array_back(&self->predicates_by_pattern)->length++;
+ array_push(&self->predicate_steps, ((TSQueryPredicateStep) {
+ .type = TSQueryPredicateStepTypeString,
+ .value_id = id,
+ }));
+ }
+
+ else {
+ return TSQueryErrorSyntax;
+ }
+
+ stream_skip_whitespace(stream);
+ }
+
+ return 0;
+}
+
+// Read one S-expression pattern from the stream, and incorporate it into
+// the query's internal state machine representation. For nested patterns,
+// this function calls itself recursively.
+static TSQueryError ts_query__parse_pattern(
+ TSQuery *self,
+ Stream *stream,
+ uint32_t depth,
+ uint32_t *capture_count,
+ bool is_immediate
+) {
+ uint32_t starting_step_index = self->steps.size;
+
+ if (stream->next == 0) return TSQueryErrorSyntax;
+
+ // Finish the parent S-expression.
+ if (stream->next == ')' || stream->next == ']') {
+ return PARENT_DONE;
+ }
+
+ // An open bracket is the start of an alternation.
+ else if (stream->next == '[') {
+ stream_advance(stream);
+ stream_skip_whitespace(stream);
+
+ // Parse each branch, and add a placeholder step in between the branches.
+ Array(uint32_t) branch_step_indices = array_new();
+ for (;;) {
+ uint32_t start_index = self->steps.size;
+ TSQueryError e = ts_query__parse_pattern(
+ self,
+ stream,
+ depth,
+ capture_count,
+ is_immediate
+ );
+
+ if (e == PARENT_DONE && stream->next == ']' && branch_step_indices.size > 0) {
+ stream_advance(stream);
+ break;
+ } else if (e) {
+ array_delete(&branch_step_indices);
+ return e;
+ }
+
+ array_push(&branch_step_indices, start_index);
+ array_push(&self->steps, query_step__new(0, depth, false));
+ }
+ (void)array_pop(&self->steps);
+
+ // For all of the branches except for the last one, add the subsequent branch as an
+ // alternative, and link the end of the branch to the current end of the steps.
+ for (unsigned i = 0; i < branch_step_indices.size - 1; i++) {
+ uint32_t step_index = branch_step_indices.contents[i];
+ uint32_t next_step_index = branch_step_indices.contents[i + 1];
+ QueryStep *start_step = &self->steps.contents[step_index];
+ QueryStep *end_step = &self->steps.contents[next_step_index - 1];
+ start_step->alternative_index = next_step_index;
+ end_step->alternative_index = self->steps.size;
+ end_step->is_dead_end = true;
+ }
+
+ array_delete(&branch_step_indices);
+ }
+
+ // An open parenthesis can be the start of three possible constructs:
+ // * A grouped sequence
+ // * A predicate
+ // * A named node
+ else if (stream->next == '(') {
+ stream_advance(stream);
+ stream_skip_whitespace(stream);
+
+ // If this parenthesis is followed by a node, then it represents a grouped sequence.
+ if (stream->next == '(' || stream->next == '"' || stream->next == '[') {
+ bool child_is_immediate = false;
+ for (;;) {
+ if (stream->next == '.') {
+ child_is_immediate = true;
+ stream_advance(stream);
+ stream_skip_whitespace(stream);
+ }
+ TSQueryError e = ts_query__parse_pattern(
+ self,
+ stream,
+ depth,
+ capture_count,
+ child_is_immediate
+ );
+ if (e == PARENT_DONE && stream->next == ')') {
+ stream_advance(stream);
+ break;
+ } else if (e) {
+ return e;
+ }
+
+ child_is_immediate = false;
+ }
+ }
+
+ // A pound character indicates the start of a predicate.
+ else if (stream->next == '#') {
+ stream_advance(stream);
+ return ts_query__parse_predicate(self, stream);
+ }
+
+ // Otherwise, this parenthesis is the start of a named node.
+ else {
+ TSSymbol symbol;
+
+ // Parse the wildcard symbol
+ if (
+ stream->next == '_' ||
+
+ // TODO - remove.
+ // For temporary backward compatibility, handle '*' as a wildcard.
+ stream->next == '*'
+ ) {
+ symbol = depth > 0 ? NAMED_WILDCARD_SYMBOL : WILDCARD_SYMBOL;
+ stream_advance(stream);
+ }
+
+ // Parse a normal node name
+ else if (stream_is_ident_start(stream)) {
+ const char *node_name = stream->input;
+ stream_scan_identifier(stream);
+ uint32_t length = stream->input - node_name;
+
+ // TODO - remove.
+ // For temporary backward compatibility, handle predicates without the leading '#' sign.
+ if (length > 0 && (node_name[length - 1] == '!' || node_name[length - 1] == '?')) {
+ stream_reset(stream, node_name);
+ return ts_query__parse_predicate(self, stream);
+ }
+
+ symbol = ts_language_symbol_for_name(
+ self->language,
+ node_name,
+ length,
+ true
+ );
+ if (!symbol) {
+ stream_reset(stream, node_name);
+ return TSQueryErrorNodeType;
+ }
+ } else {
+ return TSQueryErrorSyntax;
+ }
+
+ // Add a step for the node.
+ array_push(&self->steps, query_step__new(symbol, depth, is_immediate));
+
+ // Parse the child patterns
+ stream_skip_whitespace(stream);
+ bool child_is_immediate = false;
+ uint16_t child_start_step_index = self->steps.size;
+ for (;;) {
+ if (stream->next == '.') {
+ child_is_immediate = true;
+ stream_advance(stream);
+ stream_skip_whitespace(stream);
+ }
+
+ TSQueryError e = ts_query__parse_pattern(
+ self,
+ stream,
+ depth + 1,
+ capture_count,
+ child_is_immediate
+ );
+ if (e == PARENT_DONE && stream->next == ')') {
+ if (child_is_immediate) {
+ self->steps.contents[child_start_step_index].is_last_child = true;
+ }
+ stream_advance(stream);
+ break;
+ } else if (e) {
+ return e;
+ }
+
+ child_is_immediate = false;
+ }
+ }
+ }
+
+ // Parse a wildcard pattern
+ else if (
+ stream->next == '_' ||
+
+ // TODO remove.
+ // For temporary backward compatibility, handle '*' as a wildcard.
+ stream->next == '*'
+ ) {
+ stream_advance(stream);
+ stream_skip_whitespace(stream);
+
+ // Add a step that matches any kind of node
+ array_push(&self->steps, query_step__new(WILDCARD_SYMBOL, depth, is_immediate));
+ }
+
+ // Parse a double-quoted anonymous leaf node expression
+ else if (stream->next == '"') {
+ stream_advance(stream);
+
+ // Parse the string content
+ const char *string_content = stream->input;
+ while (stream->next != '"') {
+ if (!stream_advance(stream)) {
+ stream_reset(stream, string_content - 1);
+ return TSQueryErrorSyntax;
+ }
+ }
+ uint32_t length = stream->input - string_content;
+
+ // Add a step for the node
+ TSSymbol symbol = ts_language_symbol_for_name(
+ self->language,
+ string_content,
+ length,
+ false
+ );
+ if (!symbol) {
+ stream_reset(stream, string_content);
+ return TSQueryErrorNodeType;
+ }
+ array_push(&self->steps, query_step__new(symbol, depth, is_immediate));
+
+ if (stream->next != '"') return TSQueryErrorSyntax;
+ stream_advance(stream);
+ }
+
+ // Parse a field-prefixed pattern
+ else if (stream_is_ident_start(stream)) {
+ // Parse the field name
+ const char *field_name = stream->input;
+ stream_scan_identifier(stream);
+ uint32_t length = stream->input - field_name;
+ stream_skip_whitespace(stream);
+
+ if (stream->next != ':') {
+ stream_reset(stream, field_name);
+ return TSQueryErrorSyntax;
+ }
+ stream_advance(stream);
+ stream_skip_whitespace(stream);
+
+ // Parse the pattern
+ uint32_t step_index = self->steps.size;
+ TSQueryError e = ts_query__parse_pattern(
+ self,
+ stream,
+ depth,
+ capture_count,
+ is_immediate
+ );
+ if (e == PARENT_DONE) return TSQueryErrorSyntax;
+ if (e) return e;
+
+ // Add the field name to the first step of the pattern
+ TSFieldId field_id = ts_language_field_id_for_name(
+ self->language,
+ field_name,
+ length
+ );
+ if (!field_id) {
+ stream->input = field_name;
+ return TSQueryErrorField;
+ }
+ self->steps.contents[step_index].field = field_id;
+ }
+
+ else {
+ return TSQueryErrorSyntax;
+ }
+
+ stream_skip_whitespace(stream);
+
+ // Parse suffixes modifiers for this pattern
+ for (;;) {
+ QueryStep *step = &self->steps.contents[starting_step_index];
+
+ // Parse the one-or-more operator.
+ if (stream->next == '+') {
+ stream_advance(stream);
+ stream_skip_whitespace(stream);
+
+ QueryStep repeat_step = query_step__new(WILDCARD_SYMBOL, depth, false);
+ repeat_step.alternative_index = starting_step_index;
+ repeat_step.is_pass_through = true;
+ repeat_step.alternative_is_immediate = true;
+ array_push(&self->steps, repeat_step);
+ }
+
+ // Parse the zero-or-more repetition operator.
+ else if (stream->next == '*') {
+ stream_advance(stream);
+ stream_skip_whitespace(stream);
+
+ QueryStep repeat_step = query_step__new(WILDCARD_SYMBOL, depth, false);
+ repeat_step.alternative_index = starting_step_index;
+ repeat_step.is_pass_through = true;
+ repeat_step.alternative_is_immediate = true;
+ array_push(&self->steps, repeat_step);
+
+ while (step->alternative_index != NONE) {
+ step = &self->steps.contents[step->alternative_index];
+ }
+ step->alternative_index = self->steps.size;
+ }
+
+ // Parse the optional operator.
+ else if (stream->next == '?') {
+ stream_advance(stream);
+ stream_skip_whitespace(stream);
+
+ while (step->alternative_index != NONE) {
+ step = &self->steps.contents[step->alternative_index];
+ }
+ step->alternative_index = self->steps.size;
+ }
+
+ // Parse an '@'-prefixed capture pattern
+ else if (stream->next == '@') {
+ stream_advance(stream);
+ if (!stream_is_ident_start(stream)) return TSQueryErrorSyntax;
+ const char *capture_name = stream->input;
+ stream_scan_identifier(stream);
+ uint32_t length = stream->input - capture_name;
+ stream_skip_whitespace(stream);
+
+ // Add the capture id to the first step of the pattern
+ uint16_t capture_id = symbol_table_insert_name(
+ &self->captures,
+ capture_name,
+ length
+ );
+
+ for (;;) {
+ query_step__add_capture(step, capture_id);
+ if (
+ step->alternative_index != NONE &&
+ step->alternative_index > starting_step_index &&
+ step->alternative_index < self->steps.size
+ ) {
+ starting_step_index = step->alternative_index;
+ step = &self->steps.contents[starting_step_index];
+ } else {
+ break;
+ }
+ }
+
+ (*capture_count)++;
+ }
+
+ // No more suffix modifiers
+ else {
+ break;
+ }
+ }
+
+ return 0;
+}
+
+TSQuery *ts_query_new(
+ const TSLanguage *language,
+ const char *source,
+ uint32_t source_len,
+ uint32_t *error_offset,
+ TSQueryError *error_type
+) {
+ TSSymbol *symbol_map;
+ if (ts_language_version(language) >= TREE_SITTER_LANGUAGE_VERSION_WITH_SYMBOL_DEDUPING) {
+ symbol_map = NULL;
+ } else {
+ // Work around the fact that multiple symbols can currently be
+ // associated with the same name, due to "simple aliases".
+ // In the next language ABI version, this map will be contained
+ // in the language's `public_symbol_map` field.
+ uint32_t symbol_count = ts_language_symbol_count(language);
+ symbol_map = ts_malloc(sizeof(TSSymbol) * symbol_count);
+ for (unsigned i = 0; i < symbol_count; i++) {
+ const char *name = ts_language_symbol_name(language, i);
+ const TSSymbolType symbol_type = ts_language_symbol_type(language, i);
+
+ symbol_map[i] = i;
+
+ for (unsigned j = 0; j < i; j++) {
+ if (ts_language_symbol_type(language, j) == symbol_type) {
+ if (!strcmp(name, ts_language_symbol_name(language, j))) {
+ symbol_map[i] = j;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ TSQuery *self = ts_malloc(sizeof(TSQuery));
+ *self = (TSQuery) {
+ .steps = array_new(),
+ .pattern_map = array_new(),
+ .captures = symbol_table_new(),
+ .predicate_values = symbol_table_new(),
+ .predicate_steps = array_new(),
+ .predicates_by_pattern = array_new(),
+ .symbol_map = symbol_map,
+ .wildcard_root_pattern_count = 0,
+ .language = language,
+ };
+
+ // Parse all of the S-expressions in the given string.
+ Stream stream = stream_new(source, source_len);
+ stream_skip_whitespace(&stream);
+ while (stream.input < stream.end) {
+ uint32_t pattern_index = self->predicates_by_pattern.size;
+ uint32_t start_step_index = self->steps.size;
+ uint32_t capture_count = 0;
+ array_push(&self->start_bytes_by_pattern, stream.input - source);
+ array_push(&self->predicates_by_pattern, ((Slice) {
+ .offset = self->predicate_steps.size,
+ .length = 0,
+ }));
+ *error_type = ts_query__parse_pattern(self, &stream, 0, &capture_count, false);
+ array_push(&self->steps, query_step__new(0, PATTERN_DONE_MARKER, false));
+
+ // If any pattern could not be parsed, then report the error information
+ // and terminate.
+ if (*error_type) {
+ if (*error_type == PARENT_DONE) *error_type = TSQueryErrorSyntax;
+ *error_offset = stream.input - source;
+ ts_query_delete(self);
+ return NULL;
+ }
+
+ // If a pattern has a wildcard at its root, optimize the matching process
+ // by skipping matching the wildcard.
+ if (
+ self->steps.contents[start_step_index].symbol == WILDCARD_SYMBOL
+ ) {
+ QueryStep *second_step = &self->steps.contents[start_step_index + 1];
+ if (second_step->symbol != WILDCARD_SYMBOL && second_step->depth != PATTERN_DONE_MARKER) {
+ start_step_index += 1;
+ }
+ }
+
+ // Maintain a map that can look up patterns for a given root symbol.
+ for (;;) {
+ QueryStep *step = &self->steps.contents[start_step_index];
+ step->is_pattern_start = true;
+ ts_query__pattern_map_insert(self, step->symbol, start_step_index, pattern_index);
+ if (step->symbol == WILDCARD_SYMBOL) {
+ self->wildcard_root_pattern_count++;
+ }
+
+ // If there are alternatives or options at the root of the pattern,
+ // then add multiple entries to the pattern map.
+ if (step->alternative_index != NONE) {
+ start_step_index = step->alternative_index;
+ } else {
+ break;
+ }
+ }
+ }
+
+ ts_query__finalize_steps(self);
+ return self;
+}
+
+void ts_query_delete(TSQuery *self) {
+ if (self) {
+ array_delete(&self->steps);
+ array_delete(&self->pattern_map);
+ array_delete(&self->predicate_steps);
+ array_delete(&self->predicates_by_pattern);
+ array_delete(&self->start_bytes_by_pattern);
+ symbol_table_delete(&self->captures);
+ symbol_table_delete(&self->predicate_values);
+ ts_free(self->symbol_map);
+ ts_free(self);
+ }
+}
+
+uint32_t ts_query_pattern_count(const TSQuery *self) {
+ return self->predicates_by_pattern.size;
+}
+
+uint32_t ts_query_capture_count(const TSQuery *self) {
+ return self->captures.slices.size;
+}
+
+uint32_t ts_query_string_count(const TSQuery *self) {
+ return self->predicate_values.slices.size;
+}
+
+const char *ts_query_capture_name_for_id(
+ const TSQuery *self,
+ uint32_t index,
+ uint32_t *length
+) {
+ return symbol_table_name_for_id(&self->captures, index, length);
+}
+
+const char *ts_query_string_value_for_id(
+ const TSQuery *self,
+ uint32_t index,
+ uint32_t *length
+) {
+ return symbol_table_name_for_id(&self->predicate_values, index, length);
+}
+
+const TSQueryPredicateStep *ts_query_predicates_for_pattern(
+ const TSQuery *self,
+ uint32_t pattern_index,
+ uint32_t *step_count
+) {
+ Slice slice = self->predicates_by_pattern.contents[pattern_index];
+ *step_count = slice.length;
+ return &self->predicate_steps.contents[slice.offset];
+}
+
+uint32_t ts_query_start_byte_for_pattern(
+ const TSQuery *self,
+ uint32_t pattern_index
+) {
+ return self->start_bytes_by_pattern.contents[pattern_index];
+}
+
+void ts_query_disable_capture(
+ TSQuery *self,
+ const char *name,
+ uint32_t length
+) {
+ // Remove capture information for any pattern step that previously
+ // captured with the given name.
+ int id = symbol_table_id_for_name(&self->captures, name, length);
+ if (id != -1) {
+ for (unsigned i = 0; i < self->steps.size; i++) {
+ QueryStep *step = &self->steps.contents[i];
+ query_step__remove_capture(step, id);
+ }
+ ts_query__finalize_steps(self);
+ }
+}
+
+void ts_query_disable_pattern(
+ TSQuery *self,
+ uint32_t pattern_index
+) {
+ // Remove the given pattern from the pattern map. Its steps will still
+ // be in the `steps` array, but they will never be read.
+ for (unsigned i = 0; i < self->pattern_map.size; i++) {
+ PatternEntry *pattern = &self->pattern_map.contents[i];
+ if (pattern->pattern_index == pattern_index) {
+ array_erase(&self->pattern_map, i);
+ i--;
+ }
+ }
+}
+
+/***************
+ * QueryCursor
+ ***************/
+
+TSQueryCursor *ts_query_cursor_new(void) {
+ TSQueryCursor *self = ts_malloc(sizeof(TSQueryCursor));
+ *self = (TSQueryCursor) {
+ .ascending = false,
+ .states = array_new(),
+ .finished_states = array_new(),
+ .capture_list_pool = capture_list_pool_new(),
+ .start_byte = 0,
+ .end_byte = UINT32_MAX,
+ .start_point = {0, 0},
+ .end_point = POINT_MAX,
+ };
+ array_reserve(&self->states, MAX_STATE_COUNT);
+ array_reserve(&self->finished_states, MAX_CAPTURE_LIST_COUNT);
+ return self;
+}
+
+void ts_query_cursor_delete(TSQueryCursor *self) {
+ array_delete(&self->states);
+ array_delete(&self->finished_states);
+ ts_tree_cursor_delete(&self->cursor);
+ capture_list_pool_delete(&self->capture_list_pool);
+ ts_free(self);
+}
+
+void ts_query_cursor_exec(
+ TSQueryCursor *self,
+ const TSQuery *query,
+ TSNode node
+) {
+ array_clear(&self->states);
+ array_clear(&self->finished_states);
+ ts_tree_cursor_reset(&self->cursor, node);
+ capture_list_pool_reset(&self->capture_list_pool);
+ self->next_state_id = 0;
+ self->depth = 0;
+ self->ascending = false;
+ self->query = query;
+}
+
+void ts_query_cursor_set_byte_range(
+ TSQueryCursor *self,
+ uint32_t start_byte,
+ uint32_t end_byte
+) {
+ if (end_byte == 0) {
+ start_byte = 0;
+ end_byte = UINT32_MAX;
+ }
+ self->start_byte = start_byte;
+ self->end_byte = end_byte;
+}
+
+void ts_query_cursor_set_point_range(
+ TSQueryCursor *self,
+ TSPoint start_point,
+ TSPoint end_point
+) {
+ if (end_point.row == 0 && end_point.column == 0) {
+ start_point = POINT_ZERO;
+ end_point = POINT_MAX;
+ }
+ self->start_point = start_point;
+ self->end_point = end_point;
+}
+
+// Search through all of the in-progress states, and find the captured
+// node that occurs earliest in the document.
+static bool ts_query_cursor__first_in_progress_capture(
+ TSQueryCursor *self,
+ uint32_t *state_index,
+ uint32_t *byte_offset,
+ uint32_t *pattern_index
+) {
+ bool result = false;
+ *state_index = UINT32_MAX;
+ *byte_offset = UINT32_MAX;
+ *pattern_index = UINT32_MAX;
+ for (unsigned i = 0; i < self->states.size; i++) {
+ const QueryState *state = &self->states.contents[i];
+ const CaptureList *captures = capture_list_pool_get(
+ &self->capture_list_pool,
+ state->capture_list_id
+ );
+ if (captures->size > 0) {
+ uint32_t capture_byte = ts_node_start_byte(captures->contents[0].node);
+ if (
+ !result ||
+ capture_byte < *byte_offset ||
+ (capture_byte == *byte_offset && state->pattern_index < *pattern_index)
+ ) {
+ result = true;
+ *state_index = i;
+ *byte_offset = capture_byte;
+ *pattern_index = state->pattern_index;
+ }
+ }
+ }
+ return result;
+}
+
+// Determine which node is first in a depth-first traversal
+int ts_query_cursor__compare_nodes(TSNode left, TSNode right) {
+ if (left.id != right.id) {
+ uint32_t left_start = ts_node_start_byte(left);
+ uint32_t right_start = ts_node_start_byte(right);
+ if (left_start < right_start) return -1;
+ if (left_start > right_start) return 1;
+ uint32_t left_node_count = ts_node_end_byte(left);
+ uint32_t right_node_count = ts_node_end_byte(right);
+ if (left_node_count > right_node_count) return -1;
+ if (left_node_count < right_node_count) return 1;
+ }
+ return 0;
+}
+
+// Determine if either state contains a superset of the other state's captures.
+void ts_query_cursor__compare_captures(
+ TSQueryCursor *self,
+ QueryState *left_state,
+ QueryState *right_state,
+ bool *left_contains_right,
+ bool *right_contains_left
+) {
+ const CaptureList *left_captures = capture_list_pool_get(
+ &self->capture_list_pool,
+ left_state->capture_list_id
+ );
+ const CaptureList *right_captures = capture_list_pool_get(
+ &self->capture_list_pool,
+ right_state->capture_list_id
+ );
+ *left_contains_right = true;
+ *right_contains_left = true;
+ unsigned i = 0, j = 0;
+ for (;;) {
+ if (i < left_captures->size) {
+ if (j < right_captures->size) {
+ TSQueryCapture *left = &left_captures->contents[i];
+ TSQueryCapture *right = &right_captures->contents[j];
+ if (left->node.id == right->node.id && left->index == right->index) {
+ i++;
+ j++;
+ } else {
+ switch (ts_query_cursor__compare_nodes(left->node, right->node)) {
+ case -1:
+ *right_contains_left = false;
+ i++;
+ break;
+ case 1:
+ *left_contains_right = false;
+ j++;
+ break;
+ default:
+ *right_contains_left = false;
+ *left_contains_right = false;
+ i++;
+ j++;
+ break;
+ }
+ }
+ } else {
+ *right_contains_left = false;
+ break;
+ }
+ } else {
+ if (j < right_captures->size) {
+ *left_contains_right = false;
+ }
+ break;
+ }
+ }
+}
+
+static bool ts_query_cursor__add_state(
+ TSQueryCursor *self,
+ const PatternEntry *pattern
+) {
+ if (self->states.size >= MAX_STATE_COUNT) {
+ LOG(" too many states");
+ return false;
+ }
+ LOG(
+ " start state. pattern:%u, step:%u\n",
+ pattern->pattern_index,
+ pattern->step_index
+ );
+ QueryStep *step = &self->query->steps.contents[pattern->step_index];
+ array_push(&self->states, ((QueryState) {
+ .capture_list_id = NONE,
+ .step_index = pattern->step_index,
+ .pattern_index = pattern->pattern_index,
+ .start_depth = self->depth - step->depth,
+ .consumed_capture_count = 0,
+ .seeking_immediate_match = false,
+ }));
+ return true;
+}
+
+// Duplicate the given state and insert the newly-created state immediately after
+// the given state in the `states` array.
+static QueryState *ts_query__cursor_copy_state(
+ TSQueryCursor *self,
+ const QueryState *state
+) {
+ if (self->states.size >= MAX_STATE_COUNT) {
+ LOG(" too many states");
+ return NULL;
+ }
+
+ // If the state has captures, copy its capture list.
+ QueryState copy = *state;
+ copy.capture_list_id = state->capture_list_id;
+ if (state->capture_list_id != NONE) {
+ copy.capture_list_id = capture_list_pool_acquire(&self->capture_list_pool);
+ if (copy.capture_list_id == NONE) {
+ LOG(" too many capture lists");
+ return NULL;
+ }
+ const CaptureList *old_captures = capture_list_pool_get(
+ &self->capture_list_pool,
+ state->capture_list_id
+ );
+ CaptureList *new_captures = capture_list_pool_get_mut(
+ &self->capture_list_pool,
+ copy.capture_list_id
+ );
+ array_push_all(new_captures, old_captures);
+ }
+
+ uint32_t index = (state - self->states.contents) + 1;
+ array_insert(&self->states, index, copy);
+ return &self->states.contents[index];
+}
+
+// Walk the tree, processing patterns until at least one pattern finishes,
+// If one or more patterns finish, return `true` and store their states in the
+// `finished_states` array. Multiple patterns can finish on the same node. If
+// there are no more matches, return `false`.
+static inline bool ts_query_cursor__advance(TSQueryCursor *self) {
+ do {
+ if (self->ascending) {
+ LOG("leave node. type:%s\n", ts_node_type(ts_tree_cursor_current_node(&self->cursor)));
+
+ // Leave this node by stepping to its next sibling or to its parent.
+ bool did_move = true;
+ if (ts_tree_cursor_goto_next_sibling(&self->cursor)) {
+ self->ascending = false;
+ } else if (ts_tree_cursor_goto_parent(&self->cursor)) {
+ self->depth--;
+ } else {
+ did_move = false;
+ }
+
+ // After leaving a node, remove any states that cannot make further progress.
+ uint32_t deleted_count = 0;
+ for (unsigned i = 0, n = self->states.size; i < n; i++) {
+ QueryState *state = &self->states.contents[i];
+ QueryStep *step = &self->query->steps.contents[state->step_index];
+
+ // If a state completed its pattern inside of this node, but was deferred from finishing
+ // in order to search for longer matches, mark it as finished.
+ if (step->depth == PATTERN_DONE_MARKER) {
+ if (state->start_depth > self->depth || !did_move) {
+ LOG(" finish pattern %u\n", state->pattern_index);
+ state->id = self->next_state_id++;
+ array_push(&self->finished_states, *state);
+ deleted_count++;
+ continue;
+ }
+ }
+
+ // If a state needed to match something within this node, then remove that state
+ // as it has failed to match.
+ else if ((uint32_t)state->start_depth + (uint32_t)step->depth > self->depth) {
+ LOG(
+ " failed to match. pattern:%u, step:%u\n",
+ state->pattern_index,
+ state->step_index
+ );
+ capture_list_pool_release(
+ &self->capture_list_pool,
+ state->capture_list_id
+ );
+ deleted_count++;
+ continue;
+ }
+
+ if (deleted_count > 0) {
+ self->states.contents[i - deleted_count] = *state;
+ }
+ }
+ self->states.size -= deleted_count;
+
+ if (!did_move) {
+ return self->finished_states.size > 0;
+ }
+ } else {
+ // If this node is before the selected range, then avoid descending into it.
+ TSNode node = ts_tree_cursor_current_node(&self->cursor);
+ if (
+ ts_node_end_byte(node) <= self->start_byte ||
+ point_lte(ts_node_end_point(node), self->start_point)
+ ) {
+ if (!ts_tree_cursor_goto_next_sibling(&self->cursor)) {
+ self->ascending = true;
+ }
+ continue;
+ }
+
+ // If this node is after the selected range, then stop walking.
+ if (
+ self->end_byte <= ts_node_start_byte(node) ||
+ point_lte(self->end_point, ts_node_start_point(node))
+ ) return false;
+
+ // Get the properties of the current node.
+ TSSymbol symbol = ts_node_symbol(node);
+ bool is_named = ts_node_is_named(node);
+ if (symbol != ts_builtin_sym_error && self->query->symbol_map) {
+ symbol = self->query->symbol_map[symbol];
+ }
+ bool can_have_later_siblings;
+ bool can_have_later_siblings_with_this_field;
+ TSFieldId field_id = ts_tree_cursor_current_status(
+ &self->cursor,
+ &can_have_later_siblings,
+ &can_have_later_siblings_with_this_field
+ );
+ LOG(
+ "enter node. type:%s, field:%s, row:%u state_count:%u, finished_state_count:%u\n",
+ ts_node_type(node),
+ ts_language_field_name_for_id(self->query->language, field_id),
+ ts_node_start_point(node).row,
+ self->states.size,
+ self->finished_states.size
+ );
+
+ // Add new states for any patterns whose root node is a wildcard.
+ for (unsigned i = 0; i < self->query->wildcard_root_pattern_count; i++) {
+ PatternEntry *pattern = &self->query->pattern_map.contents[i];
+ QueryStep *step = &self->query->steps.contents[pattern->step_index];
+
+ // If this node matches the first step of the pattern, then add a new
+ // state at the start of this pattern.
+ if (step->field && field_id != step->field) continue;
+ if (!ts_query_cursor__add_state(self, pattern)) break;
+ }
+
+ // Add new states for any patterns whose root node matches this node.
+ unsigned i;
+ if (ts_query__pattern_map_search(self->query, symbol, &i)) {
+ PatternEntry *pattern = &self->query->pattern_map.contents[i];
+ QueryStep *step = &self->query->steps.contents[pattern->step_index];
+ do {
+ // If this node matches the first step of the pattern, then add a new
+ // state at the start of this pattern.
+ if (step->field && field_id != step->field) continue;
+ if (!ts_query_cursor__add_state(self, pattern)) break;
+
+ // Advance to the next pattern whose root node matches this node.
+ i++;
+ if (i == self->query->pattern_map.size) break;
+ pattern = &self->query->pattern_map.contents[i];
+ step = &self->query->steps.contents[pattern->step_index];
+ } while (step->symbol == symbol);
+ }
+
+ // Update all of the in-progress states with current node.
+ for (unsigned i = 0, copy_count = 0; i < self->states.size; i += 1 + copy_count) {
+ QueryState *state = &self->states.contents[i];
+ QueryStep *step = &self->query->steps.contents[state->step_index];
+ state->has_in_progress_alternatives = false;
+ copy_count = 0;
+
+ // Check that the node matches all of the criteria for the next
+ // step of the pattern.
+ if ((uint32_t)state->start_depth + (uint32_t)step->depth != self->depth) continue;
+
+ // Determine if this node matches this step of the pattern, and also
+ // if this node can have later siblings that match this step of the
+ // pattern.
+ bool node_does_match =
+ step->symbol == symbol ||
+ step->symbol == WILDCARD_SYMBOL ||
+ (step->symbol == NAMED_WILDCARD_SYMBOL && is_named);
+ bool later_sibling_can_match = can_have_later_siblings;
+ if ((step->is_immediate && is_named) || state->seeking_immediate_match) {
+ later_sibling_can_match = false;
+ }
+ if (step->is_last_child && can_have_later_siblings) {
+ node_does_match = false;
+ }
+ if (step->field) {
+ if (step->field == field_id) {
+ if (!can_have_later_siblings_with_this_field) {
+ later_sibling_can_match = false;
+ }
+ } else {
+ node_does_match = false;
+ }
+ }
+
+ // Remove states immediately if it is ever clear that they cannot match.
+ if (!node_does_match) {
+ if (!later_sibling_can_match) {
+ LOG(
+ " discard state. pattern:%u, step:%u\n",
+ state->pattern_index,
+ state->step_index
+ );
+ capture_list_pool_release(
+ &self->capture_list_pool,
+ state->capture_list_id
+ );
+ array_erase(&self->states, i);
+ i--;
+ }
+ continue;
+ }
+
+ // Some patterns can match their root node in multiple ways, capturing different
+ // children. If this pattern step could match later children within the same
+ // parent, then this query state cannot simply be updated in place. It must be
+ // split into two states: one that matches this node, and one which skips over
+ // this node, to preserve the possibility of matching later siblings.
+ if (
+ later_sibling_can_match &&
+ !step->is_pattern_start &&
+ step->contains_captures
+ ) {
+ if (ts_query__cursor_copy_state(self, state)) {
+ LOG(
+ " split state for capture. pattern:%u, step:%u\n",
+ state->pattern_index,
+ state->step_index
+ );
+ copy_count++;
+ }
+ }
+
+ // If the current node is captured in this pattern, add it to the capture list.
+ // For the first capture in a pattern, lazily acquire a capture list.
+ if (step->capture_ids[0] != NONE) {
+ if (state->capture_list_id == NONE) {
+ state->capture_list_id = capture_list_pool_acquire(&self->capture_list_pool);
+
+ // If there are no capture lists left in the pool, then terminate whichever
+ // state has captured the earliest node in the document, and steal its
+ // capture list.
+ if (state->capture_list_id == NONE) {
+ uint32_t state_index, byte_offset, pattern_index;
+ if (ts_query_cursor__first_in_progress_capture(
+ self,
+ &state_index,
+ &byte_offset,
+ &pattern_index
+ )) {
+ LOG(
+ " abandon state. index:%u, pattern:%u, offset:%u.\n",
+ state_index, pattern_index, byte_offset
+ );
+ state->capture_list_id = self->states.contents[state_index].capture_list_id;
+ array_erase(&self->states, state_index);
+ if (state_index < i) {
+ i--;
+ state--;
+ }
+ } else {
+ LOG(" too many finished states.\n");
+ array_erase(&self->states, i);
+ i--;
+ continue;
+ }
+ }
+ }
+
+ CaptureList *capture_list = capture_list_pool_get_mut(
+ &self->capture_list_pool,
+ state->capture_list_id
+ );
+ for (unsigned j = 0; j < MAX_STEP_CAPTURE_COUNT; j++) {
+ uint16_t capture_id = step->capture_ids[j];
+ if (step->capture_ids[j] == NONE) break;
+ array_push(capture_list, ((TSQueryCapture) { node, capture_id }));
+ LOG(
+ " capture node. pattern:%u, capture_id:%u, capture_count:%u\n",
+ state->pattern_index,
+ capture_id,
+ capture_list->size
+ );
+ }
+ }
+
+ // Advance this state to the next step of its pattern.
+ state->step_index++;
+ state->seeking_immediate_match = false;
+ LOG(
+ " advance state. pattern:%u, step:%u\n",
+ state->pattern_index,
+ state->step_index
+ );
+
+ // If this state's next step has an 'alternative' step (the step is either optional,
+ // or is the end of a repetition), then copy the state in order to pursue both
+ // alternatives. The alternative step itself may have an alternative, so this is
+ // an interative process.
+ unsigned end_index = i + 1;
+ for (unsigned j = i; j < end_index; j++) {
+ QueryState *state = &self->states.contents[j];
+ QueryStep *next_step = &self->query->steps.contents[state->step_index];
+ if (next_step->alternative_index != NONE) {
+ if (next_step->is_dead_end) {
+ state->step_index = next_step->alternative_index;
+ j--;
+ continue;
+ }
+
+ QueryState *copy = ts_query__cursor_copy_state(self, state);
+ if (next_step->is_pass_through) {
+ state->step_index++;
+ j--;
+ }
+ if (copy) {
+ copy_count++;
+ end_index++;
+ copy->step_index = next_step->alternative_index;
+ if (next_step->alternative_is_immediate) {
+ copy->seeking_immediate_match = true;
+ }
+ LOG(
+ " split state for branch. pattern:%u, step:%u, step:%u, immediate:%d\n",
+ copy->pattern_index,
+ state->step_index,
+ copy->step_index,
+ copy->seeking_immediate_match
+ );
+ }
+ }
+ }
+ }
+
+ for (unsigned i = 0; i < self->states.size; i++) {
+ QueryState *state = &self->states.contents[i];
+ bool did_remove = false;
+
+ // Enfore the longest-match criteria. When a query pattern contains optional or
+ // repeated nodes, this is necesssary to avoid multiple redundant states, where
+ // one state has a strict subset of another state's captures.
+ for (unsigned j = i + 1; j < self->states.size; j++) {
+ QueryState *other_state = &self->states.contents[j];
+ if (
+ state->pattern_index == other_state->pattern_index &&
+ state->start_depth == other_state->start_depth
+ ) {
+ bool left_contains_right, right_contains_left;
+ ts_query_cursor__compare_captures(
+ self,
+ state,
+ other_state,
+ &left_contains_right,
+ &right_contains_left
+ );
+ if (left_contains_right) {
+ if (state->step_index == other_state->step_index) {
+ LOG(
+ " drop shorter state. pattern: %u, step_index: %u\n",
+ state->pattern_index,
+ state->step_index
+ );
+ capture_list_pool_release(&self->capture_list_pool, other_state->capture_list_id);
+ array_erase(&self->states, j);
+ j--;
+ continue;
+ }
+ other_state->has_in_progress_alternatives = true;
+ }
+ if (right_contains_left) {
+ if (state->step_index == other_state->step_index) {
+ LOG(
+ " drop shorter state. pattern: %u, step_index: %u\n",
+ state->pattern_index,
+ state->step_index
+ );
+ capture_list_pool_release(&self->capture_list_pool, state->capture_list_id);
+ array_erase(&self->states, i);
+ did_remove = true;
+ break;
+ }
+ state->has_in_progress_alternatives = true;
+ }
+ }
+ }
+
+ // If there the state is at the end of its pattern, remove it from the list
+ // of in-progress states and add it to the list of finished states.
+ if (!did_remove) {
+ QueryStep *next_step = &self->query->steps.contents[state->step_index];
+ if (next_step->depth == PATTERN_DONE_MARKER) {
+ if (state->has_in_progress_alternatives) {
+ LOG(" defer finishing pattern %u\n", state->pattern_index);
+ } else {
+ LOG(" finish pattern %u\n", state->pattern_index);
+ state->id = self->next_state_id++;
+ array_push(&self->finished_states, *state);
+ array_erase(&self->states, state - self->states.contents);
+ i--;
+ }
+ }
+ }
+ }
+
+ // Continue descending if possible.
+ if (ts_tree_cursor_goto_first_child(&self->cursor)) {
+ self->depth++;
+ } else {
+ self->ascending = true;
+ }
+ }
+ } while (self->finished_states.size == 0);
+
+ return true;
+}
+
+bool ts_query_cursor_next_match(
+ TSQueryCursor *self,
+ TSQueryMatch *match
+) {
+ if (self->finished_states.size == 0) {
+ if (!ts_query_cursor__advance(self)) {
+ return false;
+ }
+ }
+
+ QueryState *state = &self->finished_states.contents[0];
+ match->id = state->id;
+ match->pattern_index = state->pattern_index;
+ const CaptureList *captures = capture_list_pool_get(
+ &self->capture_list_pool,
+ state->capture_list_id
+ );
+ match->captures = captures->contents;
+ match->capture_count = captures->size;
+ capture_list_pool_release(&self->capture_list_pool, state->capture_list_id);
+ array_erase(&self->finished_states, 0);
+ return true;
+}
+
+void ts_query_cursor_remove_match(
+ TSQueryCursor *self,
+ uint32_t match_id
+) {
+ for (unsigned i = 0; i < self->finished_states.size; i++) {
+ const QueryState *state = &self->finished_states.contents[i];
+ if (state->id == match_id) {
+ capture_list_pool_release(
+ &self->capture_list_pool,
+ state->capture_list_id
+ );
+ array_erase(&self->finished_states, i);
+ return;
+ }
+ }
+}
+
+bool ts_query_cursor_next_capture(
+ TSQueryCursor *self,
+ TSQueryMatch *match,
+ uint32_t *capture_index
+) {
+ for (;;) {
+ // The goal here is to return captures in order, even though they may not
+ // be discovered in order, because patterns can overlap. If there are any
+ // finished patterns, then try to find one that contains a capture that
+ // is *definitely* before any capture in an *unfinished* pattern.
+ if (self->finished_states.size > 0) {
+ // First, identify the position of the earliest capture in an unfinished
+ // match. For a finished capture to be returned, it must be *before*
+ // this position.
+ uint32_t first_unfinished_capture_byte;
+ uint32_t first_unfinished_pattern_index;
+ uint32_t first_unfinished_state_index;
+ ts_query_cursor__first_in_progress_capture(
+ self,
+ &first_unfinished_state_index,
+ &first_unfinished_capture_byte,
+ &first_unfinished_pattern_index
+ );
+
+ // Find the earliest capture in a finished match.
+ int first_finished_state_index = -1;
+ uint32_t first_finished_capture_byte = first_unfinished_capture_byte;
+ uint32_t first_finished_pattern_index = first_unfinished_pattern_index;
+ for (unsigned i = 0; i < self->finished_states.size; i++) {
+ const QueryState *state = &self->finished_states.contents[i];
+ const CaptureList *captures = capture_list_pool_get(
+ &self->capture_list_pool,
+ state->capture_list_id
+ );
+ if (captures->size > state->consumed_capture_count) {
+ uint32_t capture_byte = ts_node_start_byte(
+ captures->contents[state->consumed_capture_count].node
+ );
+ if (
+ capture_byte < first_finished_capture_byte ||
+ (
+ capture_byte == first_finished_capture_byte &&
+ state->pattern_index < first_finished_pattern_index
+ )
+ ) {
+ first_finished_state_index = i;
+ first_finished_capture_byte = capture_byte;
+ first_finished_pattern_index = state->pattern_index;
+ }
+ } else {
+ capture_list_pool_release(
+ &self->capture_list_pool,
+ state->capture_list_id
+ );
+ array_erase(&self->finished_states, i);
+ i--;
+ }
+ }
+
+ // If there is finished capture that is clearly before any unfinished
+ // capture, then return its match, and its capture index. Internally
+ // record the fact that the capture has been 'consumed'.
+ if (first_finished_state_index != -1) {
+ QueryState *state = &self->finished_states.contents[
+ first_finished_state_index
+ ];
+ match->id = state->id;
+ match->pattern_index = state->pattern_index;
+ const CaptureList *captures = capture_list_pool_get(
+ &self->capture_list_pool,
+ state->capture_list_id
+ );
+ match->captures = captures->contents;
+ match->capture_count = captures->size;
+ *capture_index = state->consumed_capture_count;
+ state->consumed_capture_count++;
+ return true;
+ }
+
+ if (capture_list_pool_is_empty(&self->capture_list_pool)) {
+ LOG(
+ " abandon state. index:%u, pattern:%u, offset:%u.\n",
+ first_unfinished_state_index,
+ first_unfinished_pattern_index,
+ first_unfinished_capture_byte
+ );
+ capture_list_pool_release(
+ &self->capture_list_pool,
+ self->states.contents[first_unfinished_state_index].capture_list_id
+ );
+ array_erase(&self->states, first_unfinished_state_index);
+ }
+ }
+
+ // If there are no finished matches that are ready to be returned, then
+ // continue finding more matches.
+ if (!ts_query_cursor__advance(self)) return false;
+ }
+}
+
+#undef LOG
diff --git a/src/tree_sitter/reduce_action.h b/src/tree_sitter/reduce_action.h
new file mode 100644
index 0000000000..72aff08d73
--- /dev/null
+++ b/src/tree_sitter/reduce_action.h
@@ -0,0 +1,34 @@
+#ifndef TREE_SITTER_REDUCE_ACTION_H_
+#define TREE_SITTER_REDUCE_ACTION_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "./array.h"
+#include "tree_sitter/api.h"
+
+typedef struct {
+ uint32_t count;
+ TSSymbol symbol;
+ int dynamic_precedence;
+ unsigned short production_id;
+} ReduceAction;
+
+typedef Array(ReduceAction) ReduceActionSet;
+
+static inline void ts_reduce_action_set_add(ReduceActionSet *self,
+ ReduceAction new_action) {
+ for (uint32_t i = 0; i < self->size; i++) {
+ ReduceAction action = self->contents[i];
+ if (action.symbol == new_action.symbol && action.count == new_action.count)
+ return;
+ }
+ array_push(self, new_action);
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // TREE_SITTER_REDUCE_ACTION_H_
diff --git a/src/tree_sitter/reusable_node.h b/src/tree_sitter/reusable_node.h
new file mode 100644
index 0000000000..9cba951909
--- /dev/null
+++ b/src/tree_sitter/reusable_node.h
@@ -0,0 +1,88 @@
+#include "./subtree.h"
+
+typedef struct {
+ Subtree tree;
+ uint32_t child_index;
+ uint32_t byte_offset;
+} StackEntry;
+
+typedef struct {
+ Array(StackEntry) stack;
+ Subtree last_external_token;
+} ReusableNode;
+
+static inline ReusableNode reusable_node_new(void) {
+ return (ReusableNode) {array_new(), NULL_SUBTREE};
+}
+
+static inline void reusable_node_clear(ReusableNode *self) {
+ array_clear(&self->stack);
+ self->last_external_token = NULL_SUBTREE;
+}
+
+static inline void reusable_node_reset(ReusableNode *self, Subtree tree) {
+ reusable_node_clear(self);
+ array_push(&self->stack, ((StackEntry) {
+ .tree = tree,
+ .child_index = 0,
+ .byte_offset = 0,
+ }));
+}
+
+static inline Subtree reusable_node_tree(ReusableNode *self) {
+ return self->stack.size > 0
+ ? self->stack.contents[self->stack.size - 1].tree
+ : NULL_SUBTREE;
+}
+
+static inline uint32_t reusable_node_byte_offset(ReusableNode *self) {
+ return self->stack.size > 0
+ ? self->stack.contents[self->stack.size - 1].byte_offset
+ : UINT32_MAX;
+}
+
+static inline void reusable_node_delete(ReusableNode *self) {
+ array_delete(&self->stack);
+}
+
+static inline void reusable_node_advance(ReusableNode *self) {
+ StackEntry last_entry = *array_back(&self->stack);
+ uint32_t byte_offset = last_entry.byte_offset + ts_subtree_total_bytes(last_entry.tree);
+ if (ts_subtree_has_external_tokens(last_entry.tree)) {
+ self->last_external_token = ts_subtree_last_external_token(last_entry.tree);
+ }
+
+ Subtree tree;
+ uint32_t next_index;
+ do {
+ StackEntry popped_entry = array_pop(&self->stack);
+ next_index = popped_entry.child_index + 1;
+ if (self->stack.size == 0) return;
+ tree = array_back(&self->stack)->tree;
+ } while (ts_subtree_child_count(tree) <= next_index);
+
+ array_push(&self->stack, ((StackEntry) {
+ .tree = tree.ptr->children[next_index],
+ .child_index = next_index,
+ .byte_offset = byte_offset,
+ }));
+}
+
+static inline bool reusable_node_descend(ReusableNode *self) {
+ StackEntry last_entry = *array_back(&self->stack);
+ if (ts_subtree_child_count(last_entry.tree) > 0) {
+ array_push(&self->stack, ((StackEntry) {
+ .tree = last_entry.tree.ptr->children[0],
+ .child_index = 0,
+ .byte_offset = last_entry.byte_offset,
+ }));
+ return true;
+ } else {
+ return false;
+ }
+}
+
+static inline void reusable_node_advance_past_leaf(ReusableNode *self) {
+ while (reusable_node_descend(self)) {}
+ reusable_node_advance(self);
+}
diff --git a/src/tree_sitter/stack.c b/src/tree_sitter/stack.c
new file mode 100644
index 0000000000..6ceee2577f
--- /dev/null
+++ b/src/tree_sitter/stack.c
@@ -0,0 +1,848 @@
+#include "./alloc.h"
+#include "./language.h"
+#include "./subtree.h"
+#include "./array.h"
+#include "./stack.h"
+#include "./length.h"
+#include <assert.h>
+#include <stdio.h>
+
+#define MAX_LINK_COUNT 8
+#define MAX_NODE_POOL_SIZE 50
+#define MAX_ITERATOR_COUNT 64
+
+#if defined _WIN32 && !defined __GNUC__
+#define inline __forceinline
+#else
+#define inline static inline __attribute__((always_inline))
+#endif
+
+typedef struct StackNode StackNode;
+
+typedef struct {
+ StackNode *node;
+ Subtree subtree;
+ bool is_pending;
+} StackLink;
+
+struct StackNode {
+ TSStateId state;
+ Length position;
+ StackLink links[MAX_LINK_COUNT];
+ short unsigned int link_count;
+ uint32_t ref_count;
+ unsigned error_cost;
+ unsigned node_count;
+ int dynamic_precedence;
+};
+
+typedef struct {
+ StackNode *node;
+ SubtreeArray subtrees;
+ uint32_t subtree_count;
+ bool is_pending;
+} StackIterator;
+
+typedef struct {
+ void *payload;
+ StackIterateCallback callback;
+} StackIterateSession;
+
+typedef Array(StackNode *) StackNodeArray;
+
+typedef enum {
+ StackStatusActive,
+ StackStatusPaused,
+ StackStatusHalted,
+} StackStatus;
+
+typedef struct {
+ StackNode *node;
+ Subtree last_external_token;
+ StackSummary *summary;
+ unsigned node_count_at_last_error;
+ TSSymbol lookahead_when_paused;
+ StackStatus status;
+} StackHead;
+
+struct Stack {
+ Array(StackHead) heads;
+ StackSliceArray slices;
+ Array(StackIterator) iterators;
+ StackNodeArray node_pool;
+ StackNode *base_node;
+ SubtreePool *subtree_pool;
+};
+
+typedef unsigned StackAction;
+enum {
+ StackActionNone,
+ StackActionStop = 1,
+ StackActionPop = 2,
+};
+
+typedef StackAction (*StackCallback)(void *, const StackIterator *);
+
+static void stack_node_retain(StackNode *self) {
+ if (!self)
+ return;
+ assert(self->ref_count > 0);
+ self->ref_count++;
+ assert(self->ref_count != 0);
+}
+
+static void stack_node_release(StackNode *self, StackNodeArray *pool, SubtreePool *subtree_pool) {
+recur:
+ assert(self->ref_count != 0);
+ self->ref_count--;
+ if (self->ref_count > 0) return;
+
+ StackNode *first_predecessor = NULL;
+ if (self->link_count > 0) {
+ for (unsigned i = self->link_count - 1; i > 0; i--) {
+ StackLink link = self->links[i];
+ if (link.subtree.ptr) ts_subtree_release(subtree_pool, link.subtree);
+ stack_node_release(link.node, pool, subtree_pool);
+ }
+ StackLink link = self->links[0];
+ if (link.subtree.ptr) ts_subtree_release(subtree_pool, link.subtree);
+ first_predecessor = self->links[0].node;
+ }
+
+ if (pool->size < MAX_NODE_POOL_SIZE) {
+ array_push(pool, self);
+ } else {
+ ts_free(self);
+ }
+
+ if (first_predecessor) {
+ self = first_predecessor;
+ goto recur;
+ }
+}
+
+static StackNode *stack_node_new(StackNode *previous_node, Subtree subtree,
+ bool is_pending, TSStateId state, StackNodeArray *pool) {
+ StackNode *node = pool->size > 0 ?
+ array_pop(pool) :
+ ts_malloc(sizeof(StackNode));
+ *node = (StackNode){.ref_count = 1, .link_count = 0, .state = state};
+
+ if (previous_node) {
+ node->link_count = 1;
+ node->links[0] = (StackLink){
+ .node = previous_node,
+ .subtree = subtree,
+ .is_pending = is_pending,
+ };
+
+ node->position = previous_node->position;
+ node->error_cost = previous_node->error_cost;
+ node->dynamic_precedence = previous_node->dynamic_precedence;
+ node->node_count = previous_node->node_count;
+
+ if (subtree.ptr) {
+ node->error_cost += ts_subtree_error_cost(subtree);
+ node->position = length_add(node->position, ts_subtree_total_size(subtree));
+ node->node_count += ts_subtree_node_count(subtree);
+ node->dynamic_precedence += ts_subtree_dynamic_precedence(subtree);
+ }
+ } else {
+ node->position = length_zero();
+ node->error_cost = 0;
+ }
+
+ return node;
+}
+
+static bool stack__subtree_is_equivalent(Subtree left, Subtree right) {
+ return
+ left.ptr == right.ptr ||
+ (left.ptr && right.ptr &&
+ ts_subtree_symbol(left) == ts_subtree_symbol(right) &&
+ ((ts_subtree_error_cost(left) > 0 && ts_subtree_error_cost(right) > 0) ||
+ (ts_subtree_padding(left).bytes == ts_subtree_padding(right).bytes &&
+ ts_subtree_size(left).bytes == ts_subtree_size(right).bytes &&
+ ts_subtree_child_count(left) == ts_subtree_child_count(right) &&
+ ts_subtree_extra(left) == ts_subtree_extra(right) &&
+ ts_subtree_external_scanner_state_eq(left, right))));
+}
+
+static void stack_node_add_link(StackNode *self, StackLink link, SubtreePool *subtree_pool) {
+ if (link.node == self) return;
+
+ for (int i = 0; i < self->link_count; i++) {
+ StackLink *existing_link = &self->links[i];
+ if (stack__subtree_is_equivalent(existing_link->subtree, link.subtree)) {
+ // In general, we preserve ambiguities until they are removed from the stack
+ // during a pop operation where multiple paths lead to the same node. But in
+ // the special case where two links directly connect the same pair of nodes,
+ // we can safely remove the ambiguity ahead of time without changing behavior.
+ if (existing_link->node == link.node) {
+ if (
+ ts_subtree_dynamic_precedence(link.subtree) >
+ ts_subtree_dynamic_precedence(existing_link->subtree)
+ ) {
+ ts_subtree_retain(link.subtree);
+ ts_subtree_release(subtree_pool, existing_link->subtree);
+ existing_link->subtree = link.subtree;
+ self->dynamic_precedence =
+ link.node->dynamic_precedence + ts_subtree_dynamic_precedence(link.subtree);
+ }
+ return;
+ }
+
+ // If the previous nodes are mergeable, merge them recursively.
+ if (existing_link->node->state == link.node->state &&
+ existing_link->node->position.bytes == link.node->position.bytes) {
+ for (int j = 0; j < link.node->link_count; j++) {
+ stack_node_add_link(existing_link->node, link.node->links[j], subtree_pool);
+ }
+ int32_t dynamic_precedence = link.node->dynamic_precedence;
+ if (link.subtree.ptr) {
+ dynamic_precedence += ts_subtree_dynamic_precedence(link.subtree);
+ }
+ if (dynamic_precedence > self->dynamic_precedence) {
+ self->dynamic_precedence = dynamic_precedence;
+ }
+ return;
+ }
+ }
+ }
+
+ if (self->link_count == MAX_LINK_COUNT) return;
+
+ stack_node_retain(link.node);
+ unsigned node_count = link.node->node_count;
+ int dynamic_precedence = link.node->dynamic_precedence;
+ self->links[self->link_count++] = link;
+
+ if (link.subtree.ptr) {
+ ts_subtree_retain(link.subtree);
+ node_count += ts_subtree_node_count(link.subtree);
+ dynamic_precedence += ts_subtree_dynamic_precedence(link.subtree);
+ }
+
+ if (node_count > self->node_count) self->node_count = node_count;
+ if (dynamic_precedence > self->dynamic_precedence) self->dynamic_precedence = dynamic_precedence;
+}
+
+static void stack_head_delete(StackHead *self, StackNodeArray *pool, SubtreePool *subtree_pool) {
+ if (self->node) {
+ if (self->last_external_token.ptr) {
+ ts_subtree_release(subtree_pool, self->last_external_token);
+ }
+ if (self->summary) {
+ array_delete(self->summary);
+ ts_free(self->summary);
+ }
+ stack_node_release(self->node, pool, subtree_pool);
+ }
+}
+
+static StackVersion ts_stack__add_version(Stack *self, StackVersion original_version,
+ StackNode *node) {
+ StackHead head = {
+ .node = node,
+ .node_count_at_last_error = self->heads.contents[original_version].node_count_at_last_error,
+ .last_external_token = self->heads.contents[original_version].last_external_token,
+ .status = StackStatusActive,
+ .lookahead_when_paused = 0,
+ };
+ array_push(&self->heads, head);
+ stack_node_retain(node);
+ if (head.last_external_token.ptr) ts_subtree_retain(head.last_external_token);
+ return (StackVersion)(self->heads.size - 1);
+}
+
+static void ts_stack__add_slice(Stack *self, StackVersion original_version,
+ StackNode *node, SubtreeArray *subtrees) {
+ for (uint32_t i = self->slices.size - 1; i + 1 > 0; i--) {
+ StackVersion version = self->slices.contents[i].version;
+ if (self->heads.contents[version].node == node) {
+ StackSlice slice = {*subtrees, version};
+ array_insert(&self->slices, i + 1, slice);
+ return;
+ }
+ }
+
+ StackVersion version = ts_stack__add_version(self, original_version, node);
+ StackSlice slice = { *subtrees, version };
+ array_push(&self->slices, slice);
+}
+
+inline StackSliceArray stack__iter(Stack *self, StackVersion version,
+ StackCallback callback, void *payload,
+ int goal_subtree_count) {
+ array_clear(&self->slices);
+ array_clear(&self->iterators);
+
+ StackHead *head = array_get(&self->heads, version);
+ StackIterator iterator = {
+ .node = head->node,
+ .subtrees = array_new(),
+ .subtree_count = 0,
+ .is_pending = true,
+ };
+
+ bool include_subtrees = false;
+ if (goal_subtree_count >= 0) {
+ include_subtrees = true;
+ array_reserve(&iterator.subtrees, goal_subtree_count);
+ }
+
+ array_push(&self->iterators, iterator);
+
+ while (self->iterators.size > 0) {
+ for (uint32_t i = 0, size = self->iterators.size; i < size; i++) {
+ StackIterator *iterator = &self->iterators.contents[i];
+ StackNode *node = iterator->node;
+
+ StackAction action = callback(payload, iterator);
+ bool should_pop = action & StackActionPop;
+ bool should_stop = action & StackActionStop || node->link_count == 0;
+
+ if (should_pop) {
+ SubtreeArray subtrees = iterator->subtrees;
+ if (!should_stop)
+ ts_subtree_array_copy(subtrees, &subtrees);
+ ts_subtree_array_reverse(&subtrees);
+ ts_stack__add_slice(
+ self,
+ version,
+ node,
+ &subtrees
+ );
+ }
+
+ if (should_stop) {
+ if (!should_pop)
+ ts_subtree_array_delete(self->subtree_pool, &iterator->subtrees);
+ array_erase(&self->iterators, i);
+ i--, size--;
+ continue;
+ }
+
+ for (uint32_t j = 1; j <= node->link_count; j++) {
+ StackIterator *next_iterator;
+ StackLink link;
+ if (j == node->link_count) {
+ link = node->links[0];
+ next_iterator = &self->iterators.contents[i];
+ } else {
+ if (self->iterators.size >= MAX_ITERATOR_COUNT) continue;
+ link = node->links[j];
+ StackIterator current_iterator = self->iterators.contents[i];
+ array_push(&self->iterators, current_iterator);
+ next_iterator = array_back(&self->iterators);
+ ts_subtree_array_copy(next_iterator->subtrees, &next_iterator->subtrees);
+ }
+
+ next_iterator->node = link.node;
+ if (link.subtree.ptr) {
+ if (include_subtrees) {
+ array_push(&next_iterator->subtrees, link.subtree);
+ ts_subtree_retain(link.subtree);
+ }
+
+ if (!ts_subtree_extra(link.subtree)) {
+ next_iterator->subtree_count++;
+ if (!link.is_pending) {
+ next_iterator->is_pending = false;
+ }
+ }
+ } else {
+ next_iterator->subtree_count++;
+ next_iterator->is_pending = false;
+ }
+ }
+ }
+ }
+
+ return self->slices;
+}
+
+Stack *ts_stack_new(SubtreePool *subtree_pool) {
+ Stack *self = ts_calloc(1, sizeof(Stack));
+
+ array_init(&self->heads);
+ array_init(&self->slices);
+ array_init(&self->iterators);
+ array_init(&self->node_pool);
+ array_reserve(&self->heads, 4);
+ array_reserve(&self->slices, 4);
+ array_reserve(&self->iterators, 4);
+ array_reserve(&self->node_pool, MAX_NODE_POOL_SIZE);
+
+ self->subtree_pool = subtree_pool;
+ self->base_node = stack_node_new(NULL, NULL_SUBTREE, false, 1, &self->node_pool);
+ ts_stack_clear(self);
+
+ return self;
+}
+
+void ts_stack_delete(Stack *self) {
+ if (self->slices.contents)
+ array_delete(&self->slices);
+ if (self->iterators.contents)
+ array_delete(&self->iterators);
+ stack_node_release(self->base_node, &self->node_pool, self->subtree_pool);
+ for (uint32_t i = 0; i < self->heads.size; i++) {
+ stack_head_delete(&self->heads.contents[i], &self->node_pool, self->subtree_pool);
+ }
+ array_clear(&self->heads);
+ if (self->node_pool.contents) {
+ for (uint32_t i = 0; i < self->node_pool.size; i++)
+ ts_free(self->node_pool.contents[i]);
+ array_delete(&self->node_pool);
+ }
+ array_delete(&self->heads);
+ ts_free(self);
+}
+
+uint32_t ts_stack_version_count(const Stack *self) {
+ return self->heads.size;
+}
+
+TSStateId ts_stack_state(const Stack *self, StackVersion version) {
+ return array_get(&self->heads, version)->node->state;
+}
+
+Length ts_stack_position(const Stack *self, StackVersion version) {
+ return array_get(&self->heads, version)->node->position;
+}
+
+Subtree ts_stack_last_external_token(const Stack *self, StackVersion version) {
+ return array_get(&self->heads, version)->last_external_token;
+}
+
+void ts_stack_set_last_external_token(Stack *self, StackVersion version, Subtree token) {
+ StackHead *head = array_get(&self->heads, version);
+ if (token.ptr) ts_subtree_retain(token);
+ if (head->last_external_token.ptr) ts_subtree_release(self->subtree_pool, head->last_external_token);
+ head->last_external_token = token;
+}
+
+unsigned ts_stack_error_cost(const Stack *self, StackVersion version) {
+ StackHead *head = array_get(&self->heads, version);
+ unsigned result = head->node->error_cost;
+ if (
+ head->status == StackStatusPaused ||
+ (head->node->state == ERROR_STATE && !head->node->links[0].subtree.ptr)) {
+ result += ERROR_COST_PER_RECOVERY;
+ }
+ return result;
+}
+
+unsigned ts_stack_node_count_since_error(const Stack *self, StackVersion version) {
+ StackHead *head = array_get(&self->heads, version);
+ if (head->node->node_count < head->node_count_at_last_error) {
+ head->node_count_at_last_error = head->node->node_count;
+ }
+ return head->node->node_count - head->node_count_at_last_error;
+}
+
+void ts_stack_push(Stack *self, StackVersion version, Subtree subtree,
+ bool pending, TSStateId state) {
+ StackHead *head = array_get(&self->heads, version);
+ StackNode *new_node = stack_node_new(head->node, subtree, pending, state, &self->node_pool);
+ if (!subtree.ptr) head->node_count_at_last_error = new_node->node_count;
+ head->node = new_node;
+}
+
+inline StackAction iterate_callback(void *payload, const StackIterator *iterator) {
+ StackIterateSession *session = payload;
+ session->callback(
+ session->payload,
+ iterator->node->state,
+ iterator->subtree_count
+ );
+ return StackActionNone;
+}
+
+void ts_stack_iterate(Stack *self, StackVersion version,
+ StackIterateCallback callback, void *payload) {
+ StackIterateSession session = {payload, callback};
+ stack__iter(self, version, iterate_callback, &session, -1);
+}
+
+inline StackAction pop_count_callback(void *payload, const StackIterator *iterator) {
+ unsigned *goal_subtree_count = payload;
+ if (iterator->subtree_count == *goal_subtree_count) {
+ return StackActionPop | StackActionStop;
+ } else {
+ return StackActionNone;
+ }
+}
+
+StackSliceArray ts_stack_pop_count(Stack *self, StackVersion version, uint32_t count) {
+ return stack__iter(self, version, pop_count_callback, &count, count);
+}
+
+inline StackAction pop_pending_callback(void *payload, const StackIterator *iterator) {
+ (void)payload;
+ if (iterator->subtree_count >= 1) {
+ if (iterator->is_pending) {
+ return StackActionPop | StackActionStop;
+ } else {
+ return StackActionStop;
+ }
+ } else {
+ return StackActionNone;
+ }
+}
+
+StackSliceArray ts_stack_pop_pending(Stack *self, StackVersion version) {
+ StackSliceArray pop = stack__iter(self, version, pop_pending_callback, NULL, 0);
+ if (pop.size > 0) {
+ ts_stack_renumber_version(self, pop.contents[0].version, version);
+ pop.contents[0].version = version;
+ }
+ return pop;
+}
+
+inline StackAction pop_error_callback(void *payload, const StackIterator *iterator) {
+ if (iterator->subtrees.size > 0) {
+ bool *found_error = payload;
+ if (!*found_error && ts_subtree_is_error(iterator->subtrees.contents[0])) {
+ *found_error = true;
+ return StackActionPop | StackActionStop;
+ } else {
+ return StackActionStop;
+ }
+ } else {
+ return StackActionNone;
+ }
+}
+
+SubtreeArray ts_stack_pop_error(Stack *self, StackVersion version) {
+ StackNode *node = array_get(&self->heads, version)->node;
+ for (unsigned i = 0; i < node->link_count; i++) {
+ if (node->links[i].subtree.ptr && ts_subtree_is_error(node->links[i].subtree)) {
+ bool found_error = false;
+ StackSliceArray pop = stack__iter(self, version, pop_error_callback, &found_error, 1);
+ if (pop.size > 0) {
+ assert(pop.size == 1);
+ ts_stack_renumber_version(self, pop.contents[0].version, version);
+ return pop.contents[0].subtrees;
+ }
+ break;
+ }
+ }
+ return (SubtreeArray){.size = 0};
+}
+
+inline StackAction pop_all_callback(void *payload, const StackIterator *iterator) {
+ (void)payload;
+ return iterator->node->link_count == 0 ? StackActionPop : StackActionNone;
+}
+
+StackSliceArray ts_stack_pop_all(Stack *self, StackVersion version) {
+ return stack__iter(self, version, pop_all_callback, NULL, 0);
+}
+
+typedef struct {
+ StackSummary *summary;
+ unsigned max_depth;
+} SummarizeStackSession;
+
+inline StackAction summarize_stack_callback(void *payload, const StackIterator *iterator) {
+ SummarizeStackSession *session = payload;
+ TSStateId state = iterator->node->state;
+ unsigned depth = iterator->subtree_count;
+ if (depth > session->max_depth) return StackActionStop;
+ for (unsigned i = session->summary->size - 1; i + 1 > 0; i--) {
+ StackSummaryEntry entry = session->summary->contents[i];
+ if (entry.depth < depth) break;
+ if (entry.depth == depth && entry.state == state) return StackActionNone;
+ }
+ array_push(session->summary, ((StackSummaryEntry){
+ .position = iterator->node->position,
+ .depth = depth,
+ .state = state,
+ }));
+ return StackActionNone;
+}
+
+void ts_stack_record_summary(Stack *self, StackVersion version, unsigned max_depth) {
+ SummarizeStackSession session = {
+ .summary = ts_malloc(sizeof(StackSummary)),
+ .max_depth = max_depth
+ };
+ array_init(session.summary);
+ stack__iter(self, version, summarize_stack_callback, &session, -1);
+ self->heads.contents[version].summary = session.summary;
+}
+
+StackSummary *ts_stack_get_summary(Stack *self, StackVersion version) {
+ return array_get(&self->heads, version)->summary;
+}
+
+int ts_stack_dynamic_precedence(Stack *self, StackVersion version) {
+ return array_get(&self->heads, version)->node->dynamic_precedence;
+}
+
+bool ts_stack_has_advanced_since_error(const Stack *self, StackVersion version) {
+ const StackHead *head = array_get(&self->heads, version);
+ const StackNode *node = head->node;
+ if (node->error_cost == 0) return true;
+ while (node) {
+ if (node->link_count > 0) {
+ Subtree subtree = node->links[0].subtree;
+ if (subtree.ptr) {
+ if (ts_subtree_total_bytes(subtree) > 0) {
+ return true;
+ } else if (
+ node->node_count > head->node_count_at_last_error &&
+ ts_subtree_error_cost(subtree) == 0
+ ) {
+ node = node->links[0].node;
+ continue;
+ }
+ }
+ }
+ break;
+ }
+ return false;
+}
+
+void ts_stack_remove_version(Stack *self, StackVersion version) {
+ stack_head_delete(array_get(&self->heads, version), &self->node_pool, self->subtree_pool);
+ array_erase(&self->heads, version);
+}
+
+void ts_stack_renumber_version(Stack *self, StackVersion v1, StackVersion v2) {
+ if (v1 == v2) return;
+ assert(v2 < v1);
+ assert((uint32_t)v1 < self->heads.size);
+ StackHead *source_head = &self->heads.contents[v1];
+ StackHead *target_head = &self->heads.contents[v2];
+ if (target_head->summary && !source_head->summary) {
+ source_head->summary = target_head->summary;
+ target_head->summary = NULL;
+ }
+ stack_head_delete(target_head, &self->node_pool, self->subtree_pool);
+ *target_head = *source_head;
+ array_erase(&self->heads, v1);
+}
+
+void ts_stack_swap_versions(Stack *self, StackVersion v1, StackVersion v2) {
+ StackHead temporary_head = self->heads.contents[v1];
+ self->heads.contents[v1] = self->heads.contents[v2];
+ self->heads.contents[v2] = temporary_head;
+}
+
+StackVersion ts_stack_copy_version(Stack *self, StackVersion version) {
+ assert(version < self->heads.size);
+ array_push(&self->heads, self->heads.contents[version]);
+ StackHead *head = array_back(&self->heads);
+ stack_node_retain(head->node);
+ if (head->last_external_token.ptr) ts_subtree_retain(head->last_external_token);
+ head->summary = NULL;
+ return self->heads.size - 1;
+}
+
+bool ts_stack_merge(Stack *self, StackVersion version1, StackVersion version2) {
+ if (!ts_stack_can_merge(self, version1, version2)) return false;
+ StackHead *head1 = &self->heads.contents[version1];
+ StackHead *head2 = &self->heads.contents[version2];
+ for (uint32_t i = 0; i < head2->node->link_count; i++) {
+ stack_node_add_link(head1->node, head2->node->links[i], self->subtree_pool);
+ }
+ if (head1->node->state == ERROR_STATE) {
+ head1->node_count_at_last_error = head1->node->node_count;
+ }
+ ts_stack_remove_version(self, version2);
+ return true;
+}
+
+bool ts_stack_can_merge(Stack *self, StackVersion version1, StackVersion version2) {
+ StackHead *head1 = &self->heads.contents[version1];
+ StackHead *head2 = &self->heads.contents[version2];
+ return
+ head1->status == StackStatusActive &&
+ head2->status == StackStatusActive &&
+ head1->node->state == head2->node->state &&
+ head1->node->position.bytes == head2->node->position.bytes &&
+ head1->node->error_cost == head2->node->error_cost &&
+ ts_subtree_external_scanner_state_eq(head1->last_external_token, head2->last_external_token);
+}
+
+void ts_stack_halt(Stack *self, StackVersion version) {
+ array_get(&self->heads, version)->status = StackStatusHalted;
+}
+
+void ts_stack_pause(Stack *self, StackVersion version, TSSymbol lookahead) {
+ StackHead *head = array_get(&self->heads, version);
+ head->status = StackStatusPaused;
+ head->lookahead_when_paused = lookahead;
+ head->node_count_at_last_error = head->node->node_count;
+}
+
+bool ts_stack_is_active(const Stack *self, StackVersion version) {
+ return array_get(&self->heads, version)->status == StackStatusActive;
+}
+
+bool ts_stack_is_halted(const Stack *self, StackVersion version) {
+ return array_get(&self->heads, version)->status == StackStatusHalted;
+}
+
+bool ts_stack_is_paused(const Stack *self, StackVersion version) {
+ return array_get(&self->heads, version)->status == StackStatusPaused;
+}
+
+TSSymbol ts_stack_resume(Stack *self, StackVersion version) {
+ StackHead *head = array_get(&self->heads, version);
+ assert(head->status == StackStatusPaused);
+ TSSymbol result = head->lookahead_when_paused;
+ head->status = StackStatusActive;
+ head->lookahead_when_paused = 0;
+ return result;
+}
+
+void ts_stack_clear(Stack *self) {
+ stack_node_retain(self->base_node);
+ for (uint32_t i = 0; i < self->heads.size; i++) {
+ stack_head_delete(&self->heads.contents[i], &self->node_pool, self->subtree_pool);
+ }
+ array_clear(&self->heads);
+ array_push(&self->heads, ((StackHead){
+ .node = self->base_node,
+ .last_external_token = NULL_SUBTREE,
+ .status = StackStatusActive,
+ .lookahead_when_paused = 0,
+ }));
+}
+
+bool ts_stack_print_dot_graph(Stack *self, const TSLanguage *language, FILE *f) {
+ array_reserve(&self->iterators, 32);
+ bool was_recording_allocations = ts_toggle_allocation_recording(false);
+ if (!f) f = stderr;
+
+ fprintf(f, "digraph stack {\n");
+ fprintf(f, "rankdir=\"RL\";\n");
+ fprintf(f, "edge [arrowhead=none]\n");
+
+ Array(StackNode *) visited_nodes = array_new();
+
+ array_clear(&self->iterators);
+ for (uint32_t i = 0; i < self->heads.size; i++) {
+ StackHead *head = &self->heads.contents[i];
+ if (head->status == StackStatusHalted) continue;
+
+ fprintf(f, "node_head_%u [shape=none, label=\"\"]\n", i);
+ fprintf(f, "node_head_%u -> node_%p [", i, head->node);
+
+ if (head->status == StackStatusPaused) {
+ fprintf(f, "color=red ");
+ }
+ fprintf(f,
+ "label=%u, fontcolor=blue, weight=10000, labeltooltip=\"node_count: %u\nerror_cost: %u",
+ i,
+ ts_stack_node_count_since_error(self, i),
+ ts_stack_error_cost(self, i)
+ );
+
+ if (head->last_external_token.ptr) {
+ const ExternalScannerState *state = &head->last_external_token.ptr->external_scanner_state;
+ const char *data = ts_external_scanner_state_data(state);
+ fprintf(f, "\nexternal_scanner_state:");
+ for (uint32_t j = 0; j < state->length; j++) fprintf(f, " %2X", data[j]);
+ }
+
+ fprintf(f, "\"]\n");
+ array_push(&self->iterators, ((StackIterator){.node = head->node }));
+ }
+
+ bool all_iterators_done = false;
+ while (!all_iterators_done) {
+ all_iterators_done = true;
+
+ for (uint32_t i = 0; i < self->iterators.size; i++) {
+ StackIterator iterator = self->iterators.contents[i];
+ StackNode *node = iterator.node;
+
+ for (uint32_t j = 0; j < visited_nodes.size; j++) {
+ if (visited_nodes.contents[j] == node) {
+ node = NULL;
+ break;
+ }
+ }
+
+ if (!node) continue;
+ all_iterators_done = false;
+
+ fprintf(f, "node_%p [", node);
+ if (node->state == ERROR_STATE) {
+ fprintf(f, "label=\"?\"");
+ } else if (
+ node->link_count == 1 &&
+ node->links[0].subtree.ptr &&
+ ts_subtree_extra(node->links[0].subtree)
+ ) {
+ fprintf(f, "shape=point margin=0 label=\"\"");
+ } else {
+ fprintf(f, "label=\"%d\"", node->state);
+ }
+
+ fprintf(
+ f,
+ " tooltip=\"position: %u,%u\nnode_count:%u\nerror_cost: %u\ndynamic_precedence: %d\"];\n",
+ node->position.extent.row + 1,
+ node->position.extent.column,
+ node->node_count,
+ node->error_cost,
+ node->dynamic_precedence
+ );
+
+ for (int j = 0; j < node->link_count; j++) {
+ StackLink link = node->links[j];
+ fprintf(f, "node_%p -> node_%p [", node, link.node);
+ if (link.is_pending) fprintf(f, "style=dashed ");
+ if (link.subtree.ptr && ts_subtree_extra(link.subtree)) fprintf(f, "fontcolor=gray ");
+
+ if (!link.subtree.ptr) {
+ fprintf(f, "color=red");
+ } else {
+ fprintf(f, "label=\"");
+ bool quoted = ts_subtree_visible(link.subtree) && !ts_subtree_named(link.subtree);
+ if (quoted) fprintf(f, "'");
+ const char *name = ts_language_symbol_name(language, ts_subtree_symbol(link.subtree));
+ for (const char *c = name; *c; c++) {
+ if (*c == '\"' || *c == '\\') fprintf(f, "\\");
+ fprintf(f, "%c", *c);
+ }
+ if (quoted) fprintf(f, "'");
+ fprintf(f, "\"");
+ fprintf(
+ f,
+ "labeltooltip=\"error_cost: %u\ndynamic_precedence: %u\"",
+ ts_subtree_error_cost(link.subtree),
+ ts_subtree_dynamic_precedence(link.subtree)
+ );
+ }
+
+ fprintf(f, "];\n");
+
+ StackIterator *next_iterator;
+ if (j == 0) {
+ next_iterator = &self->iterators.contents[i];
+ } else {
+ array_push(&self->iterators, iterator);
+ next_iterator = array_back(&self->iterators);
+ }
+ next_iterator->node = link.node;
+ }
+
+ array_push(&visited_nodes, node);
+ }
+ }
+
+ fprintf(f, "}\n");
+
+ array_delete(&visited_nodes);
+ ts_toggle_allocation_recording(was_recording_allocations);
+ return true;
+}
+
+#undef inline
diff --git a/src/tree_sitter/stack.h b/src/tree_sitter/stack.h
new file mode 100644
index 0000000000..ec7a69d2b4
--- /dev/null
+++ b/src/tree_sitter/stack.h
@@ -0,0 +1,135 @@
+#ifndef TREE_SITTER_PARSE_STACK_H_
+#define TREE_SITTER_PARSE_STACK_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "./array.h"
+#include "./subtree.h"
+#include "./error_costs.h"
+#include <stdio.h>
+
+typedef struct Stack Stack;
+
+typedef unsigned StackVersion;
+#define STACK_VERSION_NONE ((StackVersion)-1)
+
+typedef struct {
+ SubtreeArray subtrees;
+ StackVersion version;
+} StackSlice;
+typedef Array(StackSlice) StackSliceArray;
+
+typedef struct {
+ Length position;
+ unsigned depth;
+ TSStateId state;
+} StackSummaryEntry;
+typedef Array(StackSummaryEntry) StackSummary;
+
+// Create a stack.
+Stack *ts_stack_new(SubtreePool *);
+
+// Release the memory reserved for a given stack.
+void ts_stack_delete(Stack *);
+
+// Get the stack's current number of versions.
+uint32_t ts_stack_version_count(const Stack *);
+
+// Get the state at the top of the given version of the stack. If the stack is
+// empty, this returns the initial state, 0.
+TSStateId ts_stack_state(const Stack *, StackVersion);
+
+// Get the last external token associated with a given version of the stack.
+Subtree ts_stack_last_external_token(const Stack *, StackVersion);
+
+// Set the last external token associated with a given version of the stack.
+void ts_stack_set_last_external_token(Stack *, StackVersion, Subtree );
+
+// Get the position of the given version of the stack within the document.
+Length ts_stack_position(const Stack *, StackVersion);
+
+// Push a tree and state onto the given version of the stack.
+//
+// This transfers ownership of the tree to the Stack. Callers that
+// need to retain ownership of the tree for their own purposes should
+// first retain the tree.
+void ts_stack_push(Stack *, StackVersion, Subtree , bool, TSStateId);
+
+// Pop the given number of entries from the given version of the stack. This
+// operation can increase the number of stack versions by revealing multiple
+// versions which had previously been merged. It returns an array that
+// specifies the index of each revealed version and the trees that were
+// removed from that version.
+StackSliceArray ts_stack_pop_count(Stack *, StackVersion, uint32_t count);
+
+// Remove an error at the top of the given version of the stack.
+SubtreeArray ts_stack_pop_error(Stack *, StackVersion);
+
+// Remove any pending trees from the top of the given version of the stack.
+StackSliceArray ts_stack_pop_pending(Stack *, StackVersion);
+
+// Remove any all trees from the given version of the stack.
+StackSliceArray ts_stack_pop_all(Stack *, StackVersion);
+
+// Get the maximum number of tree nodes reachable from this version of the stack
+// since the last error was detected.
+unsigned ts_stack_node_count_since_error(const Stack *, StackVersion);
+
+int ts_stack_dynamic_precedence(Stack *, StackVersion);
+
+bool ts_stack_has_advanced_since_error(const Stack *, StackVersion);
+
+// Compute a summary of all the parse states near the top of the given
+// version of the stack and store the summary for later retrieval.
+void ts_stack_record_summary(Stack *, StackVersion, unsigned max_depth);
+
+// Retrieve a summary of all the parse states near the top of the
+// given version of the stack.
+StackSummary *ts_stack_get_summary(Stack *, StackVersion);
+
+// Get the total cost of all errors on the given version of the stack.
+unsigned ts_stack_error_cost(const Stack *, StackVersion version);
+
+// Merge the given two stack versions if possible, returning true
+// if they were successfully merged and false otherwise.
+bool ts_stack_merge(Stack *, StackVersion, StackVersion);
+
+// Determine whether the given two stack versions can be merged.
+bool ts_stack_can_merge(Stack *, StackVersion, StackVersion);
+
+TSSymbol ts_stack_resume(Stack *, StackVersion);
+
+void ts_stack_pause(Stack *, StackVersion, TSSymbol);
+
+void ts_stack_halt(Stack *, StackVersion);
+
+bool ts_stack_is_active(const Stack *, StackVersion);
+
+bool ts_stack_is_paused(const Stack *, StackVersion);
+
+bool ts_stack_is_halted(const Stack *, StackVersion);
+
+void ts_stack_renumber_version(Stack *, StackVersion, StackVersion);
+
+void ts_stack_swap_versions(Stack *, StackVersion, StackVersion);
+
+StackVersion ts_stack_copy_version(Stack *, StackVersion);
+
+// Remove the given version from the stack.
+void ts_stack_remove_version(Stack *, StackVersion);
+
+void ts_stack_clear(Stack *);
+
+bool ts_stack_print_dot_graph(Stack *, const TSLanguage *, FILE *);
+
+typedef void (*StackIterateCallback)(void *, TSStateId, uint32_t);
+
+void ts_stack_iterate(Stack *, StackVersion, StackIterateCallback, void *);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // TREE_SITTER_PARSE_STACK_H_
diff --git a/src/tree_sitter/subtree.c b/src/tree_sitter/subtree.c
new file mode 100644
index 0000000000..ef92a32fe4
--- /dev/null
+++ b/src/tree_sitter/subtree.c
@@ -0,0 +1,982 @@
+#include <assert.h>
+#include <ctype.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <string.h>
+#include <stdio.h>
+#include "./alloc.h"
+#include "./atomic.h"
+#include "./subtree.h"
+#include "./length.h"
+#include "./language.h"
+#include "./error_costs.h"
+#include <stddef.h>
+
+typedef struct {
+ Length start;
+ Length old_end;
+ Length new_end;
+} Edit;
+
+#define TS_MAX_INLINE_TREE_LENGTH UINT8_MAX
+#define TS_MAX_TREE_POOL_SIZE 32
+
+static const ExternalScannerState empty_state = {{.short_data = {0}}, .length = 0};
+
+// ExternalScannerState
+
+void ts_external_scanner_state_init(ExternalScannerState *self, const char *data, unsigned length) {
+ self->length = length;
+ if (length > sizeof(self->short_data)) {
+ self->long_data = ts_malloc(length);
+ memcpy(self->long_data, data, length);
+ } else {
+ memcpy(self->short_data, data, length);
+ }
+}
+
+ExternalScannerState ts_external_scanner_state_copy(const ExternalScannerState *self) {
+ ExternalScannerState result = *self;
+ if (self->length > sizeof(self->short_data)) {
+ result.long_data = ts_malloc(self->length);
+ memcpy(result.long_data, self->long_data, self->length);
+ }
+ return result;
+}
+
+void ts_external_scanner_state_delete(ExternalScannerState *self) {
+ if (self->length > sizeof(self->short_data)) {
+ ts_free(self->long_data);
+ }
+}
+
+const char *ts_external_scanner_state_data(const ExternalScannerState *self) {
+ if (self->length > sizeof(self->short_data)) {
+ return self->long_data;
+ } else {
+ return self->short_data;
+ }
+}
+
+bool ts_external_scanner_state_eq(const ExternalScannerState *a, const ExternalScannerState *b) {
+ return a == b || (
+ a->length == b->length &&
+ !memcmp(ts_external_scanner_state_data(a), ts_external_scanner_state_data(b), a->length)
+ );
+}
+
+// SubtreeArray
+
+void ts_subtree_array_copy(SubtreeArray self, SubtreeArray *dest) {
+ dest->size = self.size;
+ dest->capacity = self.capacity;
+ dest->contents = self.contents;
+ if (self.capacity > 0) {
+ dest->contents = ts_calloc(self.capacity, sizeof(Subtree));
+ memcpy(dest->contents, self.contents, self.size * sizeof(Subtree));
+ for (uint32_t i = 0; i < self.size; i++) {
+ ts_subtree_retain(dest->contents[i]);
+ }
+ }
+}
+
+void ts_subtree_array_delete(SubtreePool *pool, SubtreeArray *self) {
+ for (uint32_t i = 0; i < self->size; i++) {
+ ts_subtree_release(pool, self->contents[i]);
+ }
+ array_delete(self);
+}
+
+SubtreeArray ts_subtree_array_remove_trailing_extras(SubtreeArray *self) {
+ SubtreeArray result = array_new();
+
+ uint32_t i = self->size - 1;
+ for (; i + 1 > 0; i--) {
+ Subtree child = self->contents[i];
+ if (!ts_subtree_extra(child)) break;
+ array_push(&result, child);
+ }
+
+ self->size = i + 1;
+ ts_subtree_array_reverse(&result);
+ return result;
+}
+
+void ts_subtree_array_reverse(SubtreeArray *self) {
+ for (uint32_t i = 0, limit = self->size / 2; i < limit; i++) {
+ size_t reverse_index = self->size - 1 - i;
+ Subtree swap = self->contents[i];
+ self->contents[i] = self->contents[reverse_index];
+ self->contents[reverse_index] = swap;
+ }
+}
+
+// SubtreePool
+
+SubtreePool ts_subtree_pool_new(uint32_t capacity) {
+ SubtreePool self = {array_new(), array_new()};
+ array_reserve(&self.free_trees, capacity);
+ return self;
+}
+
+void ts_subtree_pool_delete(SubtreePool *self) {
+ if (self->free_trees.contents) {
+ for (unsigned i = 0; i < self->free_trees.size; i++) {
+ ts_free(self->free_trees.contents[i].ptr);
+ }
+ array_delete(&self->free_trees);
+ }
+ if (self->tree_stack.contents) array_delete(&self->tree_stack);
+}
+
+static SubtreeHeapData *ts_subtree_pool_allocate(SubtreePool *self) {
+ if (self->free_trees.size > 0) {
+ return array_pop(&self->free_trees).ptr;
+ } else {
+ return ts_malloc(sizeof(SubtreeHeapData));
+ }
+}
+
+static void ts_subtree_pool_free(SubtreePool *self, SubtreeHeapData *tree) {
+ if (self->free_trees.capacity > 0 && self->free_trees.size + 1 <= TS_MAX_TREE_POOL_SIZE) {
+ array_push(&self->free_trees, (MutableSubtree) {.ptr = tree});
+ } else {
+ ts_free(tree);
+ }
+}
+
+// Subtree
+
+static inline bool ts_subtree_can_inline(Length padding, Length size, uint32_t lookahead_bytes) {
+ return
+ padding.bytes < TS_MAX_INLINE_TREE_LENGTH &&
+ padding.extent.row < 16 &&
+ padding.extent.column < TS_MAX_INLINE_TREE_LENGTH &&
+ size.extent.row == 0 &&
+ size.extent.column < TS_MAX_INLINE_TREE_LENGTH &&
+ lookahead_bytes < 16;
+}
+
+Subtree ts_subtree_new_leaf(
+ SubtreePool *pool, TSSymbol symbol, Length padding, Length size,
+ uint32_t lookahead_bytes, TSStateId parse_state, bool has_external_tokens,
+ bool is_keyword, const TSLanguage *language
+) {
+ TSSymbolMetadata metadata = ts_language_symbol_metadata(language, symbol);
+ bool extra = symbol == ts_builtin_sym_end;
+
+ bool is_inline = (
+ symbol <= UINT8_MAX &&
+ !has_external_tokens &&
+ ts_subtree_can_inline(padding, size, lookahead_bytes)
+ );
+
+ if (is_inline) {
+ return (Subtree) {{
+ .parse_state = parse_state,
+ .symbol = symbol,
+ .padding_bytes = padding.bytes,
+ .padding_rows = padding.extent.row,
+ .padding_columns = padding.extent.column,
+ .size_bytes = size.bytes,
+ .lookahead_bytes = lookahead_bytes,
+ .visible = metadata.visible,
+ .named = metadata.named,
+ .extra = extra,
+ .has_changes = false,
+ .is_missing = false,
+ .is_keyword = is_keyword,
+ .is_inline = true,
+ }};
+ } else {
+ SubtreeHeapData *data = ts_subtree_pool_allocate(pool);
+ *data = (SubtreeHeapData) {
+ .ref_count = 1,
+ .padding = padding,
+ .size = size,
+ .lookahead_bytes = lookahead_bytes,
+ .error_cost = 0,
+ .child_count = 0,
+ .symbol = symbol,
+ .parse_state = parse_state,
+ .visible = metadata.visible,
+ .named = metadata.named,
+ .extra = extra,
+ .fragile_left = false,
+ .fragile_right = false,
+ .has_changes = false,
+ .has_external_tokens = has_external_tokens,
+ .is_missing = false,
+ .is_keyword = is_keyword,
+ {{.first_leaf = {.symbol = 0, .parse_state = 0}}}
+ };
+ return (Subtree) {.ptr = data};
+ }
+}
+
+void ts_subtree_set_symbol(
+ MutableSubtree *self,
+ TSSymbol symbol,
+ const TSLanguage *language
+) {
+ TSSymbolMetadata metadata = ts_language_symbol_metadata(language, symbol);
+ if (self->data.is_inline) {
+ assert(symbol < UINT8_MAX);
+ self->data.symbol = symbol;
+ self->data.named = metadata.named;
+ self->data.visible = metadata.visible;
+ } else {
+ self->ptr->symbol = symbol;
+ self->ptr->named = metadata.named;
+ self->ptr->visible = metadata.visible;
+ }
+}
+
+Subtree ts_subtree_new_error(
+ SubtreePool *pool, int32_t lookahead_char, Length padding, Length size,
+ uint32_t bytes_scanned, TSStateId parse_state, const TSLanguage *language
+) {
+ Subtree result = ts_subtree_new_leaf(
+ pool, ts_builtin_sym_error, padding, size, bytes_scanned,
+ parse_state, false, false, language
+ );
+ SubtreeHeapData *data = (SubtreeHeapData *)result.ptr;
+ data->fragile_left = true;
+ data->fragile_right = true;
+ data->lookahead_char = lookahead_char;
+ return result;
+}
+
+MutableSubtree ts_subtree_make_mut(SubtreePool *pool, Subtree self) {
+ if (self.data.is_inline) return (MutableSubtree) {self.data};
+ if (self.ptr->ref_count == 1) return ts_subtree_to_mut_unsafe(self);
+
+ SubtreeHeapData *result = ts_subtree_pool_allocate(pool);
+ memcpy(result, self.ptr, sizeof(SubtreeHeapData));
+ if (result->child_count > 0) {
+ result->children = ts_calloc(self.ptr->child_count, sizeof(Subtree));
+ memcpy(result->children, self.ptr->children, result->child_count * sizeof(Subtree));
+ for (uint32_t i = 0; i < result->child_count; i++) {
+ ts_subtree_retain(result->children[i]);
+ }
+ } else if (result->has_external_tokens) {
+ result->external_scanner_state = ts_external_scanner_state_copy(&self.ptr->external_scanner_state);
+ }
+ result->ref_count = 1;
+ ts_subtree_release(pool, self);
+ return (MutableSubtree) {.ptr = result};
+}
+
+static void ts_subtree__compress(MutableSubtree self, unsigned count, const TSLanguage *language,
+ MutableSubtreeArray *stack) {
+ unsigned initial_stack_size = stack->size;
+
+ MutableSubtree tree = self;
+ TSSymbol symbol = tree.ptr->symbol;
+ for (unsigned i = 0; i < count; i++) {
+ if (tree.ptr->ref_count > 1 || tree.ptr->child_count < 2) break;
+
+ MutableSubtree child = ts_subtree_to_mut_unsafe(tree.ptr->children[0]);
+ if (
+ child.data.is_inline ||
+ child.ptr->child_count < 2 ||
+ child.ptr->ref_count > 1 ||
+ child.ptr->symbol != symbol
+ ) break;
+
+ MutableSubtree grandchild = ts_subtree_to_mut_unsafe(child.ptr->children[0]);
+ if (
+ grandchild.data.is_inline ||
+ grandchild.ptr->child_count < 2 ||
+ grandchild.ptr->ref_count > 1 ||
+ grandchild.ptr->symbol != symbol
+ ) break;
+
+ tree.ptr->children[0] = ts_subtree_from_mut(grandchild);
+ child.ptr->children[0] = grandchild.ptr->children[grandchild.ptr->child_count - 1];
+ grandchild.ptr->children[grandchild.ptr->child_count - 1] = ts_subtree_from_mut(child);
+ array_push(stack, tree);
+ tree = grandchild;
+ }
+
+ while (stack->size > initial_stack_size) {
+ tree = array_pop(stack);
+ MutableSubtree child = ts_subtree_to_mut_unsafe(tree.ptr->children[0]);
+ MutableSubtree grandchild = ts_subtree_to_mut_unsafe(child.ptr->children[child.ptr->child_count - 1]);
+ ts_subtree_set_children(grandchild, grandchild.ptr->children, grandchild.ptr->child_count, language);
+ ts_subtree_set_children(child, child.ptr->children, child.ptr->child_count, language);
+ ts_subtree_set_children(tree, tree.ptr->children, tree.ptr->child_count, language);
+ }
+}
+
+void ts_subtree_balance(Subtree self, SubtreePool *pool, const TSLanguage *language) {
+ array_clear(&pool->tree_stack);
+
+ if (ts_subtree_child_count(self) > 0 && self.ptr->ref_count == 1) {
+ array_push(&pool->tree_stack, ts_subtree_to_mut_unsafe(self));
+ }
+
+ while (pool->tree_stack.size > 0) {
+ MutableSubtree tree = array_pop(&pool->tree_stack);
+
+ if (tree.ptr->repeat_depth > 0) {
+ Subtree child1 = tree.ptr->children[0];
+ Subtree child2 = tree.ptr->children[tree.ptr->child_count - 1];
+ long repeat_delta = (long)ts_subtree_repeat_depth(child1) - (long)ts_subtree_repeat_depth(child2);
+ if (repeat_delta > 0) {
+ unsigned n = repeat_delta;
+ for (unsigned i = n / 2; i > 0; i /= 2) {
+ ts_subtree__compress(tree, i, language, &pool->tree_stack);
+ n -= i;
+ }
+ }
+ }
+
+ for (uint32_t i = 0; i < tree.ptr->child_count; i++) {
+ Subtree child = tree.ptr->children[i];
+ if (ts_subtree_child_count(child) > 0 && child.ptr->ref_count == 1) {
+ array_push(&pool->tree_stack, ts_subtree_to_mut_unsafe(child));
+ }
+ }
+ }
+}
+
+void ts_subtree_set_children(
+ MutableSubtree self, Subtree *children, uint32_t child_count, const TSLanguage *language
+) {
+ assert(!self.data.is_inline);
+
+ if (self.ptr->child_count > 0 && children != self.ptr->children) {
+ ts_free(self.ptr->children);
+ }
+
+ self.ptr->child_count = child_count;
+ self.ptr->children = children;
+ self.ptr->named_child_count = 0;
+ self.ptr->visible_child_count = 0;
+ self.ptr->error_cost = 0;
+ self.ptr->repeat_depth = 0;
+ self.ptr->node_count = 1;
+ self.ptr->has_external_tokens = false;
+ self.ptr->dynamic_precedence = 0;
+
+ uint32_t non_extra_index = 0;
+ const TSSymbol *alias_sequence = ts_language_alias_sequence(language, self.ptr->production_id);
+ uint32_t lookahead_end_byte = 0;
+
+ for (uint32_t i = 0; i < self.ptr->child_count; i++) {
+ Subtree child = self.ptr->children[i];
+
+ if (i == 0) {
+ self.ptr->padding = ts_subtree_padding(child);
+ self.ptr->size = ts_subtree_size(child);
+ } else {
+ self.ptr->size = length_add(self.ptr->size, ts_subtree_total_size(child));
+ }
+
+ uint32_t child_lookahead_end_byte =
+ self.ptr->padding.bytes +
+ self.ptr->size.bytes +
+ ts_subtree_lookahead_bytes(child);
+ if (child_lookahead_end_byte > lookahead_end_byte) lookahead_end_byte = child_lookahead_end_byte;
+
+ if (ts_subtree_symbol(child) != ts_builtin_sym_error_repeat) {
+ self.ptr->error_cost += ts_subtree_error_cost(child);
+ }
+
+ self.ptr->dynamic_precedence += ts_subtree_dynamic_precedence(child);
+ self.ptr->node_count += ts_subtree_node_count(child);
+
+ if (alias_sequence && alias_sequence[non_extra_index] != 0 && !ts_subtree_extra(child)) {
+ self.ptr->visible_child_count++;
+ if (ts_language_symbol_metadata(language, alias_sequence[non_extra_index]).named) {
+ self.ptr->named_child_count++;
+ }
+ } else if (ts_subtree_visible(child)) {
+ self.ptr->visible_child_count++;
+ if (ts_subtree_named(child)) self.ptr->named_child_count++;
+ } else if (ts_subtree_child_count(child) > 0) {
+ self.ptr->visible_child_count += child.ptr->visible_child_count;
+ self.ptr->named_child_count += child.ptr->named_child_count;
+ }
+
+ if (ts_subtree_has_external_tokens(child)) self.ptr->has_external_tokens = true;
+
+ if (ts_subtree_is_error(child)) {
+ self.ptr->fragile_left = self.ptr->fragile_right = true;
+ self.ptr->parse_state = TS_TREE_STATE_NONE;
+ }
+
+ if (!ts_subtree_extra(child)) non_extra_index++;
+ }
+
+ self.ptr->lookahead_bytes = lookahead_end_byte - self.ptr->size.bytes - self.ptr->padding.bytes;
+
+ if (self.ptr->symbol == ts_builtin_sym_error || self.ptr->symbol == ts_builtin_sym_error_repeat) {
+ self.ptr->error_cost +=
+ ERROR_COST_PER_RECOVERY +
+ ERROR_COST_PER_SKIPPED_CHAR * self.ptr->size.bytes +
+ ERROR_COST_PER_SKIPPED_LINE * self.ptr->size.extent.row;
+ for (uint32_t i = 0; i < self.ptr->child_count; i++) {
+ Subtree child = self.ptr->children[i];
+ uint32_t grandchild_count = ts_subtree_child_count(child);
+ if (ts_subtree_extra(child)) continue;
+ if (ts_subtree_is_error(child) && grandchild_count == 0) continue;
+ if (ts_subtree_visible(child)) {
+ self.ptr->error_cost += ERROR_COST_PER_SKIPPED_TREE;
+ } else if (grandchild_count > 0) {
+ self.ptr->error_cost += ERROR_COST_PER_SKIPPED_TREE * child.ptr->visible_child_count;
+ }
+ }
+ }
+
+ if (self.ptr->child_count > 0) {
+ Subtree first_child = self.ptr->children[0];
+ Subtree last_child = self.ptr->children[self.ptr->child_count - 1];
+
+ self.ptr->first_leaf.symbol = ts_subtree_leaf_symbol(first_child);
+ self.ptr->first_leaf.parse_state = ts_subtree_leaf_parse_state(first_child);
+
+ if (ts_subtree_fragile_left(first_child)) self.ptr->fragile_left = true;
+ if (ts_subtree_fragile_right(last_child)) self.ptr->fragile_right = true;
+
+ if (
+ self.ptr->child_count >= 2 &&
+ !self.ptr->visible &&
+ !self.ptr->named &&
+ ts_subtree_symbol(first_child) == self.ptr->symbol
+ ) {
+ if (ts_subtree_repeat_depth(first_child) > ts_subtree_repeat_depth(last_child)) {
+ self.ptr->repeat_depth = ts_subtree_repeat_depth(first_child) + 1;
+ } else {
+ self.ptr->repeat_depth = ts_subtree_repeat_depth(last_child) + 1;
+ }
+ }
+ }
+}
+
+MutableSubtree ts_subtree_new_node(SubtreePool *pool, TSSymbol symbol,
+ SubtreeArray *children, unsigned production_id,
+ const TSLanguage *language) {
+ TSSymbolMetadata metadata = ts_language_symbol_metadata(language, symbol);
+ bool fragile = symbol == ts_builtin_sym_error || symbol == ts_builtin_sym_error_repeat;
+ SubtreeHeapData *data = ts_subtree_pool_allocate(pool);
+ *data = (SubtreeHeapData) {
+ .ref_count = 1,
+ .symbol = symbol,
+ .visible = metadata.visible,
+ .named = metadata.named,
+ .has_changes = false,
+ .fragile_left = fragile,
+ .fragile_right = fragile,
+ .is_keyword = false,
+ {{
+ .node_count = 0,
+ .production_id = production_id,
+ .first_leaf = {.symbol = 0, .parse_state = 0},
+ }}
+ };
+ MutableSubtree result = {.ptr = data};
+ ts_subtree_set_children(result, children->contents, children->size, language);
+ return result;
+}
+
+Subtree ts_subtree_new_error_node(SubtreePool *pool, SubtreeArray *children,
+ bool extra, const TSLanguage *language) {
+ MutableSubtree result = ts_subtree_new_node(
+ pool, ts_builtin_sym_error, children, 0, language
+ );
+ result.ptr->extra = extra;
+ return ts_subtree_from_mut(result);
+}
+
+Subtree ts_subtree_new_missing_leaf(SubtreePool *pool, TSSymbol symbol, Length padding,
+ const TSLanguage *language) {
+ Subtree result = ts_subtree_new_leaf(
+ pool, symbol, padding, length_zero(), 0,
+ 0, false, false, language
+ );
+
+ if (result.data.is_inline) {
+ result.data.is_missing = true;
+ } else {
+ ((SubtreeHeapData *)result.ptr)->is_missing = true;
+ }
+
+ return result;
+}
+
+void ts_subtree_retain(Subtree self) {
+ if (self.data.is_inline) return;
+ assert(self.ptr->ref_count > 0);
+ atomic_inc((volatile uint32_t *)&self.ptr->ref_count);
+ assert(self.ptr->ref_count != 0);
+}
+
+void ts_subtree_release(SubtreePool *pool, Subtree self) {
+ if (self.data.is_inline) return;
+ array_clear(&pool->tree_stack);
+
+ assert(self.ptr->ref_count > 0);
+ if (atomic_dec((volatile uint32_t *)&self.ptr->ref_count) == 0) {
+ array_push(&pool->tree_stack, ts_subtree_to_mut_unsafe(self));
+ }
+
+ while (pool->tree_stack.size > 0) {
+ MutableSubtree tree = array_pop(&pool->tree_stack);
+ if (tree.ptr->child_count > 0) {
+ for (uint32_t i = 0; i < tree.ptr->child_count; i++) {
+ Subtree child = tree.ptr->children[i];
+ if (child.data.is_inline) continue;
+ assert(child.ptr->ref_count > 0);
+ if (atomic_dec((volatile uint32_t *)&child.ptr->ref_count) == 0) {
+ array_push(&pool->tree_stack, ts_subtree_to_mut_unsafe(child));
+ }
+ }
+ ts_free(tree.ptr->children);
+ } else if (tree.ptr->has_external_tokens) {
+ ts_external_scanner_state_delete(&tree.ptr->external_scanner_state);
+ }
+ ts_subtree_pool_free(pool, tree.ptr);
+ }
+}
+
+bool ts_subtree_eq(Subtree self, Subtree other) {
+ if (self.data.is_inline || other.data.is_inline) {
+ return memcmp(&self, &other, sizeof(SubtreeInlineData)) == 0;
+ }
+
+ if (self.ptr) {
+ if (!other.ptr) return false;
+ } else {
+ return !other.ptr;
+ }
+
+ if (self.ptr->symbol != other.ptr->symbol) return false;
+ if (self.ptr->visible != other.ptr->visible) return false;
+ if (self.ptr->named != other.ptr->named) return false;
+ if (self.ptr->padding.bytes != other.ptr->padding.bytes) return false;
+ if (self.ptr->size.bytes != other.ptr->size.bytes) return false;
+ if (self.ptr->symbol == ts_builtin_sym_error) return self.ptr->lookahead_char == other.ptr->lookahead_char;
+ if (self.ptr->child_count != other.ptr->child_count) return false;
+ if (self.ptr->child_count > 0) {
+ if (self.ptr->visible_child_count != other.ptr->visible_child_count) return false;
+ if (self.ptr->named_child_count != other.ptr->named_child_count) return false;
+
+ for (uint32_t i = 0; i < self.ptr->child_count; i++) {
+ if (!ts_subtree_eq(self.ptr->children[i], other.ptr->children[i])) {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+int ts_subtree_compare(Subtree left, Subtree right) {
+ if (ts_subtree_symbol(left) < ts_subtree_symbol(right)) return -1;
+ if (ts_subtree_symbol(right) < ts_subtree_symbol(left)) return 1;
+ if (ts_subtree_child_count(left) < ts_subtree_child_count(right)) return -1;
+ if (ts_subtree_child_count(right) < ts_subtree_child_count(left)) return 1;
+ for (uint32_t i = 0, n = ts_subtree_child_count(left); i < n; i++) {
+ Subtree left_child = left.ptr->children[i];
+ Subtree right_child = right.ptr->children[i];
+ switch (ts_subtree_compare(left_child, right_child)) {
+ case -1: return -1;
+ case 1: return 1;
+ default: break;
+ }
+ }
+ return 0;
+}
+
+static inline void ts_subtree_set_has_changes(MutableSubtree *self) {
+ if (self->data.is_inline) {
+ self->data.has_changes = true;
+ } else {
+ self->ptr->has_changes = true;
+ }
+}
+
+Subtree ts_subtree_edit(Subtree self, const TSInputEdit *edit, SubtreePool *pool) {
+ typedef struct {
+ Subtree *tree;
+ Edit edit;
+ } StackEntry;
+
+ Array(StackEntry) stack = array_new();
+ array_push(&stack, ((StackEntry) {
+ .tree = &self,
+ .edit = (Edit) {
+ .start = {edit->start_byte, edit->start_point},
+ .old_end = {edit->old_end_byte, edit->old_end_point},
+ .new_end = {edit->new_end_byte, edit->new_end_point},
+ },
+ }));
+
+ while (stack.size) {
+ StackEntry entry = array_pop(&stack);
+ Edit edit = entry.edit;
+ bool is_noop = edit.old_end.bytes == edit.start.bytes && edit.new_end.bytes == edit.start.bytes;
+ bool is_pure_insertion = edit.old_end.bytes == edit.start.bytes;
+
+ Length size = ts_subtree_size(*entry.tree);
+ Length padding = ts_subtree_padding(*entry.tree);
+ uint32_t lookahead_bytes = ts_subtree_lookahead_bytes(*entry.tree);
+ uint32_t end_byte = padding.bytes + size.bytes + lookahead_bytes;
+ if (edit.start.bytes > end_byte || (is_noop && edit.start.bytes == end_byte)) continue;
+
+ // If the edit is entirely within the space before this subtree, then shift this
+ // subtree over according to the edit without changing its size.
+ if (edit.old_end.bytes <= padding.bytes) {
+ padding = length_add(edit.new_end, length_sub(padding, edit.old_end));
+ }
+
+ // If the edit starts in the space before this subtree and extends into this subtree,
+ // shrink the subtree's content to compensate for the change in the space before it.
+ else if (edit.start.bytes < padding.bytes) {
+ size = length_sub(size, length_sub(edit.old_end, padding));
+ padding = edit.new_end;
+ }
+
+ // If the edit is a pure insertion right at the start of the subtree,
+ // shift the subtree over according to the insertion.
+ else if (edit.start.bytes == padding.bytes && is_pure_insertion) {
+ padding = edit.new_end;
+ }
+
+ // If the edit is within this subtree, resize the subtree to reflect the edit.
+ else {
+ uint32_t total_bytes = padding.bytes + size.bytes;
+ if (edit.start.bytes < total_bytes ||
+ (edit.start.bytes == total_bytes && is_pure_insertion)) {
+ size = length_add(
+ length_sub(edit.new_end, padding),
+ length_sub(size, length_sub(edit.old_end, padding))
+ );
+ }
+ }
+
+ MutableSubtree result = ts_subtree_make_mut(pool, *entry.tree);
+
+ if (result.data.is_inline) {
+ if (ts_subtree_can_inline(padding, size, lookahead_bytes)) {
+ result.data.padding_bytes = padding.bytes;
+ result.data.padding_rows = padding.extent.row;
+ result.data.padding_columns = padding.extent.column;
+ result.data.size_bytes = size.bytes;
+ } else {
+ SubtreeHeapData *data = ts_subtree_pool_allocate(pool);
+ data->ref_count = 1;
+ data->padding = padding;
+ data->size = size;
+ data->lookahead_bytes = lookahead_bytes;
+ data->error_cost = 0;
+ data->child_count = 0;
+ data->symbol = result.data.symbol;
+ data->parse_state = result.data.parse_state;
+ data->visible = result.data.visible;
+ data->named = result.data.named;
+ data->extra = result.data.extra;
+ data->fragile_left = false;
+ data->fragile_right = false;
+ data->has_changes = false;
+ data->has_external_tokens = false;
+ data->is_missing = result.data.is_missing;
+ data->is_keyword = result.data.is_keyword;
+ result.ptr = data;
+ }
+ } else {
+ result.ptr->padding = padding;
+ result.ptr->size = size;
+ }
+
+ ts_subtree_set_has_changes(&result);
+ *entry.tree = ts_subtree_from_mut(result);
+
+ Length child_left, child_right = length_zero();
+ for (uint32_t i = 0, n = ts_subtree_child_count(*entry.tree); i < n; i++) {
+ Subtree *child = &result.ptr->children[i];
+ Length child_size = ts_subtree_total_size(*child);
+ child_left = child_right;
+ child_right = length_add(child_left, child_size);
+
+ // If this child ends before the edit, it is not affected.
+ if (child_right.bytes + ts_subtree_lookahead_bytes(*child) < edit.start.bytes) continue;
+
+ // If this child starts after the edit, then we're done processing children.
+ if (child_left.bytes > edit.old_end.bytes ||
+ (child_left.bytes == edit.old_end.bytes && child_size.bytes > 0 && i > 0)) break;
+
+ // Transform edit into the child's coordinate space.
+ Edit child_edit = {
+ .start = length_sub(edit.start, child_left),
+ .old_end = length_sub(edit.old_end, child_left),
+ .new_end = length_sub(edit.new_end, child_left),
+ };
+
+ // Clamp child_edit to the child's bounds.
+ if (edit.start.bytes < child_left.bytes) child_edit.start = length_zero();
+ if (edit.old_end.bytes < child_left.bytes) child_edit.old_end = length_zero();
+ if (edit.new_end.bytes < child_left.bytes) child_edit.new_end = length_zero();
+ if (edit.old_end.bytes > child_right.bytes) child_edit.old_end = child_size;
+
+ // Interpret all inserted text as applying to the *first* child that touches the edit.
+ // Subsequent children are only never have any text inserted into them; they are only
+ // shrunk to compensate for the edit.
+ if (child_right.bytes > edit.start.bytes ||
+ (child_right.bytes == edit.start.bytes && is_pure_insertion)) {
+ edit.new_end = edit.start;
+ }
+
+ // Children that occur before the edit are not reshaped by the edit.
+ else {
+ child_edit.old_end = child_edit.start;
+ child_edit.new_end = child_edit.start;
+ }
+
+ // Queue processing of this child's subtree.
+ array_push(&stack, ((StackEntry) {
+ .tree = child,
+ .edit = child_edit,
+ }));
+ }
+ }
+
+ array_delete(&stack);
+ return self;
+}
+
+Subtree ts_subtree_last_external_token(Subtree tree) {
+ if (!ts_subtree_has_external_tokens(tree)) return NULL_SUBTREE;
+ while (tree.ptr->child_count > 0) {
+ for (uint32_t i = tree.ptr->child_count - 1; i + 1 > 0; i--) {
+ Subtree child = tree.ptr->children[i];
+ if (ts_subtree_has_external_tokens(child)) {
+ tree = child;
+ break;
+ }
+ }
+ }
+ return tree;
+}
+
+static size_t ts_subtree__write_char_to_string(char *s, size_t n, int32_t c) {
+ if (c == -1)
+ return snprintf(s, n, "INVALID");
+ else if (c == '\0')
+ return snprintf(s, n, "'\\0'");
+ else if (c == '\n')
+ return snprintf(s, n, "'\\n'");
+ else if (c == '\t')
+ return snprintf(s, n, "'\\t'");
+ else if (c == '\r')
+ return snprintf(s, n, "'\\r'");
+ else if (0 < c && c < 128 && isprint(c))
+ return snprintf(s, n, "'%c'", c);
+ else
+ return snprintf(s, n, "%d", c);
+}
+
+static void ts_subtree__write_dot_string(FILE *f, const char *string) {
+ for (const char *c = string; *c; c++) {
+ if (*c == '"') {
+ fputs("\\\"", f);
+ } else if (*c == '\n') {
+ fputs("\\n", f);
+ } else {
+ fputc(*c, f);
+ }
+ }
+}
+
+static const char *ROOT_FIELD = "__ROOT__";
+
+static size_t ts_subtree__write_to_string(
+ Subtree self, char *string, size_t limit,
+ const TSLanguage *language, bool include_all,
+ TSSymbol alias_symbol, bool alias_is_named, const char *field_name
+) {
+ if (!self.ptr) return snprintf(string, limit, "(NULL)");
+
+ char *cursor = string;
+ char **writer = (limit > 0) ? &cursor : &string;
+ bool is_root = field_name == ROOT_FIELD;
+ bool is_visible =
+ include_all ||
+ ts_subtree_missing(self) ||
+ (
+ alias_symbol
+ ? alias_is_named
+ : ts_subtree_visible(self) && ts_subtree_named(self)
+ );
+
+ if (is_visible) {
+ if (!is_root) {
+ cursor += snprintf(*writer, limit, " ");
+ if (field_name) {
+ cursor += snprintf(*writer, limit, "%s: ", field_name);
+ }
+ }
+
+ if (ts_subtree_is_error(self) && ts_subtree_child_count(self) == 0 && self.ptr->size.bytes > 0) {
+ cursor += snprintf(*writer, limit, "(UNEXPECTED ");
+ cursor += ts_subtree__write_char_to_string(*writer, limit, self.ptr->lookahead_char);
+ } else {
+ TSSymbol symbol = alias_symbol ? alias_symbol : ts_subtree_symbol(self);
+ const char *symbol_name = ts_language_symbol_name(language, symbol);
+ if (ts_subtree_missing(self)) {
+ cursor += snprintf(*writer, limit, "(MISSING ");
+ if (alias_is_named || ts_subtree_named(self)) {
+ cursor += snprintf(*writer, limit, "%s", symbol_name);
+ } else {
+ cursor += snprintf(*writer, limit, "\"%s\"", symbol_name);
+ }
+ } else {
+ cursor += snprintf(*writer, limit, "(%s", symbol_name);
+ }
+ }
+ } else if (is_root) {
+ TSSymbol symbol = ts_subtree_symbol(self);
+ const char *symbol_name = ts_language_symbol_name(language, symbol);
+ cursor += snprintf(*writer, limit, "(\"%s\")", symbol_name);
+ }
+
+ if (ts_subtree_child_count(self)) {
+ const TSSymbol *alias_sequence = ts_language_alias_sequence(language, self.ptr->production_id);
+ const TSFieldMapEntry *field_map, *field_map_end;
+ ts_language_field_map(
+ language,
+ self.ptr->production_id,
+ &field_map,
+ &field_map_end
+ );
+
+ uint32_t structural_child_index = 0;
+ for (uint32_t i = 0; i < self.ptr->child_count; i++) {
+ Subtree child = self.ptr->children[i];
+ if (ts_subtree_extra(child)) {
+ cursor += ts_subtree__write_to_string(
+ child, *writer, limit,
+ language, include_all,
+ 0, false, NULL
+ );
+ } else {
+ TSSymbol alias_symbol = alias_sequence
+ ? alias_sequence[structural_child_index]
+ : 0;
+ bool alias_is_named = alias_symbol
+ ? ts_language_symbol_metadata(language, alias_symbol).named
+ : false;
+
+ const char *child_field_name = is_visible ? NULL : field_name;
+ for (const TSFieldMapEntry *i = field_map; i < field_map_end; i++) {
+ if (!i->inherited && i->child_index == structural_child_index) {
+ child_field_name = language->field_names[i->field_id];
+ break;
+ }
+ }
+
+ cursor += ts_subtree__write_to_string(
+ child, *writer, limit,
+ language, include_all,
+ alias_symbol, alias_is_named, child_field_name
+ );
+ structural_child_index++;
+ }
+ }
+ }
+
+ if (is_visible) cursor += snprintf(*writer, limit, ")");
+
+ return cursor - string;
+}
+
+char *ts_subtree_string(
+ Subtree self,
+ const TSLanguage *language,
+ bool include_all
+) {
+ char scratch_string[1];
+ size_t size = ts_subtree__write_to_string(
+ self, scratch_string, 0,
+ language, include_all,
+ 0, false, ROOT_FIELD
+ ) + 1;
+ char *result = malloc(size * sizeof(char));
+ ts_subtree__write_to_string(
+ self, result, size,
+ language, include_all,
+ 0, false, ROOT_FIELD
+ );
+ return result;
+}
+
+void ts_subtree__print_dot_graph(const Subtree *self, uint32_t start_offset,
+ const TSLanguage *language, TSSymbol alias_symbol,
+ FILE *f) {
+ TSSymbol subtree_symbol = ts_subtree_symbol(*self);
+ TSSymbol symbol = alias_symbol ? alias_symbol : subtree_symbol;
+ uint32_t end_offset = start_offset + ts_subtree_total_bytes(*self);
+ fprintf(f, "tree_%p [label=\"", self);
+ ts_subtree__write_dot_string(f, ts_language_symbol_name(language, symbol));
+ fprintf(f, "\"");
+
+ if (ts_subtree_child_count(*self) == 0) fprintf(f, ", shape=plaintext");
+ if (ts_subtree_extra(*self)) fprintf(f, ", fontcolor=gray");
+
+ fprintf(f, ", tooltip=\""
+ "range: %u - %u\n"
+ "state: %d\n"
+ "error-cost: %u\n"
+ "has-changes: %u\n"
+ "repeat-depth: %u\n"
+ "lookahead-bytes: %u",
+ start_offset, end_offset,
+ ts_subtree_parse_state(*self),
+ ts_subtree_error_cost(*self),
+ ts_subtree_has_changes(*self),
+ ts_subtree_repeat_depth(*self),
+ ts_subtree_lookahead_bytes(*self)
+ );
+
+ if (ts_subtree_is_error(*self) && ts_subtree_child_count(*self) == 0) {
+ fprintf(f, "\ncharacter: '%c'", self->ptr->lookahead_char);
+ }
+
+ fprintf(f, "\"]\n");
+
+ uint32_t child_start_offset = start_offset;
+ uint32_t child_info_offset =
+ language->max_alias_sequence_length *
+ ts_subtree_production_id(*self);
+ for (uint32_t i = 0, n = ts_subtree_child_count(*self); i < n; i++) {
+ const Subtree *child = &self->ptr->children[i];
+ TSSymbol alias_symbol = 0;
+ if (!ts_subtree_extra(*child) && child_info_offset) {
+ alias_symbol = language->alias_sequences[child_info_offset];
+ child_info_offset++;
+ }
+ ts_subtree__print_dot_graph(child, child_start_offset, language, alias_symbol, f);
+ fprintf(f, "tree_%p -> tree_%p [tooltip=%u]\n", self, child, i);
+ child_start_offset += ts_subtree_total_bytes(*child);
+ }
+}
+
+void ts_subtree_print_dot_graph(Subtree self, const TSLanguage *language, FILE *f) {
+ fprintf(f, "digraph tree {\n");
+ fprintf(f, "edge [arrowhead=none]\n");
+ ts_subtree__print_dot_graph(&self, 0, language, 0, f);
+ fprintf(f, "}\n");
+}
+
+bool ts_subtree_external_scanner_state_eq(Subtree self, Subtree other) {
+ const ExternalScannerState *state1 = &empty_state;
+ const ExternalScannerState *state2 = &empty_state;
+ if (self.ptr && ts_subtree_has_external_tokens(self) && !self.ptr->child_count) {
+ state1 = &self.ptr->external_scanner_state;
+ }
+ if (other.ptr && ts_subtree_has_external_tokens(other) && !other.ptr->child_count) {
+ state2 = &other.ptr->external_scanner_state;
+ }
+ return ts_external_scanner_state_eq(state1, state2);
+}
diff --git a/src/tree_sitter/subtree.h b/src/tree_sitter/subtree.h
new file mode 100644
index 0000000000..18c48dcbd0
--- /dev/null
+++ b/src/tree_sitter/subtree.h
@@ -0,0 +1,285 @@
+#ifndef TREE_SITTER_SUBTREE_H_
+#define TREE_SITTER_SUBTREE_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <limits.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include "./length.h"
+#include "./array.h"
+#include "./error_costs.h"
+#include "tree_sitter/api.h"
+#include "tree_sitter/parser.h"
+
+static const TSStateId TS_TREE_STATE_NONE = USHRT_MAX;
+#define NULL_SUBTREE ((Subtree) {.ptr = NULL})
+
+typedef union Subtree Subtree;
+typedef union MutableSubtree MutableSubtree;
+
+typedef struct {
+ union {
+ char *long_data;
+ char short_data[24];
+ };
+ uint32_t length;
+} ExternalScannerState;
+
+typedef struct {
+ bool is_inline : 1;
+ bool visible : 1;
+ bool named : 1;
+ bool extra : 1;
+ bool has_changes : 1;
+ bool is_missing : 1;
+ bool is_keyword : 1;
+ uint8_t symbol;
+ uint8_t padding_bytes;
+ uint8_t size_bytes;
+ uint8_t padding_columns;
+ uint8_t padding_rows : 4;
+ uint8_t lookahead_bytes : 4;
+ uint16_t parse_state;
+} SubtreeInlineData;
+
+typedef struct {
+ volatile uint32_t ref_count;
+ Length padding;
+ Length size;
+ uint32_t lookahead_bytes;
+ uint32_t error_cost;
+ uint32_t child_count;
+ TSSymbol symbol;
+ TSStateId parse_state;
+
+ bool visible : 1;
+ bool named : 1;
+ bool extra : 1;
+ bool fragile_left : 1;
+ bool fragile_right : 1;
+ bool has_changes : 1;
+ bool has_external_tokens : 1;
+ bool is_missing : 1;
+ bool is_keyword : 1;
+
+ union {
+ // Non-terminal subtrees (`child_count > 0`)
+ struct {
+ Subtree *children;
+ uint32_t visible_child_count;
+ uint32_t named_child_count;
+ uint32_t node_count;
+ uint32_t repeat_depth;
+ int32_t dynamic_precedence;
+ uint16_t production_id;
+ struct {
+ TSSymbol symbol;
+ TSStateId parse_state;
+ } first_leaf;
+ };
+
+ // External terminal subtrees (`child_count == 0 && has_external_tokens`)
+ ExternalScannerState external_scanner_state;
+
+ // Error terminal subtrees (`child_count == 0 && symbol == ts_builtin_sym_error`)
+ int32_t lookahead_char;
+ };
+} SubtreeHeapData;
+
+union Subtree {
+ SubtreeInlineData data;
+ const SubtreeHeapData *ptr;
+};
+
+union MutableSubtree {
+ SubtreeInlineData data;
+ SubtreeHeapData *ptr;
+};
+
+typedef Array(Subtree) SubtreeArray;
+typedef Array(MutableSubtree) MutableSubtreeArray;
+
+typedef struct {
+ MutableSubtreeArray free_trees;
+ MutableSubtreeArray tree_stack;
+} SubtreePool;
+
+void ts_external_scanner_state_init(ExternalScannerState *, const char *, unsigned);
+const char *ts_external_scanner_state_data(const ExternalScannerState *);
+
+void ts_subtree_array_copy(SubtreeArray, SubtreeArray *);
+void ts_subtree_array_delete(SubtreePool *, SubtreeArray *);
+SubtreeArray ts_subtree_array_remove_trailing_extras(SubtreeArray *);
+void ts_subtree_array_reverse(SubtreeArray *);
+
+SubtreePool ts_subtree_pool_new(uint32_t capacity);
+void ts_subtree_pool_delete(SubtreePool *);
+
+Subtree ts_subtree_new_leaf(
+ SubtreePool *, TSSymbol, Length, Length, uint32_t,
+ TSStateId, bool, bool, const TSLanguage *
+);
+Subtree ts_subtree_new_error(
+ SubtreePool *, int32_t, Length, Length, uint32_t, TSStateId, const TSLanguage *
+);
+MutableSubtree ts_subtree_new_node(SubtreePool *, TSSymbol, SubtreeArray *, unsigned, const TSLanguage *);
+Subtree ts_subtree_new_error_node(SubtreePool *, SubtreeArray *, bool, const TSLanguage *);
+Subtree ts_subtree_new_missing_leaf(SubtreePool *, TSSymbol, Length, const TSLanguage *);
+MutableSubtree ts_subtree_make_mut(SubtreePool *, Subtree);
+void ts_subtree_retain(Subtree);
+void ts_subtree_release(SubtreePool *, Subtree);
+bool ts_subtree_eq(Subtree, Subtree);
+int ts_subtree_compare(Subtree, Subtree);
+void ts_subtree_set_symbol(MutableSubtree *, TSSymbol, const TSLanguage *);
+void ts_subtree_set_children(MutableSubtree, Subtree *, uint32_t, const TSLanguage *);
+void ts_subtree_balance(Subtree, SubtreePool *, const TSLanguage *);
+Subtree ts_subtree_edit(Subtree, const TSInputEdit *edit, SubtreePool *);
+char *ts_subtree_string(Subtree, const TSLanguage *, bool include_all);
+void ts_subtree_print_dot_graph(Subtree, const TSLanguage *, FILE *);
+Subtree ts_subtree_last_external_token(Subtree);
+bool ts_subtree_external_scanner_state_eq(Subtree, Subtree);
+
+#define SUBTREE_GET(self, name) (self.data.is_inline ? self.data.name : self.ptr->name)
+
+static inline TSSymbol ts_subtree_symbol(Subtree self) { return SUBTREE_GET(self, symbol); }
+static inline bool ts_subtree_visible(Subtree self) { return SUBTREE_GET(self, visible); }
+static inline bool ts_subtree_named(Subtree self) { return SUBTREE_GET(self, named); }
+static inline bool ts_subtree_extra(Subtree self) { return SUBTREE_GET(self, extra); }
+static inline bool ts_subtree_has_changes(Subtree self) { return SUBTREE_GET(self, has_changes); }
+static inline bool ts_subtree_missing(Subtree self) { return SUBTREE_GET(self, is_missing); }
+static inline bool ts_subtree_is_keyword(Subtree self) { return SUBTREE_GET(self, is_keyword); }
+static inline TSStateId ts_subtree_parse_state(Subtree self) { return SUBTREE_GET(self, parse_state); }
+static inline uint32_t ts_subtree_lookahead_bytes(Subtree self) { return SUBTREE_GET(self, lookahead_bytes); }
+
+#undef SUBTREE_GET
+
+static inline void ts_subtree_set_extra(MutableSubtree *self) {
+ if (self->data.is_inline) {
+ self->data.extra = true;
+ } else {
+ self->ptr->extra = true;
+ }
+}
+
+static inline TSSymbol ts_subtree_leaf_symbol(Subtree self) {
+ if (self.data.is_inline) return self.data.symbol;
+ if (self.ptr->child_count == 0) return self.ptr->symbol;
+ return self.ptr->first_leaf.symbol;
+}
+
+static inline TSStateId ts_subtree_leaf_parse_state(Subtree self) {
+ if (self.data.is_inline) return self.data.parse_state;
+ if (self.ptr->child_count == 0) return self.ptr->parse_state;
+ return self.ptr->first_leaf.parse_state;
+}
+
+static inline Length ts_subtree_padding(Subtree self) {
+ if (self.data.is_inline) {
+ Length result = {self.data.padding_bytes, {self.data.padding_rows, self.data.padding_columns}};
+ return result;
+ } else {
+ return self.ptr->padding;
+ }
+}
+
+static inline Length ts_subtree_size(Subtree self) {
+ if (self.data.is_inline) {
+ Length result = {self.data.size_bytes, {0, self.data.size_bytes}};
+ return result;
+ } else {
+ return self.ptr->size;
+ }
+}
+
+static inline Length ts_subtree_total_size(Subtree self) {
+ return length_add(ts_subtree_padding(self), ts_subtree_size(self));
+}
+
+static inline uint32_t ts_subtree_total_bytes(Subtree self) {
+ return ts_subtree_total_size(self).bytes;
+}
+
+static inline uint32_t ts_subtree_child_count(Subtree self) {
+ return self.data.is_inline ? 0 : self.ptr->child_count;
+}
+
+static inline uint32_t ts_subtree_repeat_depth(Subtree self) {
+ return self.data.is_inline ? 0 : self.ptr->repeat_depth;
+}
+
+static inline uint32_t ts_subtree_node_count(Subtree self) {
+ return (self.data.is_inline || self.ptr->child_count == 0) ? 1 : self.ptr->node_count;
+}
+
+static inline uint32_t ts_subtree_visible_child_count(Subtree self) {
+ if (ts_subtree_child_count(self) > 0) {
+ return self.ptr->visible_child_count;
+ } else {
+ return 0;
+ }
+}
+
+static inline uint32_t ts_subtree_error_cost(Subtree self) {
+ if (ts_subtree_missing(self)) {
+ return ERROR_COST_PER_MISSING_TREE + ERROR_COST_PER_RECOVERY;
+ } else {
+ return self.data.is_inline ? 0 : self.ptr->error_cost;
+ }
+}
+
+static inline int32_t ts_subtree_dynamic_precedence(Subtree self) {
+ return (self.data.is_inline || self.ptr->child_count == 0) ? 0 : self.ptr->dynamic_precedence;
+}
+
+static inline uint16_t ts_subtree_production_id(Subtree self) {
+ if (ts_subtree_child_count(self) > 0) {
+ return self.ptr->production_id;
+ } else {
+ return 0;
+ }
+}
+
+static inline bool ts_subtree_fragile_left(Subtree self) {
+ return self.data.is_inline ? false : self.ptr->fragile_left;
+}
+
+static inline bool ts_subtree_fragile_right(Subtree self) {
+ return self.data.is_inline ? false : self.ptr->fragile_right;
+}
+
+static inline bool ts_subtree_has_external_tokens(Subtree self) {
+ return self.data.is_inline ? false : self.ptr->has_external_tokens;
+}
+
+static inline bool ts_subtree_is_fragile(Subtree self) {
+ return self.data.is_inline ? false : (self.ptr->fragile_left || self.ptr->fragile_right);
+}
+
+static inline bool ts_subtree_is_error(Subtree self) {
+ return ts_subtree_symbol(self) == ts_builtin_sym_error;
+}
+
+static inline bool ts_subtree_is_eof(Subtree self) {
+ return ts_subtree_symbol(self) == ts_builtin_sym_end;
+}
+
+static inline Subtree ts_subtree_from_mut(MutableSubtree self) {
+ Subtree result;
+ result.data = self.data;
+ return result;
+}
+
+static inline MutableSubtree ts_subtree_to_mut_unsafe(Subtree self) {
+ MutableSubtree result;
+ result.data = self.data;
+ return result;
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // TREE_SITTER_SUBTREE_H_
diff --git a/src/tree_sitter/tree.c b/src/tree_sitter/tree.c
new file mode 100644
index 0000000000..391fa7f592
--- /dev/null
+++ b/src/tree_sitter/tree.c
@@ -0,0 +1,148 @@
+#include "tree_sitter/api.h"
+#include "./array.h"
+#include "./get_changed_ranges.h"
+#include "./subtree.h"
+#include "./tree_cursor.h"
+#include "./tree.h"
+
+static const unsigned PARENT_CACHE_CAPACITY = 32;
+
+TSTree *ts_tree_new(
+ Subtree root, const TSLanguage *language,
+ const TSRange *included_ranges, unsigned included_range_count
+) {
+ TSTree *result = ts_malloc(sizeof(TSTree));
+ result->root = root;
+ result->language = language;
+ result->parent_cache = NULL;
+ result->parent_cache_start = 0;
+ result->parent_cache_size = 0;
+ result->included_ranges = ts_calloc(included_range_count, sizeof(TSRange));
+ memcpy(result->included_ranges, included_ranges, included_range_count * sizeof(TSRange));
+ result->included_range_count = included_range_count;
+ return result;
+}
+
+TSTree *ts_tree_copy(const TSTree *self) {
+ ts_subtree_retain(self->root);
+ return ts_tree_new(self->root, self->language, self->included_ranges, self->included_range_count);
+}
+
+void ts_tree_delete(TSTree *self) {
+ if (!self) return;
+
+ SubtreePool pool = ts_subtree_pool_new(0);
+ ts_subtree_release(&pool, self->root);
+ ts_subtree_pool_delete(&pool);
+ ts_free(self->included_ranges);
+ if (self->parent_cache) ts_free(self->parent_cache);
+ ts_free(self);
+}
+
+TSNode ts_tree_root_node(const TSTree *self) {
+ return ts_node_new(self, &self->root, ts_subtree_padding(self->root), 0);
+}
+
+const TSLanguage *ts_tree_language(const TSTree *self) {
+ return self->language;
+}
+
+void ts_tree_edit(TSTree *self, const TSInputEdit *edit) {
+ for (unsigned i = 0; i < self->included_range_count; i++) {
+ TSRange *range = &self->included_ranges[i];
+ if (range->end_byte >= edit->old_end_byte) {
+ if (range->end_byte != UINT32_MAX) {
+ range->end_byte = edit->new_end_byte + (range->end_byte - edit->old_end_byte);
+ range->end_point = point_add(
+ edit->new_end_point,
+ point_sub(range->end_point, edit->old_end_point)
+ );
+ if (range->end_byte < edit->new_end_byte) {
+ range->end_byte = UINT32_MAX;
+ range->end_point = POINT_MAX;
+ }
+ }
+ if (range->start_byte >= edit->old_end_byte) {
+ range->start_byte = edit->new_end_byte + (range->start_byte - edit->old_end_byte);
+ range->start_point = point_add(
+ edit->new_end_point,
+ point_sub(range->start_point, edit->old_end_point)
+ );
+ if (range->start_byte < edit->new_end_byte) {
+ range->start_byte = UINT32_MAX;
+ range->start_point = POINT_MAX;
+ }
+ }
+ }
+ }
+
+ SubtreePool pool = ts_subtree_pool_new(0);
+ self->root = ts_subtree_edit(self->root, edit, &pool);
+ self->parent_cache_start = 0;
+ self->parent_cache_size = 0;
+ ts_subtree_pool_delete(&pool);
+}
+
+TSRange *ts_tree_get_changed_ranges(const TSTree *self, const TSTree *other, uint32_t *count) {
+ TreeCursor cursor1 = {NULL, array_new()};
+ TreeCursor cursor2 = {NULL, array_new()};
+ ts_tree_cursor_init(&cursor1, ts_tree_root_node(self));
+ ts_tree_cursor_init(&cursor2, ts_tree_root_node(other));
+
+ TSRangeArray included_range_differences = array_new();
+ ts_range_array_get_changed_ranges(
+ self->included_ranges, self->included_range_count,
+ other->included_ranges, other->included_range_count,
+ &included_range_differences
+ );
+
+ TSRange *result;
+ *count = ts_subtree_get_changed_ranges(
+ &self->root, &other->root, &cursor1, &cursor2,
+ self->language, &included_range_differences, &result
+ );
+
+ array_delete(&included_range_differences);
+ array_delete(&cursor1.stack);
+ array_delete(&cursor2.stack);
+ return result;
+}
+
+void ts_tree_print_dot_graph(const TSTree *self, FILE *file) {
+ ts_subtree_print_dot_graph(self->root, self->language, file);
+}
+
+TSNode ts_tree_get_cached_parent(const TSTree *self, const TSNode *node) {
+ for (uint32_t i = 0; i < self->parent_cache_size; i++) {
+ uint32_t index = (self->parent_cache_start + i) % PARENT_CACHE_CAPACITY;
+ ParentCacheEntry *entry = &self->parent_cache[index];
+ if (entry->child == node->id) {
+ return ts_node_new(self, entry->parent, entry->position, entry->alias_symbol);
+ }
+ }
+ return ts_node_new(NULL, NULL, length_zero(), 0);
+}
+
+void ts_tree_set_cached_parent(const TSTree *_self, const TSNode *node, const TSNode *parent) {
+ TSTree *self = (TSTree *)_self;
+ if (!self->parent_cache) {
+ self->parent_cache = ts_calloc(PARENT_CACHE_CAPACITY, sizeof(ParentCacheEntry));
+ }
+
+ uint32_t index = (self->parent_cache_start + self->parent_cache_size) % PARENT_CACHE_CAPACITY;
+ self->parent_cache[index] = (ParentCacheEntry) {
+ .child = node->id,
+ .parent = (const Subtree *)parent->id,
+ .position = {
+ parent->context[0],
+ {parent->context[1], parent->context[2]}
+ },
+ .alias_symbol = parent->context[3],
+ };
+
+ if (self->parent_cache_size == PARENT_CACHE_CAPACITY) {
+ self->parent_cache_start++;
+ } else {
+ self->parent_cache_size++;
+ }
+}
diff --git a/src/tree_sitter/tree.h b/src/tree_sitter/tree.h
new file mode 100644
index 0000000000..92a7e64179
--- /dev/null
+++ b/src/tree_sitter/tree.h
@@ -0,0 +1,34 @@
+#ifndef TREE_SITTER_TREE_H_
+#define TREE_SITTER_TREE_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct {
+ const Subtree *child;
+ const Subtree *parent;
+ Length position;
+ TSSymbol alias_symbol;
+} ParentCacheEntry;
+
+struct TSTree {
+ Subtree root;
+ const TSLanguage *language;
+ ParentCacheEntry *parent_cache;
+ uint32_t parent_cache_start;
+ uint32_t parent_cache_size;
+ TSRange *included_ranges;
+ unsigned included_range_count;
+};
+
+TSTree *ts_tree_new(Subtree root, const TSLanguage *language, const TSRange *, unsigned);
+TSNode ts_node_new(const TSTree *, const Subtree *, Length, TSSymbol);
+TSNode ts_tree_get_cached_parent(const TSTree *, const TSNode *);
+void ts_tree_set_cached_parent(const TSTree *, const TSNode *, const TSNode *);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // TREE_SITTER_TREE_H_
diff --git a/src/tree_sitter/tree_cursor.c b/src/tree_sitter/tree_cursor.c
new file mode 100644
index 0000000000..00b9679d73
--- /dev/null
+++ b/src/tree_sitter/tree_cursor.c
@@ -0,0 +1,367 @@
+#include "tree_sitter/api.h"
+#include "./alloc.h"
+#include "./tree_cursor.h"
+#include "./language.h"
+#include "./tree.h"
+
+typedef struct {
+ Subtree parent;
+ const TSTree *tree;
+ Length position;
+ uint32_t child_index;
+ uint32_t structural_child_index;
+ const TSSymbol *alias_sequence;
+} CursorChildIterator;
+
+// CursorChildIterator
+
+static inline CursorChildIterator ts_tree_cursor_iterate_children(const TreeCursor *self) {
+ TreeCursorEntry *last_entry = array_back(&self->stack);
+ if (ts_subtree_child_count(*last_entry->subtree) == 0) {
+ return (CursorChildIterator) {NULL_SUBTREE, self->tree, length_zero(), 0, 0, NULL};
+ }
+ const TSSymbol *alias_sequence = ts_language_alias_sequence(
+ self->tree->language,
+ last_entry->subtree->ptr->production_id
+ );
+ return (CursorChildIterator) {
+ .tree = self->tree,
+ .parent = *last_entry->subtree,
+ .position = last_entry->position,
+ .child_index = 0,
+ .structural_child_index = 0,
+ .alias_sequence = alias_sequence,
+ };
+}
+
+static inline bool ts_tree_cursor_child_iterator_next(CursorChildIterator *self,
+ TreeCursorEntry *result,
+ bool *visible) {
+ if (!self->parent.ptr || self->child_index == self->parent.ptr->child_count) return false;
+ const Subtree *child = &self->parent.ptr->children[self->child_index];
+ *result = (TreeCursorEntry) {
+ .subtree = child,
+ .position = self->position,
+ .child_index = self->child_index,
+ .structural_child_index = self->structural_child_index,
+ };
+ *visible = ts_subtree_visible(*child);
+ bool extra = ts_subtree_extra(*child);
+ if (!extra && self->alias_sequence) {
+ *visible |= self->alias_sequence[self->structural_child_index];
+ self->structural_child_index++;
+ }
+
+ self->position = length_add(self->position, ts_subtree_size(*child));
+ self->child_index++;
+
+ if (self->child_index < self->parent.ptr->child_count) {
+ Subtree next_child = self->parent.ptr->children[self->child_index];
+ self->position = length_add(self->position, ts_subtree_padding(next_child));
+ }
+
+ return true;
+}
+
+// TSTreeCursor - lifecycle
+
+TSTreeCursor ts_tree_cursor_new(TSNode node) {
+ TSTreeCursor self = {NULL, NULL, {0, 0}};
+ ts_tree_cursor_init((TreeCursor *)&self, node);
+ return self;
+}
+
+void ts_tree_cursor_reset(TSTreeCursor *_self, TSNode node) {
+ ts_tree_cursor_init((TreeCursor *)_self, node);
+}
+
+void ts_tree_cursor_init(TreeCursor *self, TSNode node) {
+ self->tree = node.tree;
+ array_clear(&self->stack);
+ array_push(&self->stack, ((TreeCursorEntry) {
+ .subtree = (const Subtree *)node.id,
+ .position = {
+ ts_node_start_byte(node),
+ ts_node_start_point(node)
+ },
+ .child_index = 0,
+ .structural_child_index = 0,
+ }));
+}
+
+void ts_tree_cursor_delete(TSTreeCursor *_self) {
+ TreeCursor *self = (TreeCursor *)_self;
+ array_delete(&self->stack);
+}
+
+// TSTreeCursor - walking the tree
+
+bool ts_tree_cursor_goto_first_child(TSTreeCursor *_self) {
+ TreeCursor *self = (TreeCursor *)_self;
+
+ bool did_descend;
+ do {
+ did_descend = false;
+
+ bool visible;
+ TreeCursorEntry entry;
+ CursorChildIterator iterator = ts_tree_cursor_iterate_children(self);
+ while (ts_tree_cursor_child_iterator_next(&iterator, &entry, &visible)) {
+ if (visible) {
+ array_push(&self->stack, entry);
+ return true;
+ }
+
+ if (ts_subtree_visible_child_count(*entry.subtree) > 0) {
+ array_push(&self->stack, entry);
+ did_descend = true;
+ break;
+ }
+ }
+ } while (did_descend);
+
+ return false;
+}
+
+int64_t ts_tree_cursor_goto_first_child_for_byte(TSTreeCursor *_self, uint32_t goal_byte) {
+ TreeCursor *self = (TreeCursor *)_self;
+ uint32_t initial_size = self->stack.size;
+ uint32_t visible_child_index = 0;
+
+ bool did_descend;
+ do {
+ did_descend = false;
+
+ bool visible;
+ TreeCursorEntry entry;
+ CursorChildIterator iterator = ts_tree_cursor_iterate_children(self);
+ while (ts_tree_cursor_child_iterator_next(&iterator, &entry, &visible)) {
+ uint32_t end_byte = entry.position.bytes + ts_subtree_size(*entry.subtree).bytes;
+ bool at_goal = end_byte > goal_byte;
+ uint32_t visible_child_count = ts_subtree_visible_child_count(*entry.subtree);
+
+ if (at_goal) {
+ if (visible) {
+ array_push(&self->stack, entry);
+ return visible_child_index;
+ }
+
+ if (visible_child_count > 0) {
+ array_push(&self->stack, entry);
+ did_descend = true;
+ break;
+ }
+ } else if (visible) {
+ visible_child_index++;
+ } else {
+ visible_child_index += visible_child_count;
+ }
+ }
+ } while (did_descend);
+
+ if (self->stack.size > initial_size &&
+ ts_tree_cursor_goto_next_sibling((TSTreeCursor *)self)) {
+ return visible_child_index;
+ }
+
+ self->stack.size = initial_size;
+ return -1;
+}
+
+bool ts_tree_cursor_goto_next_sibling(TSTreeCursor *_self) {
+ TreeCursor *self = (TreeCursor *)_self;
+ uint32_t initial_size = self->stack.size;
+
+ while (self->stack.size > 1) {
+ TreeCursorEntry entry = array_pop(&self->stack);
+ CursorChildIterator iterator = ts_tree_cursor_iterate_children(self);
+ iterator.child_index = entry.child_index;
+ iterator.structural_child_index = entry.structural_child_index;
+ iterator.position = entry.position;
+
+ bool visible = false;
+ ts_tree_cursor_child_iterator_next(&iterator, &entry, &visible);
+ if (visible && self->stack.size + 1 < initial_size) break;
+
+ while (ts_tree_cursor_child_iterator_next(&iterator, &entry, &visible)) {
+ if (visible) {
+ array_push(&self->stack, entry);
+ return true;
+ }
+
+ if (ts_subtree_visible_child_count(*entry.subtree)) {
+ array_push(&self->stack, entry);
+ ts_tree_cursor_goto_first_child(_self);
+ return true;
+ }
+ }
+ }
+
+ self->stack.size = initial_size;
+ return false;
+}
+
+bool ts_tree_cursor_goto_parent(TSTreeCursor *_self) {
+ TreeCursor *self = (TreeCursor *)_self;
+ for (unsigned i = self->stack.size - 2; i + 1 > 0; i--) {
+ TreeCursorEntry *entry = &self->stack.contents[i];
+ bool is_aliased = false;
+ if (i > 0) {
+ TreeCursorEntry *parent_entry = &self->stack.contents[i - 1];
+ const TSSymbol *alias_sequence = ts_language_alias_sequence(
+ self->tree->language,
+ parent_entry->subtree->ptr->production_id
+ );
+ is_aliased = alias_sequence && alias_sequence[entry->structural_child_index];
+ }
+ if (ts_subtree_visible(*entry->subtree) || is_aliased) {
+ self->stack.size = i + 1;
+ return true;
+ }
+ }
+ return false;
+}
+
+TSNode ts_tree_cursor_current_node(const TSTreeCursor *_self) {
+ const TreeCursor *self = (const TreeCursor *)_self;
+ TreeCursorEntry *last_entry = array_back(&self->stack);
+ TSSymbol alias_symbol = 0;
+ if (self->stack.size > 1) {
+ TreeCursorEntry *parent_entry = &self->stack.contents[self->stack.size - 2];
+ const TSSymbol *alias_sequence = ts_language_alias_sequence(
+ self->tree->language,
+ parent_entry->subtree->ptr->production_id
+ );
+ if (alias_sequence && !ts_subtree_extra(*last_entry->subtree)) {
+ alias_symbol = alias_sequence[last_entry->structural_child_index];
+ }
+ }
+ return ts_node_new(
+ self->tree,
+ last_entry->subtree,
+ last_entry->position,
+ alias_symbol
+ );
+}
+
+TSFieldId ts_tree_cursor_current_status(
+ const TSTreeCursor *_self,
+ bool *can_have_later_siblings,
+ bool *can_have_later_siblings_with_this_field
+) {
+ const TreeCursor *self = (const TreeCursor *)_self;
+ TSFieldId result = 0;
+ *can_have_later_siblings = false;
+ *can_have_later_siblings_with_this_field = false;
+
+ // Walk up the tree, visiting the current node and its invisible ancestors,
+ // because fields can refer to nodes through invisible *wrapper* nodes,
+ for (unsigned i = self->stack.size - 1; i > 0; i--) {
+ TreeCursorEntry *entry = &self->stack.contents[i];
+ TreeCursorEntry *parent_entry = &self->stack.contents[i - 1];
+
+ // Stop walking up when a visible ancestor is found.
+ if (i != self->stack.size - 1) {
+ if (ts_subtree_visible(*entry->subtree)) break;
+ const TSSymbol *alias_sequence = ts_language_alias_sequence(
+ self->tree->language,
+ parent_entry->subtree->ptr->production_id
+ );
+ if (alias_sequence && alias_sequence[entry->structural_child_index]) {
+ break;
+ }
+ }
+
+ if (ts_subtree_child_count(*parent_entry->subtree) > entry->child_index + 1) {
+ *can_have_later_siblings = true;
+ }
+
+ if (ts_subtree_extra(*entry->subtree)) break;
+
+ const TSFieldMapEntry *field_map, *field_map_end;
+ ts_language_field_map(
+ self->tree->language,
+ parent_entry->subtree->ptr->production_id,
+ &field_map, &field_map_end
+ );
+
+ // Look for a field name associated with the current node.
+ if (!result) {
+ for (const TSFieldMapEntry *i = field_map; i < field_map_end; i++) {
+ if (!i->inherited && i->child_index == entry->structural_child_index) {
+ result = i->field_id;
+ *can_have_later_siblings_with_this_field = false;
+ break;
+ }
+ }
+ }
+
+ // Determine if there other later siblings with the same field name.
+ if (result) {
+ for (const TSFieldMapEntry *i = field_map; i < field_map_end; i++) {
+ if (i->field_id == result && i->child_index > entry->structural_child_index) {
+ *can_have_later_siblings_with_this_field = true;
+ break;
+ }
+ }
+ }
+ }
+
+ return result;
+}
+
+TSFieldId ts_tree_cursor_current_field_id(const TSTreeCursor *_self) {
+ const TreeCursor *self = (const TreeCursor *)_self;
+
+ // Walk up the tree, visiting the current node and its invisible ancestors.
+ for (unsigned i = self->stack.size - 1; i > 0; i--) {
+ TreeCursorEntry *entry = &self->stack.contents[i];
+ TreeCursorEntry *parent_entry = &self->stack.contents[i - 1];
+
+ // Stop walking up when another visible node is found.
+ if (i != self->stack.size - 1) {
+ if (ts_subtree_visible(*entry->subtree)) break;
+ const TSSymbol *alias_sequence = ts_language_alias_sequence(
+ self->tree->language,
+ parent_entry->subtree->ptr->production_id
+ );
+ if (alias_sequence && alias_sequence[entry->structural_child_index]) {
+ break;
+ }
+ }
+
+ if (ts_subtree_extra(*entry->subtree)) break;
+
+ const TSFieldMapEntry *field_map, *field_map_end;
+ ts_language_field_map(
+ self->tree->language,
+ parent_entry->subtree->ptr->production_id,
+ &field_map, &field_map_end
+ );
+ for (const TSFieldMapEntry *i = field_map; i < field_map_end; i++) {
+ if (!i->inherited && i->child_index == entry->structural_child_index) {
+ return i->field_id;
+ }
+ }
+ }
+ return 0;
+}
+
+const char *ts_tree_cursor_current_field_name(const TSTreeCursor *_self) {
+ TSFieldId id = ts_tree_cursor_current_field_id(_self);
+ if (id) {
+ const TreeCursor *self = (const TreeCursor *)_self;
+ return self->tree->language->field_names[id];
+ } else {
+ return NULL;
+ }
+}
+
+TSTreeCursor ts_tree_cursor_copy(const TSTreeCursor *_cursor) {
+ const TreeCursor *cursor = (const TreeCursor *)_cursor;
+ TSTreeCursor res = {NULL, NULL, {0, 0}};
+ TreeCursor *copy = (TreeCursor *)&res;
+ copy->tree = cursor->tree;
+ array_push_all(&copy->stack, &cursor->stack);
+ return res;
+}
diff --git a/src/tree_sitter/tree_cursor.h b/src/tree_sitter/tree_cursor.h
new file mode 100644
index 0000000000..5a39dd278c
--- /dev/null
+++ b/src/tree_sitter/tree_cursor.h
@@ -0,0 +1,21 @@
+#ifndef TREE_SITTER_TREE_CURSOR_H_
+#define TREE_SITTER_TREE_CURSOR_H_
+
+#include "./subtree.h"
+
+typedef struct {
+ const Subtree *subtree;
+ Length position;
+ uint32_t child_index;
+ uint32_t structural_child_index;
+} TreeCursorEntry;
+
+typedef struct {
+ const TSTree *tree;
+ Array(TreeCursorEntry) stack;
+} TreeCursor;
+
+void ts_tree_cursor_init(TreeCursor *, TSNode);
+TSFieldId ts_tree_cursor_current_status(const TSTreeCursor *, bool *, bool *);
+
+#endif // TREE_SITTER_TREE_CURSOR_H_
diff --git a/src/tree_sitter/treesitter_commit_hash.txt b/src/tree_sitter/treesitter_commit_hash.txt
new file mode 100644
index 0000000000..bd7fcfbe76
--- /dev/null
+++ b/src/tree_sitter/treesitter_commit_hash.txt
@@ -0,0 +1 @@
+81d533d2d1b580fdb507accabc91ceddffb5b6f0
diff --git a/src/tree_sitter/unicode.h b/src/tree_sitter/unicode.h
new file mode 100644
index 0000000000..2ab51c2a3a
--- /dev/null
+++ b/src/tree_sitter/unicode.h
@@ -0,0 +1,50 @@
+#ifndef TREE_SITTER_UNICODE_H_
+#define TREE_SITTER_UNICODE_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <limits.h>
+#include <stdint.h>
+
+#define U_EXPORT
+#define U_EXPORT2
+#include "./unicode/utf8.h"
+#include "./unicode/utf16.h"
+
+static const int32_t TS_DECODE_ERROR = U_SENTINEL;
+
+// These functions read one unicode code point from the given string,
+// returning the number of bytes consumed.
+typedef uint32_t (*UnicodeDecodeFunction)(
+ const uint8_t *string,
+ uint32_t length,
+ int32_t *code_point
+);
+
+static inline uint32_t ts_decode_utf8(
+ const uint8_t *string,
+ uint32_t length,
+ int32_t *code_point
+) {
+ uint32_t i = 0;
+ U8_NEXT(string, i, length, *code_point);
+ return i;
+}
+
+static inline uint32_t ts_decode_utf16(
+ const uint8_t *string,
+ uint32_t length,
+ int32_t *code_point
+) {
+ uint32_t i = 0;
+ U16_NEXT(((uint16_t *)string), i, length, *code_point);
+ return i * 2;
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // TREE_SITTER_UNICODE_H_
diff --git a/src/tree_sitter/unicode/ICU_SHA b/src/tree_sitter/unicode/ICU_SHA
new file mode 100644
index 0000000000..3622283ba3
--- /dev/null
+++ b/src/tree_sitter/unicode/ICU_SHA
@@ -0,0 +1 @@
+552b01f61127d30d6589aa4bf99468224979b661
diff --git a/src/tree_sitter/unicode/LICENSE b/src/tree_sitter/unicode/LICENSE
new file mode 100644
index 0000000000..2e01e36876
--- /dev/null
+++ b/src/tree_sitter/unicode/LICENSE
@@ -0,0 +1,414 @@
+COPYRIGHT AND PERMISSION NOTICE (ICU 58 and later)
+
+Copyright © 1991-2019 Unicode, Inc. All rights reserved.
+Distributed under the Terms of Use in https://www.unicode.org/copyright.html.
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Unicode data files and any associated documentation
+(the "Data Files") or Unicode software and any associated documentation
+(the "Software") to deal in the Data Files or Software
+without restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, and/or sell copies of
+the Data Files or Software, and to permit persons to whom the Data Files
+or Software are furnished to do so, provided that either
+(a) this copyright and permission notice appear with all copies
+of the Data Files or Software, or
+(b) this copyright and permission notice appear in associated
+Documentation.
+
+THE DATA FILES AND SOFTWARE ARE 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 OF THIRD PARTY RIGHTS.
+IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS
+NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL
+DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
+DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+PERFORMANCE OF THE DATA FILES OR SOFTWARE.
+
+Except as contained in this notice, the name of a copyright holder
+shall not be used in advertising or otherwise to promote the sale,
+use or other dealings in these Data Files or Software without prior
+written authorization of the copyright holder.
+
+---------------------
+
+Third-Party Software Licenses
+
+This section contains third-party software notices and/or additional
+terms for licensed third-party software components included within ICU
+libraries.
+
+1. ICU License - ICU 1.8.1 to ICU 57.1
+
+COPYRIGHT AND PERMISSION NOTICE
+
+Copyright (c) 1995-2016 International Business Machines Corporation and others
+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, and/or sell copies of the Software, and to permit persons
+to whom the Software is furnished to do so, provided that the above
+copyright notice(s) and this permission notice appear in all copies of
+the Software and that both the above copyright notice(s) and this
+permission notice appear in supporting documentation.
+
+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
+OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY
+SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER
+RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
+CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+Except as contained in this notice, the name of a copyright holder
+shall not be used in advertising or otherwise to promote the sale, use
+or other dealings in this Software without prior written authorization
+of the copyright holder.
+
+All trademarks and registered trademarks mentioned herein are the
+property of their respective owners.
+
+2. Chinese/Japanese Word Break Dictionary Data (cjdict.txt)
+
+ # The Google Chrome software developed by Google is licensed under
+ # the BSD license. Other software included in this distribution is
+ # provided under other licenses, as set forth below.
+ #
+ # The BSD License
+ # http://opensource.org/licenses/bsd-license.php
+ # Copyright (C) 2006-2008, Google Inc.
+ #
+ # All rights reserved.
+ #
+ # Redistribution and use in source and binary forms, with or without
+ # modification, are permitted provided that the following conditions are met:
+ #
+ # Redistributions of source code must retain the above copyright notice,
+ # this list of conditions and the following disclaimer.
+ # Redistributions in binary form must reproduce the above
+ # copyright notice, this list of conditions and the following
+ # disclaimer in the documentation and/or other materials provided with
+ # the distribution.
+ # Neither the name of Google Inc. nor the names of its
+ # contributors may be used to endorse or promote products derived from
+ # this software without specific prior written permission.
+ #
+ #
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ # CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ # BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ #
+ #
+ # The word list in cjdict.txt are generated by combining three word lists
+ # listed below with further processing for compound word breaking. The
+ # frequency is generated with an iterative training against Google web
+ # corpora.
+ #
+ # * Libtabe (Chinese)
+ # - https://sourceforge.net/project/?group_id=1519
+ # - Its license terms and conditions are shown below.
+ #
+ # * IPADIC (Japanese)
+ # - http://chasen.aist-nara.ac.jp/chasen/distribution.html
+ # - Its license terms and conditions are shown below.
+ #
+ # ---------COPYING.libtabe ---- BEGIN--------------------
+ #
+ # /*
+ # * Copyright (c) 1999 TaBE Project.
+ # * Copyright (c) 1999 Pai-Hsiang Hsiao.
+ # * All rights reserved.
+ # *
+ # * Redistribution and use in source and binary forms, with or without
+ # * modification, are permitted provided that the following conditions
+ # * are met:
+ # *
+ # * . Redistributions of source code must retain the above copyright
+ # * notice, this list of conditions and the following disclaimer.
+ # * . Redistributions in binary form must reproduce the above copyright
+ # * notice, this list of conditions and the following disclaimer in
+ # * the documentation and/or other materials provided with the
+ # * distribution.
+ # * . Neither the name of the TaBE Project nor the names of its
+ # * contributors may be used to endorse or promote products derived
+ # * from this software without specific prior written permission.
+ # *
+ # * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ # * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ # * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ # * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ # * REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ # * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ # * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ # * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ # * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ # * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ # * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ # * OF THE POSSIBILITY OF SUCH DAMAGE.
+ # */
+ #
+ # /*
+ # * Copyright (c) 1999 Computer Systems and Communication Lab,
+ # * Institute of Information Science, Academia
+ # * Sinica. All rights reserved.
+ # *
+ # * Redistribution and use in source and binary forms, with or without
+ # * modification, are permitted provided that the following conditions
+ # * are met:
+ # *
+ # * . Redistributions of source code must retain the above copyright
+ # * notice, this list of conditions and the following disclaimer.
+ # * . Redistributions in binary form must reproduce the above copyright
+ # * notice, this list of conditions and the following disclaimer in
+ # * the documentation and/or other materials provided with the
+ # * distribution.
+ # * . Neither the name of the Computer Systems and Communication Lab
+ # * nor the names of its contributors may be used to endorse or
+ # * promote products derived from this software without specific
+ # * prior written permission.
+ # *
+ # * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ # * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ # * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ # * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ # * REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ # * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ # * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ # * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ # * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ # * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ # * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ # * OF THE POSSIBILITY OF SUCH DAMAGE.
+ # */
+ #
+ # Copyright 1996 Chih-Hao Tsai @ Beckman Institute,
+ # University of Illinois
+ # c-tsai4@uiuc.edu http://casper.beckman.uiuc.edu/~c-tsai4
+ #
+ # ---------------COPYING.libtabe-----END--------------------------------
+ #
+ #
+ # ---------------COPYING.ipadic-----BEGIN-------------------------------
+ #
+ # Copyright 2000, 2001, 2002, 2003 Nara Institute of Science
+ # and Technology. All Rights Reserved.
+ #
+ # Use, reproduction, and distribution of this software is permitted.
+ # Any copy of this software, whether in its original form or modified,
+ # must include both the above copyright notice and the following
+ # paragraphs.
+ #
+ # Nara Institute of Science and Technology (NAIST),
+ # the copyright holders, disclaims all warranties with regard to this
+ # software, including all implied warranties of merchantability and
+ # fitness, in no event shall NAIST be liable for
+ # any special, indirect or consequential damages or any damages
+ # whatsoever resulting from loss of use, data or profits, whether in an
+ # action of contract, negligence or other tortuous action, arising out
+ # of or in connection with the use or performance of this software.
+ #
+ # A large portion of the dictionary entries
+ # originate from ICOT Free Software. The following conditions for ICOT
+ # Free Software applies to the current dictionary as well.
+ #
+ # Each User may also freely distribute the Program, whether in its
+ # original form or modified, to any third party or parties, PROVIDED
+ # that the provisions of Section 3 ("NO WARRANTY") will ALWAYS appear
+ # on, or be attached to, the Program, which is distributed substantially
+ # in the same form as set out herein and that such intended
+ # distribution, if actually made, will neither violate or otherwise
+ # contravene any of the laws and regulations of the countries having
+ # jurisdiction over the User or the intended distribution itself.
+ #
+ # NO WARRANTY
+ #
+ # The program was produced on an experimental basis in the course of the
+ # research and development conducted during the project and is provided
+ # to users as so produced on an experimental basis. Accordingly, the
+ # program is provided without any warranty whatsoever, whether express,
+ # implied, statutory or otherwise. The term "warranty" used herein
+ # includes, but is not limited to, any warranty of the quality,
+ # performance, merchantability and fitness for a particular purpose of
+ # the program and the nonexistence of any infringement or violation of
+ # any right of any third party.
+ #
+ # Each user of the program will agree and understand, and be deemed to
+ # have agreed and understood, that there is no warranty whatsoever for
+ # the program and, accordingly, the entire risk arising from or
+ # otherwise connected with the program is assumed by the user.
+ #
+ # Therefore, neither ICOT, the copyright holder, or any other
+ # organization that participated in or was otherwise related to the
+ # development of the program and their respective officials, directors,
+ # officers and other employees shall be held liable for any and all
+ # damages, including, without limitation, general, special, incidental
+ # and consequential damages, arising out of or otherwise in connection
+ # with the use or inability to use the program or any product, material
+ # or result produced or otherwise obtained by using the program,
+ # regardless of whether they have been advised of, or otherwise had
+ # knowledge of, the possibility of such damages at any time during the
+ # project or thereafter. Each user will be deemed to have agreed to the
+ # foregoing by his or her commencement of use of the program. The term
+ # "use" as used herein includes, but is not limited to, the use,
+ # modification, copying and distribution of the program and the
+ # production of secondary products from the program.
+ #
+ # In the case where the program, whether in its original form or
+ # modified, was distributed or delivered to or received by a user from
+ # any person, organization or entity other than ICOT, unless it makes or
+ # grants independently of ICOT any specific warranty to the user in
+ # writing, such person, organization or entity, will also be exempted
+ # from and not be held liable to the user for any such damages as noted
+ # above as far as the program is concerned.
+ #
+ # ---------------COPYING.ipadic-----END----------------------------------
+
+3. Lao Word Break Dictionary Data (laodict.txt)
+
+ # Copyright (c) 2013 International Business Machines Corporation
+ # and others. All Rights Reserved.
+ #
+ # Project: http://code.google.com/p/lao-dictionary/
+ # Dictionary: http://lao-dictionary.googlecode.com/git/Lao-Dictionary.txt
+ # License: http://lao-dictionary.googlecode.com/git/Lao-Dictionary-LICENSE.txt
+ # (copied below)
+ #
+ # This file is derived from the above dictionary, with slight
+ # modifications.
+ # ----------------------------------------------------------------------
+ # Copyright (C) 2013 Brian Eugene Wilson, Robert Martin Campbell.
+ # All rights reserved.
+ #
+ # Redistribution and use in source and binary forms, with or without
+ # modification,
+ # are permitted provided that the following conditions are met:
+ #
+ #
+ # Redistributions of source code must retain the above copyright notice, this
+ # list of conditions and the following disclaimer. Redistributions in
+ # binary form must reproduce the above copyright notice, this list of
+ # conditions and the following disclaimer in the documentation and/or
+ # other materials provided with the distribution.
+ #
+ #
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ # OF THE POSSIBILITY OF SUCH DAMAGE.
+ # --------------------------------------------------------------------------
+
+4. Burmese Word Break Dictionary Data (burmesedict.txt)
+
+ # Copyright (c) 2014 International Business Machines Corporation
+ # and others. All Rights Reserved.
+ #
+ # This list is part of a project hosted at:
+ # github.com/kanyawtech/myanmar-karen-word-lists
+ #
+ # --------------------------------------------------------------------------
+ # Copyright (c) 2013, LeRoy Benjamin Sharon
+ # All rights reserved.
+ #
+ # Redistribution and use in source and binary forms, with or without
+ # modification, are permitted provided that the following conditions
+ # are met: Redistributions of source code must retain the above
+ # copyright notice, this list of conditions and the following
+ # disclaimer. Redistributions in binary form must reproduce the
+ # above copyright notice, this list of conditions and the following
+ # disclaimer in the documentation and/or other materials provided
+ # with the distribution.
+ #
+ # Neither the name Myanmar Karen Word Lists, nor the names of its
+ # contributors may be used to endorse or promote products derived
+ # from this software without specific prior written permission.
+ #
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ # CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS
+ # BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ # TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+ # TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+ # THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ # SUCH DAMAGE.
+ # --------------------------------------------------------------------------
+
+5. Time Zone Database
+
+ ICU uses the public domain data and code derived from Time Zone
+Database for its time zone support. The ownership of the TZ database
+is explained in BCP 175: Procedure for Maintaining the Time Zone
+Database section 7.
+
+ # 7. Database Ownership
+ #
+ # The TZ database itself is not an IETF Contribution or an IETF
+ # document. Rather it is a pre-existing and regularly updated work
+ # that is in the public domain, and is intended to remain in the
+ # public domain. Therefore, BCPs 78 [RFC5378] and 79 [RFC3979] do
+ # not apply to the TZ Database or contributions that individuals make
+ # to it. Should any claims be made and substantiated against the TZ
+ # Database, the organization that is providing the IANA
+ # Considerations defined in this RFC, under the memorandum of
+ # understanding with the IETF, currently ICANN, may act in accordance
+ # with all competent court orders. No ownership claims will be made
+ # by ICANN or the IETF Trust on the database or the code. Any person
+ # making a contribution to the database or code waives all rights to
+ # future claims in that contribution or in the TZ Database.
+
+6. Google double-conversion
+
+Copyright 2006-2011, the V8 project authors. All rights reserved.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of Google Inc. nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/src/tree_sitter/unicode/README.md b/src/tree_sitter/unicode/README.md
new file mode 100644
index 0000000000..623b8e3843
--- /dev/null
+++ b/src/tree_sitter/unicode/README.md
@@ -0,0 +1,29 @@
+# ICU Parts
+
+This directory contains a small subset of files from the Unicode organization's [ICU repository](https://github.com/unicode-org/icu).
+
+### License
+
+The license for these files is contained in the `LICENSE` file within this directory.
+
+### Contents
+
+* Source files taken from the [`icu4c/source/common/unicode`](https://github.com/unicode-org/icu/tree/552b01f61127d30d6589aa4bf99468224979b661/icu4c/source/common/unicode) directory:
+ * `utf8.h`
+ * `utf16.h`
+ * `umachine.h`
+* Empty source files that are referenced by the above source files, but whose original contents in `libicu` are not needed:
+ * `ptypes.h`
+ * `urename.h`
+ * `utf.h`
+* `ICU_SHA` - File containing the Git SHA of the commit in the `icu` repository from which the files were obtained.
+* `LICENSE` - The license file from the [`icu4c`](https://github.com/unicode-org/icu/tree/552b01f61127d30d6589aa4bf99468224979b661/icu4c) directory of the `icu` repository.
+* `README.md` - This text file.
+
+### Updating ICU
+
+To incorporate changes from the upstream `icu` repository:
+
+* Update `ICU_SHA` with the new Git SHA.
+* Update `LICENSE` with the license text from the directory mentioned above.
+* Update `utf8.h`, `utf16.h`, and `umachine.h` with their new contents in the `icu` repository.
diff --git a/src/tree_sitter/unicode/ptypes.h b/src/tree_sitter/unicode/ptypes.h
new file mode 100644
index 0000000000..ac79ad0f98
--- /dev/null
+++ b/src/tree_sitter/unicode/ptypes.h
@@ -0,0 +1 @@
+// This file must exist in order for `utf8.h` and `utf16.h` to be used.
diff --git a/src/tree_sitter/unicode/umachine.h b/src/tree_sitter/unicode/umachine.h
new file mode 100644
index 0000000000..bbf6ef9c8b
--- /dev/null
+++ b/src/tree_sitter/unicode/umachine.h
@@ -0,0 +1,448 @@
+// © 2016 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html
+/*
+******************************************************************************
+*
+* Copyright (C) 1999-2015, International Business Machines
+* Corporation and others. All Rights Reserved.
+*
+******************************************************************************
+* file name: umachine.h
+* encoding: UTF-8
+* tab size: 8 (not used)
+* indentation:4
+*
+* created on: 1999sep13
+* created by: Markus W. Scherer
+*
+* This file defines basic types and constants for ICU to be
+* platform-independent. umachine.h and utf.h are included into
+* utypes.h to provide all the general definitions for ICU.
+* All of these definitions used to be in utypes.h before
+* the UTF-handling macros made this unmaintainable.
+*/
+
+#ifndef __UMACHINE_H__
+#define __UMACHINE_H__
+
+
+/**
+ * \file
+ * \brief Basic types and constants for UTF
+ *
+ * <h2> Basic types and constants for UTF </h2>
+ * This file defines basic types and constants for utf.h to be
+ * platform-independent. umachine.h and utf.h are included into
+ * utypes.h to provide all the general definitions for ICU.
+ * All of these definitions used to be in utypes.h before
+ * the UTF-handling macros made this unmaintainable.
+ *
+ */
+/*==========================================================================*/
+/* Include platform-dependent definitions */
+/* which are contained in the platform-specific file platform.h */
+/*==========================================================================*/
+
+#include "./ptypes.h" /* platform.h is included in ptypes.h */
+
+/*
+ * ANSI C headers:
+ * stddef.h defines wchar_t
+ */
+#include <stddef.h>
+
+/*==========================================================================*/
+/* For C wrappers, we use the symbol U_STABLE. */
+/* This works properly if the includer is C or C++. */
+/* Functions are declared U_STABLE return-type U_EXPORT2 function-name()... */
+/*==========================================================================*/
+
+/**
+ * \def U_CFUNC
+ * This is used in a declaration of a library private ICU C function.
+ * @stable ICU 2.4
+ */
+
+/**
+ * \def U_CDECL_BEGIN
+ * This is used to begin a declaration of a library private ICU C API.
+ * @stable ICU 2.4
+ */
+
+/**
+ * \def U_CDECL_END
+ * This is used to end a declaration of a library private ICU C API
+ * @stable ICU 2.4
+ */
+
+#ifdef __cplusplus
+# define U_CFUNC extern "C"
+# define U_CDECL_BEGIN extern "C" {
+# define U_CDECL_END }
+#else
+# define U_CFUNC extern
+# define U_CDECL_BEGIN
+# define U_CDECL_END
+#endif
+
+#ifndef U_ATTRIBUTE_DEPRECATED
+/**
+ * \def U_ATTRIBUTE_DEPRECATED
+ * This is used for GCC specific attributes
+ * @internal
+ */
+#if U_GCC_MAJOR_MINOR >= 302
+# define U_ATTRIBUTE_DEPRECATED __attribute__ ((deprecated))
+/**
+ * \def U_ATTRIBUTE_DEPRECATED
+ * This is used for Visual C++ specific attributes
+ * @internal
+ */
+#elif defined(_MSC_VER) && (_MSC_VER >= 1400)
+# define U_ATTRIBUTE_DEPRECATED __declspec(deprecated)
+#else
+# define U_ATTRIBUTE_DEPRECATED
+#endif
+#endif
+
+/** This is used to declare a function as a public ICU C API @stable ICU 2.0*/
+#define U_CAPI U_CFUNC U_EXPORT
+/** This is used to declare a function as a stable public ICU C API*/
+#define U_STABLE U_CAPI
+/** This is used to declare a function as a draft public ICU C API */
+#define U_DRAFT U_CAPI
+/** This is used to declare a function as a deprecated public ICU C API */
+#define U_DEPRECATED U_CAPI U_ATTRIBUTE_DEPRECATED
+/** This is used to declare a function as an obsolete public ICU C API */
+#define U_OBSOLETE U_CAPI
+/** This is used to declare a function as an internal ICU C API */
+#define U_INTERNAL U_CAPI
+
+/**
+ * \def U_OVERRIDE
+ * Defined to the C++11 "override" keyword if available.
+ * Denotes a class or member which is an override of the base class.
+ * May result in an error if it applied to something not an override.
+ * @internal
+ */
+#ifndef U_OVERRIDE
+#define U_OVERRIDE override
+#endif
+
+/**
+ * \def U_FINAL
+ * Defined to the C++11 "final" keyword if available.
+ * Denotes a class or member which may not be overridden in subclasses.
+ * May result in an error if subclasses attempt to override.
+ * @internal
+ */
+#if !defined(U_FINAL) || defined(U_IN_DOXYGEN)
+#define U_FINAL final
+#endif
+
+// Before ICU 65, function-like, multi-statement ICU macros were just defined as
+// series of statements wrapped in { } blocks and the caller could choose to
+// either treat them as if they were actual functions and end the invocation
+// with a trailing ; creating an empty statement after the block or else omit
+// this trailing ; using the knowledge that the macro would expand to { }.
+//
+// But doing so doesn't work well with macros that look like functions and
+// compiler warnings about empty statements (ICU-20601) and ICU 65 therefore
+// switches to the standard solution of wrapping such macros in do { } while.
+//
+// This will however break existing code that depends on being able to invoke
+// these macros without a trailing ; so to be able to remain compatible with
+// such code the wrapper is itself defined as macros so that it's possible to
+// build ICU 65 and later with the old macro behaviour, like this:
+//
+// CPPFLAGS='-DUPRV_BLOCK_MACRO_BEGIN="" -DUPRV_BLOCK_MACRO_END=""'
+// runConfigureICU ...
+
+/**
+ * \def UPRV_BLOCK_MACRO_BEGIN
+ * Defined as the "do" keyword by default.
+ * @internal
+ */
+#ifndef UPRV_BLOCK_MACRO_BEGIN
+#define UPRV_BLOCK_MACRO_BEGIN do
+#endif
+
+/**
+ * \def UPRV_BLOCK_MACRO_END
+ * Defined as "while (FALSE)" by default.
+ * @internal
+ */
+#ifndef UPRV_BLOCK_MACRO_END
+#define UPRV_BLOCK_MACRO_END while (FALSE)
+#endif
+
+/*==========================================================================*/
+/* limits for int32_t etc., like in POSIX inttypes.h */
+/*==========================================================================*/
+
+#ifndef INT8_MIN
+/** The smallest value an 8 bit signed integer can hold @stable ICU 2.0 */
+# define INT8_MIN ((int8_t)(-128))
+#endif
+#ifndef INT16_MIN
+/** The smallest value a 16 bit signed integer can hold @stable ICU 2.0 */
+# define INT16_MIN ((int16_t)(-32767-1))
+#endif
+#ifndef INT32_MIN
+/** The smallest value a 32 bit signed integer can hold @stable ICU 2.0 */
+# define INT32_MIN ((int32_t)(-2147483647-1))
+#endif
+
+#ifndef INT8_MAX
+/** The largest value an 8 bit signed integer can hold @stable ICU 2.0 */
+# define INT8_MAX ((int8_t)(127))
+#endif
+#ifndef INT16_MAX
+/** The largest value a 16 bit signed integer can hold @stable ICU 2.0 */
+# define INT16_MAX ((int16_t)(32767))
+#endif
+#ifndef INT32_MAX
+/** The largest value a 32 bit signed integer can hold @stable ICU 2.0 */
+# define INT32_MAX ((int32_t)(2147483647))
+#endif
+
+#ifndef UINT8_MAX
+/** The largest value an 8 bit unsigned integer can hold @stable ICU 2.0 */
+# define UINT8_MAX ((uint8_t)(255U))
+#endif
+#ifndef UINT16_MAX
+/** The largest value a 16 bit unsigned integer can hold @stable ICU 2.0 */
+# define UINT16_MAX ((uint16_t)(65535U))
+#endif
+#ifndef UINT32_MAX
+/** The largest value a 32 bit unsigned integer can hold @stable ICU 2.0 */
+# define UINT32_MAX ((uint32_t)(4294967295U))
+#endif
+
+#if defined(U_INT64_T_UNAVAILABLE)
+# error int64_t is required for decimal format and rule-based number format.
+#else
+# ifndef INT64_C
+/**
+ * Provides a platform independent way to specify a signed 64-bit integer constant.
+ * note: may be wrong for some 64 bit platforms - ensure your compiler provides INT64_C
+ * @stable ICU 2.8
+ */
+# define INT64_C(c) c ## LL
+# endif
+# ifndef UINT64_C
+/**
+ * Provides a platform independent way to specify an unsigned 64-bit integer constant.
+ * note: may be wrong for some 64 bit platforms - ensure your compiler provides UINT64_C
+ * @stable ICU 2.8
+ */
+# define UINT64_C(c) c ## ULL
+# endif
+# ifndef U_INT64_MIN
+/** The smallest value a 64 bit signed integer can hold @stable ICU 2.8 */
+# define U_INT64_MIN ((int64_t)(INT64_C(-9223372036854775807)-1))
+# endif
+# ifndef U_INT64_MAX
+/** The largest value a 64 bit signed integer can hold @stable ICU 2.8 */
+# define U_INT64_MAX ((int64_t)(INT64_C(9223372036854775807)))
+# endif
+# ifndef U_UINT64_MAX
+/** The largest value a 64 bit unsigned integer can hold @stable ICU 2.8 */
+# define U_UINT64_MAX ((uint64_t)(UINT64_C(18446744073709551615)))
+# endif
+#endif
+
+/*==========================================================================*/
+/* Boolean data type */
+/*==========================================================================*/
+
+/** The ICU boolean type @stable ICU 2.0 */
+typedef int8_t UBool;
+
+#ifndef TRUE
+/** The TRUE value of a UBool @stable ICU 2.0 */
+# define TRUE 1
+#endif
+#ifndef FALSE
+/** The FALSE value of a UBool @stable ICU 2.0 */
+# define FALSE 0
+#endif
+
+
+/*==========================================================================*/
+/* Unicode data types */
+/*==========================================================================*/
+
+/* wchar_t-related definitions -------------------------------------------- */
+
+/*
+ * \def U_WCHAR_IS_UTF16
+ * Defined if wchar_t uses UTF-16.
+ *
+ * @stable ICU 2.0
+ */
+/*
+ * \def U_WCHAR_IS_UTF32
+ * Defined if wchar_t uses UTF-32.
+ *
+ * @stable ICU 2.0
+ */
+#if !defined(U_WCHAR_IS_UTF16) && !defined(U_WCHAR_IS_UTF32)
+# ifdef __STDC_ISO_10646__
+# if (U_SIZEOF_WCHAR_T==2)
+# define U_WCHAR_IS_UTF16
+# elif (U_SIZEOF_WCHAR_T==4)
+# define U_WCHAR_IS_UTF32
+# endif
+# elif defined __UCS2__
+# if (U_PF_OS390 <= U_PLATFORM && U_PLATFORM <= U_PF_OS400) && (U_SIZEOF_WCHAR_T==2)
+# define U_WCHAR_IS_UTF16
+# endif
+# elif defined(__UCS4__) || (U_PLATFORM == U_PF_OS400 && defined(__UTF32__))
+# if (U_SIZEOF_WCHAR_T==4)
+# define U_WCHAR_IS_UTF32
+# endif
+# elif U_PLATFORM_IS_DARWIN_BASED || (U_SIZEOF_WCHAR_T==4 && U_PLATFORM_IS_LINUX_BASED)
+# define U_WCHAR_IS_UTF32
+# elif U_PLATFORM_HAS_WIN32_API
+# define U_WCHAR_IS_UTF16
+# endif
+#endif
+
+/* UChar and UChar32 definitions -------------------------------------------- */
+
+/** Number of bytes in a UChar. @stable ICU 2.0 */
+#define U_SIZEOF_UCHAR 2
+
+/**
+ * \def U_CHAR16_IS_TYPEDEF
+ * If 1, then char16_t is a typedef and not a real type (yet)
+ * @internal
+ */
+#if (U_PLATFORM == U_PF_AIX) && defined(__cplusplus) &&(U_CPLUSPLUS_VERSION < 11)
+// for AIX, uchar.h needs to be included
+# include <uchar.h>
+# define U_CHAR16_IS_TYPEDEF 1
+#elif defined(_MSC_VER) && (_MSC_VER < 1900)
+// Versions of Visual Studio/MSVC below 2015 do not support char16_t as a real type,
+// and instead use a typedef. https://msdn.microsoft.com/library/bb531344.aspx
+# define U_CHAR16_IS_TYPEDEF 1
+#else
+# define U_CHAR16_IS_TYPEDEF 0
+#endif
+
+
+/**
+ * \var UChar
+ *
+ * The base type for UTF-16 code units and pointers.
+ * Unsigned 16-bit integer.
+ * Starting with ICU 59, C++ API uses char16_t directly, while C API continues to use UChar.
+ *
+ * UChar is configurable by defining the macro UCHAR_TYPE
+ * on the preprocessor or compiler command line:
+ * -DUCHAR_TYPE=uint16_t or -DUCHAR_TYPE=wchar_t (if U_SIZEOF_WCHAR_T==2) etc.
+ * (The UCHAR_TYPE can also be \#defined earlier in this file, for outside the ICU library code.)
+ * This is for transitional use from application code that uses uint16_t or wchar_t for UTF-16.
+ *
+ * The default is UChar=char16_t.
+ *
+ * C++11 defines char16_t as bit-compatible with uint16_t, but as a distinct type.
+ *
+ * In C, char16_t is a simple typedef of uint_least16_t.
+ * ICU requires uint_least16_t=uint16_t for data memory mapping.
+ * On macOS, char16_t is not available because the uchar.h standard header is missing.
+ *
+ * @stable ICU 4.4
+ */
+
+#if 1
+ // #if 1 is normal. UChar defaults to char16_t in C++.
+ // For configuration testing of UChar=uint16_t temporarily change this to #if 0.
+ // The intltest Makefile #defines UCHAR_TYPE=char16_t,
+ // so we only #define it to uint16_t if it is undefined so far.
+#elif !defined(UCHAR_TYPE)
+# define UCHAR_TYPE uint16_t
+#endif
+
+#if defined(U_COMBINED_IMPLEMENTATION) || defined(U_COMMON_IMPLEMENTATION) || \
+ defined(U_I18N_IMPLEMENTATION) || defined(U_IO_IMPLEMENTATION)
+ // Inside the ICU library code, never configurable.
+ typedef char16_t UChar;
+#elif defined(UCHAR_TYPE)
+ typedef UCHAR_TYPE UChar;
+#elif defined(__cplusplus)
+ typedef char16_t UChar;
+#else
+ typedef uint16_t UChar;
+#endif
+
+/**
+ * \var OldUChar
+ * Default ICU 58 definition of UChar.
+ * A base type for UTF-16 code units and pointers.
+ * Unsigned 16-bit integer.
+ *
+ * Define OldUChar to be wchar_t if that is 16 bits wide.
+ * If wchar_t is not 16 bits wide, then define UChar to be uint16_t.
+ *
+ * This makes the definition of OldUChar platform-dependent
+ * but allows direct string type compatibility with platforms with
+ * 16-bit wchar_t types.
+ *
+ * This is how UChar was defined in ICU 58, for transition convenience.
+ * Exception: ICU 58 UChar was defined to UCHAR_TYPE if that macro was defined.
+ * The current UChar responds to UCHAR_TYPE but OldUChar does not.
+ *
+ * @stable ICU 59
+ */
+#if U_SIZEOF_WCHAR_T==2
+ typedef wchar_t OldUChar;
+#elif defined(__CHAR16_TYPE__)
+ typedef __CHAR16_TYPE__ OldUChar;
+#else
+ typedef uint16_t OldUChar;
+#endif
+
+/**
+ * Define UChar32 as a type for single Unicode code points.
+ * UChar32 is a signed 32-bit integer (same as int32_t).
+ *
+ * The Unicode code point range is 0..0x10ffff.
+ * All other values (negative or >=0x110000) are illegal as Unicode code points.
+ * They may be used as sentinel values to indicate "done", "error"
+ * or similar non-code point conditions.
+ *
+ * Before ICU 2.4 (Jitterbug 2146), UChar32 was defined
+ * to be wchar_t if that is 32 bits wide (wchar_t may be signed or unsigned)
+ * or else to be uint32_t.
+ * That is, the definition of UChar32 was platform-dependent.
+ *
+ * @see U_SENTINEL
+ * @stable ICU 2.4
+ */
+typedef int32_t UChar32;
+
+/**
+ * This value is intended for sentinel values for APIs that
+ * (take or) return single code points (UChar32).
+ * It is outside of the Unicode code point range 0..0x10ffff.
+ *
+ * For example, a "done" or "error" value in a new API
+ * could be indicated with U_SENTINEL.
+ *
+ * ICU APIs designed before ICU 2.4 usually define service-specific "done"
+ * values, mostly 0xffff.
+ * Those may need to be distinguished from
+ * actual U+ffff text contents by calling functions like
+ * CharacterIterator::hasNext() or UnicodeString::length().
+ *
+ * @return -1
+ * @see UChar32
+ * @stable ICU 2.4
+ */
+#define U_SENTINEL (-1)
+
+#include "./urename.h"
+
+#endif
diff --git a/src/tree_sitter/unicode/urename.h b/src/tree_sitter/unicode/urename.h
new file mode 100644
index 0000000000..ac79ad0f98
--- /dev/null
+++ b/src/tree_sitter/unicode/urename.h
@@ -0,0 +1 @@
+// This file must exist in order for `utf8.h` and `utf16.h` to be used.
diff --git a/src/tree_sitter/unicode/utf.h b/src/tree_sitter/unicode/utf.h
new file mode 100644
index 0000000000..ac79ad0f98
--- /dev/null
+++ b/src/tree_sitter/unicode/utf.h
@@ -0,0 +1 @@
+// This file must exist in order for `utf8.h` and `utf16.h` to be used.
diff --git a/src/tree_sitter/unicode/utf16.h b/src/tree_sitter/unicode/utf16.h
new file mode 100644
index 0000000000..b547922441
--- /dev/null
+++ b/src/tree_sitter/unicode/utf16.h
@@ -0,0 +1,733 @@
+// © 2016 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html
+/*
+*******************************************************************************
+*
+* Copyright (C) 1999-2012, International Business Machines
+* Corporation and others. All Rights Reserved.
+*
+*******************************************************************************
+* file name: utf16.h
+* encoding: UTF-8
+* tab size: 8 (not used)
+* indentation:4
+*
+* created on: 1999sep09
+* created by: Markus W. Scherer
+*/
+
+/**
+ * \file
+ * \brief C API: 16-bit Unicode handling macros
+ *
+ * This file defines macros to deal with 16-bit Unicode (UTF-16) code units and strings.
+ *
+ * For more information see utf.h and the ICU User Guide Strings chapter
+ * (http://userguide.icu-project.org/strings).
+ *
+ * <em>Usage:</em>
+ * ICU coding guidelines for if() statements should be followed when using these macros.
+ * Compound statements (curly braces {}) must be used for if-else-while...
+ * bodies and all macro statements should be terminated with semicolon.
+ */
+
+#ifndef __UTF16_H__
+#define __UTF16_H__
+
+#include "./umachine.h"
+#ifndef __UTF_H__
+# include "./utf.h"
+#endif
+
+/* single-code point definitions -------------------------------------------- */
+
+/**
+ * Does this code unit alone encode a code point (BMP, not a surrogate)?
+ * @param c 16-bit code unit
+ * @return TRUE or FALSE
+ * @stable ICU 2.4
+ */
+#define U16_IS_SINGLE(c) !U_IS_SURROGATE(c)
+
+/**
+ * Is this code unit a lead surrogate (U+d800..U+dbff)?
+ * @param c 16-bit code unit
+ * @return TRUE or FALSE
+ * @stable ICU 2.4
+ */
+#define U16_IS_LEAD(c) (((c)&0xfffffc00)==0xd800)
+
+/**
+ * Is this code unit a trail surrogate (U+dc00..U+dfff)?
+ * @param c 16-bit code unit
+ * @return TRUE or FALSE
+ * @stable ICU 2.4
+ */
+#define U16_IS_TRAIL(c) (((c)&0xfffffc00)==0xdc00)
+
+/**
+ * Is this code unit a surrogate (U+d800..U+dfff)?
+ * @param c 16-bit code unit
+ * @return TRUE or FALSE
+ * @stable ICU 2.4
+ */
+#define U16_IS_SURROGATE(c) U_IS_SURROGATE(c)
+
+/**
+ * Assuming c is a surrogate code point (U16_IS_SURROGATE(c)),
+ * is it a lead surrogate?
+ * @param c 16-bit code unit
+ * @return TRUE or FALSE
+ * @stable ICU 2.4
+ */
+#define U16_IS_SURROGATE_LEAD(c) (((c)&0x400)==0)
+
+/**
+ * Assuming c is a surrogate code point (U16_IS_SURROGATE(c)),
+ * is it a trail surrogate?
+ * @param c 16-bit code unit
+ * @return TRUE or FALSE
+ * @stable ICU 4.2
+ */
+#define U16_IS_SURROGATE_TRAIL(c) (((c)&0x400)!=0)
+
+/**
+ * Helper constant for U16_GET_SUPPLEMENTARY.
+ * @internal
+ */
+#define U16_SURROGATE_OFFSET ((0xd800<<10UL)+0xdc00-0x10000)
+
+/**
+ * Get a supplementary code point value (U+10000..U+10ffff)
+ * from its lead and trail surrogates.
+ * The result is undefined if the input values are not
+ * lead and trail surrogates.
+ *
+ * @param lead lead surrogate (U+d800..U+dbff)
+ * @param trail trail surrogate (U+dc00..U+dfff)
+ * @return supplementary code point (U+10000..U+10ffff)
+ * @stable ICU 2.4
+ */
+#define U16_GET_SUPPLEMENTARY(lead, trail) \
+ (((UChar32)(lead)<<10UL)+(UChar32)(trail)-U16_SURROGATE_OFFSET)
+
+
+/**
+ * Get the lead surrogate (0xd800..0xdbff) for a
+ * supplementary code point (0x10000..0x10ffff).
+ * @param supplementary 32-bit code point (U+10000..U+10ffff)
+ * @return lead surrogate (U+d800..U+dbff) for supplementary
+ * @stable ICU 2.4
+ */
+#define U16_LEAD(supplementary) (UChar)(((supplementary)>>10)+0xd7c0)
+
+/**
+ * Get the trail surrogate (0xdc00..0xdfff) for a
+ * supplementary code point (0x10000..0x10ffff).
+ * @param supplementary 32-bit code point (U+10000..U+10ffff)
+ * @return trail surrogate (U+dc00..U+dfff) for supplementary
+ * @stable ICU 2.4
+ */
+#define U16_TRAIL(supplementary) (UChar)(((supplementary)&0x3ff)|0xdc00)
+
+/**
+ * How many 16-bit code units are used to encode this Unicode code point? (1 or 2)
+ * The result is not defined if c is not a Unicode code point (U+0000..U+10ffff).
+ * @param c 32-bit code point
+ * @return 1 or 2
+ * @stable ICU 2.4
+ */
+#define U16_LENGTH(c) ((uint32_t)(c)<=0xffff ? 1 : 2)
+
+/**
+ * The maximum number of 16-bit code units per Unicode code point (U+0000..U+10ffff).
+ * @return 2
+ * @stable ICU 2.4
+ */
+#define U16_MAX_LENGTH 2
+
+/**
+ * Get a code point from a string at a random-access offset,
+ * without changing the offset.
+ * "Unsafe" macro, assumes well-formed UTF-16.
+ *
+ * The offset may point to either the lead or trail surrogate unit
+ * for a supplementary code point, in which case the macro will read
+ * the adjacent matching surrogate as well.
+ * The result is undefined if the offset points to a single, unpaired surrogate.
+ * Iteration through a string is more efficient with U16_NEXT_UNSAFE or U16_NEXT.
+ *
+ * @param s const UChar * string
+ * @param i string offset
+ * @param c output UChar32 variable
+ * @see U16_GET
+ * @stable ICU 2.4
+ */
+#define U16_GET_UNSAFE(s, i, c) UPRV_BLOCK_MACRO_BEGIN { \
+ (c)=(s)[i]; \
+ if(U16_IS_SURROGATE(c)) { \
+ if(U16_IS_SURROGATE_LEAD(c)) { \
+ (c)=U16_GET_SUPPLEMENTARY((c), (s)[(i)+1]); \
+ } else { \
+ (c)=U16_GET_SUPPLEMENTARY((s)[(i)-1], (c)); \
+ } \
+ } \
+} UPRV_BLOCK_MACRO_END
+
+/**
+ * Get a code point from a string at a random-access offset,
+ * without changing the offset.
+ * "Safe" macro, handles unpaired surrogates and checks for string boundaries.
+ *
+ * The offset may point to either the lead or trail surrogate unit
+ * for a supplementary code point, in which case the macro will read
+ * the adjacent matching surrogate as well.
+ *
+ * The length can be negative for a NUL-terminated string.
+ *
+ * If the offset points to a single, unpaired surrogate, then
+ * c is set to that unpaired surrogate.
+ * Iteration through a string is more efficient with U16_NEXT_UNSAFE or U16_NEXT.
+ *
+ * @param s const UChar * string
+ * @param start starting string offset (usually 0)
+ * @param i string offset, must be start<=i<length
+ * @param length string length
+ * @param c output UChar32 variable
+ * @see U16_GET_UNSAFE
+ * @stable ICU 2.4
+ */
+#define U16_GET(s, start, i, length, c) UPRV_BLOCK_MACRO_BEGIN { \
+ (c)=(s)[i]; \
+ if(U16_IS_SURROGATE(c)) { \
+ uint16_t __c2; \
+ if(U16_IS_SURROGATE_LEAD(c)) { \
+ if((i)+1!=(length) && U16_IS_TRAIL(__c2=(s)[(i)+1])) { \
+ (c)=U16_GET_SUPPLEMENTARY((c), __c2); \
+ } \
+ } else { \
+ if((i)>(start) && U16_IS_LEAD(__c2=(s)[(i)-1])) { \
+ (c)=U16_GET_SUPPLEMENTARY(__c2, (c)); \
+ } \
+ } \
+ } \
+} UPRV_BLOCK_MACRO_END
+
+/**
+ * Get a code point from a string at a random-access offset,
+ * without changing the offset.
+ * "Safe" macro, handles unpaired surrogates and checks for string boundaries.
+ *
+ * The offset may point to either the lead or trail surrogate unit
+ * for a supplementary code point, in which case the macro will read
+ * the adjacent matching surrogate as well.
+ *
+ * The length can be negative for a NUL-terminated string.
+ *
+ * If the offset points to a single, unpaired surrogate, then
+ * c is set to U+FFFD.
+ * Iteration through a string is more efficient with U16_NEXT_UNSAFE or U16_NEXT_OR_FFFD.
+ *
+ * @param s const UChar * string
+ * @param start starting string offset (usually 0)
+ * @param i string offset, must be start<=i<length
+ * @param length string length
+ * @param c output UChar32 variable
+ * @see U16_GET_UNSAFE
+ * @stable ICU 60
+ */
+#define U16_GET_OR_FFFD(s, start, i, length, c) UPRV_BLOCK_MACRO_BEGIN { \
+ (c)=(s)[i]; \
+ if(U16_IS_SURROGATE(c)) { \
+ uint16_t __c2; \
+ if(U16_IS_SURROGATE_LEAD(c)) { \
+ if((i)+1!=(length) && U16_IS_TRAIL(__c2=(s)[(i)+1])) { \
+ (c)=U16_GET_SUPPLEMENTARY((c), __c2); \
+ } else { \
+ (c)=0xfffd; \
+ } \
+ } else { \
+ if((i)>(start) && U16_IS_LEAD(__c2=(s)[(i)-1])) { \
+ (c)=U16_GET_SUPPLEMENTARY(__c2, (c)); \
+ } else { \
+ (c)=0xfffd; \
+ } \
+ } \
+ } \
+} UPRV_BLOCK_MACRO_END
+
+/* definitions with forward iteration --------------------------------------- */
+
+/**
+ * Get a code point from a string at a code point boundary offset,
+ * and advance the offset to the next code point boundary.
+ * (Post-incrementing forward iteration.)
+ * "Unsafe" macro, assumes well-formed UTF-16.
+ *
+ * The offset may point to the lead surrogate unit
+ * for a supplementary code point, in which case the macro will read
+ * the following trail surrogate as well.
+ * If the offset points to a trail surrogate, then that itself
+ * will be returned as the code point.
+ * The result is undefined if the offset points to a single, unpaired lead surrogate.
+ *
+ * @param s const UChar * string
+ * @param i string offset
+ * @param c output UChar32 variable
+ * @see U16_NEXT
+ * @stable ICU 2.4
+ */
+#define U16_NEXT_UNSAFE(s, i, c) UPRV_BLOCK_MACRO_BEGIN { \
+ (c)=(s)[(i)++]; \
+ if(U16_IS_LEAD(c)) { \
+ (c)=U16_GET_SUPPLEMENTARY((c), (s)[(i)++]); \
+ } \
+} UPRV_BLOCK_MACRO_END
+
+/**
+ * Get a code point from a string at a code point boundary offset,
+ * and advance the offset to the next code point boundary.
+ * (Post-incrementing forward iteration.)
+ * "Safe" macro, handles unpaired surrogates and checks for string boundaries.
+ *
+ * The length can be negative for a NUL-terminated string.
+ *
+ * The offset may point to the lead surrogate unit
+ * for a supplementary code point, in which case the macro will read
+ * the following trail surrogate as well.
+ * If the offset points to a trail surrogate or
+ * to a single, unpaired lead surrogate, then c is set to that unpaired surrogate.
+ *
+ * @param s const UChar * string
+ * @param i string offset, must be i<length
+ * @param length string length
+ * @param c output UChar32 variable
+ * @see U16_NEXT_UNSAFE
+ * @stable ICU 2.4
+ */
+#define U16_NEXT(s, i, length, c) UPRV_BLOCK_MACRO_BEGIN { \
+ (c)=(s)[(i)++]; \
+ if(U16_IS_LEAD(c)) { \
+ uint16_t __c2; \
+ if((i)!=(length) && U16_IS_TRAIL(__c2=(s)[(i)])) { \
+ ++(i); \
+ (c)=U16_GET_SUPPLEMENTARY((c), __c2); \
+ } \
+ } \
+} UPRV_BLOCK_MACRO_END
+
+/**
+ * Get a code point from a string at a code point boundary offset,
+ * and advance the offset to the next code point boundary.
+ * (Post-incrementing forward iteration.)
+ * "Safe" macro, handles unpaired surrogates and checks for string boundaries.
+ *
+ * The length can be negative for a NUL-terminated string.
+ *
+ * The offset may point to the lead surrogate unit
+ * for a supplementary code point, in which case the macro will read
+ * the following trail surrogate as well.
+ * If the offset points to a trail surrogate or
+ * to a single, unpaired lead surrogate, then c is set to U+FFFD.
+ *
+ * @param s const UChar * string
+ * @param i string offset, must be i<length
+ * @param length string length
+ * @param c output UChar32 variable
+ * @see U16_NEXT_UNSAFE
+ * @stable ICU 60
+ */
+#define U16_NEXT_OR_FFFD(s, i, length, c) UPRV_BLOCK_MACRO_BEGIN { \
+ (c)=(s)[(i)++]; \
+ if(U16_IS_SURROGATE(c)) { \
+ uint16_t __c2; \
+ if(U16_IS_SURROGATE_LEAD(c) && (i)!=(length) && U16_IS_TRAIL(__c2=(s)[(i)])) { \
+ ++(i); \
+ (c)=U16_GET_SUPPLEMENTARY((c), __c2); \
+ } else { \
+ (c)=0xfffd; \
+ } \
+ } \
+} UPRV_BLOCK_MACRO_END
+
+/**
+ * Append a code point to a string, overwriting 1 or 2 code units.
+ * The offset points to the current end of the string contents
+ * and is advanced (post-increment).
+ * "Unsafe" macro, assumes a valid code point and sufficient space in the string.
+ * Otherwise, the result is undefined.
+ *
+ * @param s const UChar * string buffer
+ * @param i string offset
+ * @param c code point to append
+ * @see U16_APPEND
+ * @stable ICU 2.4
+ */
+#define U16_APPEND_UNSAFE(s, i, c) UPRV_BLOCK_MACRO_BEGIN { \
+ if((uint32_t)(c)<=0xffff) { \
+ (s)[(i)++]=(uint16_t)(c); \
+ } else { \
+ (s)[(i)++]=(uint16_t)(((c)>>10)+0xd7c0); \
+ (s)[(i)++]=(uint16_t)(((c)&0x3ff)|0xdc00); \
+ } \
+} UPRV_BLOCK_MACRO_END
+
+/**
+ * Append a code point to a string, overwriting 1 or 2 code units.
+ * The offset points to the current end of the string contents
+ * and is advanced (post-increment).
+ * "Safe" macro, checks for a valid code point.
+ * If a surrogate pair is written, checks for sufficient space in the string.
+ * If the code point is not valid or a trail surrogate does not fit,
+ * then isError is set to TRUE.
+ *
+ * @param s const UChar * string buffer
+ * @param i string offset, must be i<capacity
+ * @param capacity size of the string buffer
+ * @param c code point to append
+ * @param isError output UBool set to TRUE if an error occurs, otherwise not modified
+ * @see U16_APPEND_UNSAFE
+ * @stable ICU 2.4
+ */
+#define U16_APPEND(s, i, capacity, c, isError) UPRV_BLOCK_MACRO_BEGIN { \
+ if((uint32_t)(c)<=0xffff) { \
+ (s)[(i)++]=(uint16_t)(c); \
+ } else if((uint32_t)(c)<=0x10ffff && (i)+1<(capacity)) { \
+ (s)[(i)++]=(uint16_t)(((c)>>10)+0xd7c0); \
+ (s)[(i)++]=(uint16_t)(((c)&0x3ff)|0xdc00); \
+ } else /* c>0x10ffff or not enough space */ { \
+ (isError)=TRUE; \
+ } \
+} UPRV_BLOCK_MACRO_END
+
+/**
+ * Advance the string offset from one code point boundary to the next.
+ * (Post-incrementing iteration.)
+ * "Unsafe" macro, assumes well-formed UTF-16.
+ *
+ * @param s const UChar * string
+ * @param i string offset
+ * @see U16_FWD_1
+ * @stable ICU 2.4
+ */
+#define U16_FWD_1_UNSAFE(s, i) UPRV_BLOCK_MACRO_BEGIN { \
+ if(U16_IS_LEAD((s)[(i)++])) { \
+ ++(i); \
+ } \
+} UPRV_BLOCK_MACRO_END
+
+/**
+ * Advance the string offset from one code point boundary to the next.
+ * (Post-incrementing iteration.)
+ * "Safe" macro, handles unpaired surrogates and checks for string boundaries.
+ *
+ * The length can be negative for a NUL-terminated string.
+ *
+ * @param s const UChar * string
+ * @param i string offset, must be i<length
+ * @param length string length
+ * @see U16_FWD_1_UNSAFE
+ * @stable ICU 2.4
+ */
+#define U16_FWD_1(s, i, length) UPRV_BLOCK_MACRO_BEGIN { \
+ if(U16_IS_LEAD((s)[(i)++]) && (i)!=(length) && U16_IS_TRAIL((s)[i])) { \
+ ++(i); \
+ } \
+} UPRV_BLOCK_MACRO_END
+
+/**
+ * Advance the string offset from one code point boundary to the n-th next one,
+ * i.e., move forward by n code points.
+ * (Post-incrementing iteration.)
+ * "Unsafe" macro, assumes well-formed UTF-16.
+ *
+ * @param s const UChar * string
+ * @param i string offset
+ * @param n number of code points to skip
+ * @see U16_FWD_N
+ * @stable ICU 2.4
+ */
+#define U16_FWD_N_UNSAFE(s, i, n) UPRV_BLOCK_MACRO_BEGIN { \
+ int32_t __N=(n); \
+ while(__N>0) { \
+ U16_FWD_1_UNSAFE(s, i); \
+ --__N; \
+ } \
+} UPRV_BLOCK_MACRO_END
+
+/**
+ * Advance the string offset from one code point boundary to the n-th next one,
+ * i.e., move forward by n code points.
+ * (Post-incrementing iteration.)
+ * "Safe" macro, handles unpaired surrogates and checks for string boundaries.
+ *
+ * The length can be negative for a NUL-terminated string.
+ *
+ * @param s const UChar * string
+ * @param i int32_t string offset, must be i<length
+ * @param length int32_t string length
+ * @param n number of code points to skip
+ * @see U16_FWD_N_UNSAFE
+ * @stable ICU 2.4
+ */
+#define U16_FWD_N(s, i, length, n) UPRV_BLOCK_MACRO_BEGIN { \
+ int32_t __N=(n); \
+ while(__N>0 && ((i)<(length) || ((length)<0 && (s)[i]!=0))) { \
+ U16_FWD_1(s, i, length); \
+ --__N; \
+ } \
+} UPRV_BLOCK_MACRO_END
+
+/**
+ * Adjust a random-access offset to a code point boundary
+ * at the start of a code point.
+ * If the offset points to the trail surrogate of a surrogate pair,
+ * then the offset is decremented.
+ * Otherwise, it is not modified.
+ * "Unsafe" macro, assumes well-formed UTF-16.
+ *
+ * @param s const UChar * string
+ * @param i string offset
+ * @see U16_SET_CP_START
+ * @stable ICU 2.4
+ */
+#define U16_SET_CP_START_UNSAFE(s, i) UPRV_BLOCK_MACRO_BEGIN { \
+ if(U16_IS_TRAIL((s)[i])) { \
+ --(i); \
+ } \
+} UPRV_BLOCK_MACRO_END
+
+/**
+ * Adjust a random-access offset to a code point boundary
+ * at the start of a code point.
+ * If the offset points to the trail surrogate of a surrogate pair,
+ * then the offset is decremented.
+ * Otherwise, it is not modified.
+ * "Safe" macro, handles unpaired surrogates and checks for string boundaries.
+ *
+ * @param s const UChar * string
+ * @param start starting string offset (usually 0)
+ * @param i string offset, must be start<=i
+ * @see U16_SET_CP_START_UNSAFE
+ * @stable ICU 2.4
+ */
+#define U16_SET_CP_START(s, start, i) UPRV_BLOCK_MACRO_BEGIN { \
+ if(U16_IS_TRAIL((s)[i]) && (i)>(start) && U16_IS_LEAD((s)[(i)-1])) { \
+ --(i); \
+ } \
+} UPRV_BLOCK_MACRO_END
+
+/* definitions with backward iteration -------------------------------------- */
+
+/**
+ * Move the string offset from one code point boundary to the previous one
+ * and get the code point between them.
+ * (Pre-decrementing backward iteration.)
+ * "Unsafe" macro, assumes well-formed UTF-16.
+ *
+ * The input offset may be the same as the string length.
+ * If the offset is behind a trail surrogate unit
+ * for a supplementary code point, then the macro will read
+ * the preceding lead surrogate as well.
+ * If the offset is behind a lead surrogate, then that itself
+ * will be returned as the code point.
+ * The result is undefined if the offset is behind a single, unpaired trail surrogate.
+ *
+ * @param s const UChar * string
+ * @param i string offset
+ * @param c output UChar32 variable
+ * @see U16_PREV
+ * @stable ICU 2.4
+ */
+#define U16_PREV_UNSAFE(s, i, c) UPRV_BLOCK_MACRO_BEGIN { \
+ (c)=(s)[--(i)]; \
+ if(U16_IS_TRAIL(c)) { \
+ (c)=U16_GET_SUPPLEMENTARY((s)[--(i)], (c)); \
+ } \
+} UPRV_BLOCK_MACRO_END
+
+/**
+ * Move the string offset from one code point boundary to the previous one
+ * and get the code point between them.
+ * (Pre-decrementing backward iteration.)
+ * "Safe" macro, handles unpaired surrogates and checks for string boundaries.
+ *
+ * The input offset may be the same as the string length.
+ * If the offset is behind a trail surrogate unit
+ * for a supplementary code point, then the macro will read
+ * the preceding lead surrogate as well.
+ * If the offset is behind a lead surrogate or behind a single, unpaired
+ * trail surrogate, then c is set to that unpaired surrogate.
+ *
+ * @param s const UChar * string
+ * @param start starting string offset (usually 0)
+ * @param i string offset, must be start<i
+ * @param c output UChar32 variable
+ * @see U16_PREV_UNSAFE
+ * @stable ICU 2.4
+ */
+#define U16_PREV(s, start, i, c) UPRV_BLOCK_MACRO_BEGIN { \
+ (c)=(s)[--(i)]; \
+ if(U16_IS_TRAIL(c)) { \
+ uint16_t __c2; \
+ if((i)>(start) && U16_IS_LEAD(__c2=(s)[(i)-1])) { \
+ --(i); \
+ (c)=U16_GET_SUPPLEMENTARY(__c2, (c)); \
+ } \
+ } \
+} UPRV_BLOCK_MACRO_END
+
+/**
+ * Move the string offset from one code point boundary to the previous one
+ * and get the code point between them.
+ * (Pre-decrementing backward iteration.)
+ * "Safe" macro, handles unpaired surrogates and checks for string boundaries.
+ *
+ * The input offset may be the same as the string length.
+ * If the offset is behind a trail surrogate unit
+ * for a supplementary code point, then the macro will read
+ * the preceding lead surrogate as well.
+ * If the offset is behind a lead surrogate or behind a single, unpaired
+ * trail surrogate, then c is set to U+FFFD.
+ *
+ * @param s const UChar * string
+ * @param start starting string offset (usually 0)
+ * @param i string offset, must be start<i
+ * @param c output UChar32 variable
+ * @see U16_PREV_UNSAFE
+ * @stable ICU 60
+ */
+#define U16_PREV_OR_FFFD(s, start, i, c) UPRV_BLOCK_MACRO_BEGIN { \
+ (c)=(s)[--(i)]; \
+ if(U16_IS_SURROGATE(c)) { \
+ uint16_t __c2; \
+ if(U16_IS_SURROGATE_TRAIL(c) && (i)>(start) && U16_IS_LEAD(__c2=(s)[(i)-1])) { \
+ --(i); \
+ (c)=U16_GET_SUPPLEMENTARY(__c2, (c)); \
+ } else { \
+ (c)=0xfffd; \
+ } \
+ } \
+} UPRV_BLOCK_MACRO_END
+
+/**
+ * Move the string offset from one code point boundary to the previous one.
+ * (Pre-decrementing backward iteration.)
+ * The input offset may be the same as the string length.
+ * "Unsafe" macro, assumes well-formed UTF-16.
+ *
+ * @param s const UChar * string
+ * @param i string offset
+ * @see U16_BACK_1
+ * @stable ICU 2.4
+ */
+#define U16_BACK_1_UNSAFE(s, i) UPRV_BLOCK_MACRO_BEGIN { \
+ if(U16_IS_TRAIL((s)[--(i)])) { \
+ --(i); \
+ } \
+} UPRV_BLOCK_MACRO_END
+
+/**
+ * Move the string offset from one code point boundary to the previous one.
+ * (Pre-decrementing backward iteration.)
+ * The input offset may be the same as the string length.
+ * "Safe" macro, handles unpaired surrogates and checks for string boundaries.
+ *
+ * @param s const UChar * string
+ * @param start starting string offset (usually 0)
+ * @param i string offset, must be start<i
+ * @see U16_BACK_1_UNSAFE
+ * @stable ICU 2.4
+ */
+#define U16_BACK_1(s, start, i) UPRV_BLOCK_MACRO_BEGIN { \
+ if(U16_IS_TRAIL((s)[--(i)]) && (i)>(start) && U16_IS_LEAD((s)[(i)-1])) { \
+ --(i); \
+ } \
+} UPRV_BLOCK_MACRO_END
+
+/**
+ * Move the string offset from one code point boundary to the n-th one before it,
+ * i.e., move backward by n code points.
+ * (Pre-decrementing backward iteration.)
+ * The input offset may be the same as the string length.
+ * "Unsafe" macro, assumes well-formed UTF-16.
+ *
+ * @param s const UChar * string
+ * @param i string offset
+ * @param n number of code points to skip
+ * @see U16_BACK_N
+ * @stable ICU 2.4
+ */
+#define U16_BACK_N_UNSAFE(s, i, n) UPRV_BLOCK_MACRO_BEGIN { \
+ int32_t __N=(n); \
+ while(__N>0) { \
+ U16_BACK_1_UNSAFE(s, i); \
+ --__N; \
+ } \
+} UPRV_BLOCK_MACRO_END
+
+/**
+ * Move the string offset from one code point boundary to the n-th one before it,
+ * i.e., move backward by n code points.
+ * (Pre-decrementing backward iteration.)
+ * The input offset may be the same as the string length.
+ * "Safe" macro, handles unpaired surrogates and checks for string boundaries.
+ *
+ * @param s const UChar * string
+ * @param start start of string
+ * @param i string offset, must be start<i
+ * @param n number of code points to skip
+ * @see U16_BACK_N_UNSAFE
+ * @stable ICU 2.4
+ */
+#define U16_BACK_N(s, start, i, n) UPRV_BLOCK_MACRO_BEGIN { \
+ int32_t __N=(n); \
+ while(__N>0 && (i)>(start)) { \
+ U16_BACK_1(s, start, i); \
+ --__N; \
+ } \
+} UPRV_BLOCK_MACRO_END
+
+/**
+ * Adjust a random-access offset to a code point boundary after a code point.
+ * If the offset is behind the lead surrogate of a surrogate pair,
+ * then the offset is incremented.
+ * Otherwise, it is not modified.
+ * The input offset may be the same as the string length.
+ * "Unsafe" macro, assumes well-formed UTF-16.
+ *
+ * @param s const UChar * string
+ * @param i string offset
+ * @see U16_SET_CP_LIMIT
+ * @stable ICU 2.4
+ */
+#define U16_SET_CP_LIMIT_UNSAFE(s, i) UPRV_BLOCK_MACRO_BEGIN { \
+ if(U16_IS_LEAD((s)[(i)-1])) { \
+ ++(i); \
+ } \
+} UPRV_BLOCK_MACRO_END
+
+/**
+ * Adjust a random-access offset to a code point boundary after a code point.
+ * If the offset is behind the lead surrogate of a surrogate pair,
+ * then the offset is incremented.
+ * Otherwise, it is not modified.
+ * The input offset may be the same as the string length.
+ * "Safe" macro, handles unpaired surrogates and checks for string boundaries.
+ *
+ * The length can be negative for a NUL-terminated string.
+ *
+ * @param s const UChar * string
+ * @param start int32_t starting string offset (usually 0)
+ * @param i int32_t string offset, start<=i<=length
+ * @param length int32_t string length
+ * @see U16_SET_CP_LIMIT_UNSAFE
+ * @stable ICU 2.4
+ */
+#define U16_SET_CP_LIMIT(s, start, i, length) UPRV_BLOCK_MACRO_BEGIN { \
+ if((start)<(i) && ((i)<(length) || (length)<0) && U16_IS_LEAD((s)[(i)-1]) && U16_IS_TRAIL((s)[i])) { \
+ ++(i); \
+ } \
+} UPRV_BLOCK_MACRO_END
+
+#endif
diff --git a/src/tree_sitter/unicode/utf8.h b/src/tree_sitter/unicode/utf8.h
new file mode 100644
index 0000000000..3b37873e37
--- /dev/null
+++ b/src/tree_sitter/unicode/utf8.h
@@ -0,0 +1,881 @@
+// © 2016 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html
+/*
+*******************************************************************************
+*
+* Copyright (C) 1999-2015, International Business Machines
+* Corporation and others. All Rights Reserved.
+*
+*******************************************************************************
+* file name: utf8.h
+* encoding: UTF-8
+* tab size: 8 (not used)
+* indentation:4
+*
+* created on: 1999sep13
+* created by: Markus W. Scherer
+*/
+
+/**
+ * \file
+ * \brief C API: 8-bit Unicode handling macros
+ *
+ * This file defines macros to deal with 8-bit Unicode (UTF-8) code units (bytes) and strings.
+ *
+ * For more information see utf.h and the ICU User Guide Strings chapter
+ * (http://userguide.icu-project.org/strings).
+ *
+ * <em>Usage:</em>
+ * ICU coding guidelines for if() statements should be followed when using these macros.
+ * Compound statements (curly braces {}) must be used for if-else-while...
+ * bodies and all macro statements should be terminated with semicolon.
+ */
+
+#ifndef __UTF8_H__
+#define __UTF8_H__
+
+#include "./umachine.h"
+#ifndef __UTF_H__
+# include "./utf.h"
+#endif
+
+/* internal definitions ----------------------------------------------------- */
+
+/**
+ * Counts the trail bytes for a UTF-8 lead byte.
+ * Returns 0 for 0..0xc1 as well as for 0xf5..0xff.
+ * leadByte might be evaluated multiple times.
+ *
+ * This is internal since it is not meant to be called directly by external clients;
+ * however it is called by public macros in this file and thus must remain stable.
+ *
+ * @param leadByte The first byte of a UTF-8 sequence. Must be 0..0xff.
+ * @internal
+ */
+#define U8_COUNT_TRAIL_BYTES(leadByte) \
+ (U8_IS_LEAD(leadByte) ? \
+ ((uint8_t)(leadByte)>=0xe0)+((uint8_t)(leadByte)>=0xf0)+1 : 0)
+
+/**
+ * Counts the trail bytes for a UTF-8 lead byte of a valid UTF-8 sequence.
+ * Returns 0 for 0..0xc1. Undefined for 0xf5..0xff.
+ * leadByte might be evaluated multiple times.
+ *
+ * This is internal since it is not meant to be called directly by external clients;
+ * however it is called by public macros in this file and thus must remain stable.
+ *
+ * @param leadByte The first byte of a UTF-8 sequence. Must be 0..0xff.
+ * @internal
+ */
+#define U8_COUNT_TRAIL_BYTES_UNSAFE(leadByte) \
+ (((uint8_t)(leadByte)>=0xc2)+((uint8_t)(leadByte)>=0xe0)+((uint8_t)(leadByte)>=0xf0))
+
+/**
+ * Mask a UTF-8 lead byte, leave only the lower bits that form part of the code point value.
+ *
+ * This is internal since it is not meant to be called directly by external clients;
+ * however it is called by public macros in this file and thus must remain stable.
+ * @internal
+ */
+#define U8_MASK_LEAD_BYTE(leadByte, countTrailBytes) ((leadByte)&=(1<<(6-(countTrailBytes)))-1)
+
+/**
+ * Internal bit vector for 3-byte UTF-8 validity check, for use in U8_IS_VALID_LEAD3_AND_T1.
+ * Each bit indicates whether one lead byte + first trail byte pair starts a valid sequence.
+ * Lead byte E0..EF bits 3..0 are used as byte index,
+ * first trail byte bits 7..5 are used as bit index into that byte.
+ * @see U8_IS_VALID_LEAD3_AND_T1
+ * @internal
+ */
+#define U8_LEAD3_T1_BITS "\x20\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x10\x30\x30"
+
+/**
+ * Internal 3-byte UTF-8 validity check.
+ * Non-zero if lead byte E0..EF and first trail byte 00..FF start a valid sequence.
+ * @internal
+ */
+#define U8_IS_VALID_LEAD3_AND_T1(lead, t1) (U8_LEAD3_T1_BITS[(lead)&0xf]&(1<<((uint8_t)(t1)>>5)))
+
+/**
+ * Internal bit vector for 4-byte UTF-8 validity check, for use in U8_IS_VALID_LEAD4_AND_T1.
+ * Each bit indicates whether one lead byte + first trail byte pair starts a valid sequence.
+ * First trail byte bits 7..4 are used as byte index,
+ * lead byte F0..F4 bits 2..0 are used as bit index into that byte.
+ * @see U8_IS_VALID_LEAD4_AND_T1
+ * @internal
+ */
+#define U8_LEAD4_T1_BITS "\x00\x00\x00\x00\x00\x00\x00\x00\x1E\x0F\x0F\x0F\x00\x00\x00\x00"
+
+/**
+ * Internal 4-byte UTF-8 validity check.
+ * Non-zero if lead byte F0..F4 and first trail byte 00..FF start a valid sequence.
+ * @internal
+ */
+#define U8_IS_VALID_LEAD4_AND_T1(lead, t1) (U8_LEAD4_T1_BITS[(uint8_t)(t1)>>4]&(1<<((lead)&7)))
+
+/**
+ * Function for handling "next code point" with error-checking.
+ *
+ * This is internal since it is not meant to be called directly by external clients;
+ * however it is U_STABLE (not U_INTERNAL) since it is called by public macros in this
+ * file and thus must remain stable, and should not be hidden when other internal
+ * functions are hidden (otherwise public macros would fail to compile).
+ * @internal
+ */
+U_STABLE UChar32 U_EXPORT2
+utf8_nextCharSafeBody(const uint8_t *s, int32_t *pi, int32_t length, UChar32 c, UBool strict);
+
+/**
+ * Function for handling "append code point" with error-checking.
+ *
+ * This is internal since it is not meant to be called directly by external clients;
+ * however it is U_STABLE (not U_INTERNAL) since it is called by public macros in this
+ * file and thus must remain stable, and should not be hidden when other internal
+ * functions are hidden (otherwise public macros would fail to compile).
+ * @internal
+ */
+U_STABLE int32_t U_EXPORT2
+utf8_appendCharSafeBody(uint8_t *s, int32_t i, int32_t length, UChar32 c, UBool *pIsError);
+
+/**
+ * Function for handling "previous code point" with error-checking.
+ *
+ * This is internal since it is not meant to be called directly by external clients;
+ * however it is U_STABLE (not U_INTERNAL) since it is called by public macros in this
+ * file and thus must remain stable, and should not be hidden when other internal
+ * functions are hidden (otherwise public macros would fail to compile).
+ * @internal
+ */
+U_STABLE UChar32 U_EXPORT2
+utf8_prevCharSafeBody(const uint8_t *s, int32_t start, int32_t *pi, UChar32 c, UBool strict);
+
+/**
+ * Function for handling "skip backward one code point" with error-checking.
+ *
+ * This is internal since it is not meant to be called directly by external clients;
+ * however it is U_STABLE (not U_INTERNAL) since it is called by public macros in this
+ * file and thus must remain stable, and should not be hidden when other internal
+ * functions are hidden (otherwise public macros would fail to compile).
+ * @internal
+ */
+U_STABLE int32_t U_EXPORT2
+utf8_back1SafeBody(const uint8_t *s, int32_t start, int32_t i);
+
+/* single-code point definitions -------------------------------------------- */
+
+/**
+ * Does this code unit (byte) encode a code point by itself (US-ASCII 0..0x7f)?
+ * @param c 8-bit code unit (byte)
+ * @return TRUE or FALSE
+ * @stable ICU 2.4
+ */
+#define U8_IS_SINGLE(c) (((c)&0x80)==0)
+
+/**
+ * Is this code unit (byte) a UTF-8 lead byte? (0xC2..0xF4)
+ * @param c 8-bit code unit (byte)
+ * @return TRUE or FALSE
+ * @stable ICU 2.4
+ */
+#define U8_IS_LEAD(c) ((uint8_t)((c)-0xc2)<=0x32)
+// 0x32=0xf4-0xc2
+
+/**
+ * Is this code unit (byte) a UTF-8 trail byte? (0x80..0xBF)
+ * @param c 8-bit code unit (byte)
+ * @return TRUE or FALSE
+ * @stable ICU 2.4
+ */
+#define U8_IS_TRAIL(c) ((int8_t)(c)<-0x40)
+
+/**
+ * How many code units (bytes) are used for the UTF-8 encoding
+ * of this Unicode code point?
+ * @param c 32-bit code point
+ * @return 1..4, or 0 if c is a surrogate or not a Unicode code point
+ * @stable ICU 2.4
+ */
+#define U8_LENGTH(c) \
+ ((uint32_t)(c)<=0x7f ? 1 : \
+ ((uint32_t)(c)<=0x7ff ? 2 : \
+ ((uint32_t)(c)<=0xd7ff ? 3 : \
+ ((uint32_t)(c)<=0xdfff || (uint32_t)(c)>0x10ffff ? 0 : \
+ ((uint32_t)(c)<=0xffff ? 3 : 4)\
+ ) \
+ ) \
+ ) \
+ )
+
+/**
+ * The maximum number of UTF-8 code units (bytes) per Unicode code point (U+0000..U+10ffff).
+ * @return 4
+ * @stable ICU 2.4
+ */
+#define U8_MAX_LENGTH 4
+
+/**
+ * Get a code point from a string at a random-access offset,
+ * without changing the offset.
+ * The offset may point to either the lead byte or one of the trail bytes
+ * for a code point, in which case the macro will read all of the bytes
+ * for the code point.
+ * The result is undefined if the offset points to an illegal UTF-8
+ * byte sequence.
+ * Iteration through a string is more efficient with U8_NEXT_UNSAFE or U8_NEXT.
+ *
+ * @param s const uint8_t * string
+ * @param i string offset
+ * @param c output UChar32 variable
+ * @see U8_GET
+ * @stable ICU 2.4
+ */
+#define U8_GET_UNSAFE(s, i, c) UPRV_BLOCK_MACRO_BEGIN { \
+ int32_t _u8_get_unsafe_index=(int32_t)(i); \
+ U8_SET_CP_START_UNSAFE(s, _u8_get_unsafe_index); \
+ U8_NEXT_UNSAFE(s, _u8_get_unsafe_index, c); \
+} UPRV_BLOCK_MACRO_END
+
+/**
+ * Get a code point from a string at a random-access offset,
+ * without changing the offset.
+ * The offset may point to either the lead byte or one of the trail bytes
+ * for a code point, in which case the macro will read all of the bytes
+ * for the code point.
+ *
+ * The length can be negative for a NUL-terminated string.
+ *
+ * If the offset points to an illegal UTF-8 byte sequence, then
+ * c is set to a negative value.
+ * Iteration through a string is more efficient with U8_NEXT_UNSAFE or U8_NEXT.
+ *
+ * @param s const uint8_t * string
+ * @param start int32_t starting string offset
+ * @param i int32_t string offset, must be start<=i<length
+ * @param length int32_t string length
+ * @param c output UChar32 variable, set to <0 in case of an error
+ * @see U8_GET_UNSAFE
+ * @stable ICU 2.4
+ */
+#define U8_GET(s, start, i, length, c) UPRV_BLOCK_MACRO_BEGIN { \
+ int32_t _u8_get_index=(i); \
+ U8_SET_CP_START(s, start, _u8_get_index); \
+ U8_NEXT(s, _u8_get_index, length, c); \
+} UPRV_BLOCK_MACRO_END
+
+/**
+ * Get a code point from a string at a random-access offset,
+ * without changing the offset.
+ * The offset may point to either the lead byte or one of the trail bytes
+ * for a code point, in which case the macro will read all of the bytes
+ * for the code point.
+ *
+ * The length can be negative for a NUL-terminated string.
+ *
+ * If the offset points to an illegal UTF-8 byte sequence, then
+ * c is set to U+FFFD.
+ * Iteration through a string is more efficient with U8_NEXT_UNSAFE or U8_NEXT_OR_FFFD.
+ *
+ * This macro does not distinguish between a real U+FFFD in the text
+ * and U+FFFD returned for an ill-formed sequence.
+ * Use U8_GET() if that distinction is important.
+ *
+ * @param s const uint8_t * string
+ * @param start int32_t starting string offset
+ * @param i int32_t string offset, must be start<=i<length
+ * @param length int32_t string length
+ * @param c output UChar32 variable, set to U+FFFD in case of an error
+ * @see U8_GET
+ * @stable ICU 51
+ */
+#define U8_GET_OR_FFFD(s, start, i, length, c) UPRV_BLOCK_MACRO_BEGIN { \
+ int32_t _u8_get_index=(i); \
+ U8_SET_CP_START(s, start, _u8_get_index); \
+ U8_NEXT_OR_FFFD(s, _u8_get_index, length, c); \
+} UPRV_BLOCK_MACRO_END
+
+/* definitions with forward iteration --------------------------------------- */
+
+/**
+ * Get a code point from a string at a code point boundary offset,
+ * and advance the offset to the next code point boundary.
+ * (Post-incrementing forward iteration.)
+ * "Unsafe" macro, assumes well-formed UTF-8.
+ *
+ * The offset may point to the lead byte of a multi-byte sequence,
+ * in which case the macro will read the whole sequence.
+ * The result is undefined if the offset points to a trail byte
+ * or an illegal UTF-8 sequence.
+ *
+ * @param s const uint8_t * string
+ * @param i string offset
+ * @param c output UChar32 variable
+ * @see U8_NEXT
+ * @stable ICU 2.4
+ */
+#define U8_NEXT_UNSAFE(s, i, c) UPRV_BLOCK_MACRO_BEGIN { \
+ (c)=(uint8_t)(s)[(i)++]; \
+ if(!U8_IS_SINGLE(c)) { \
+ if((c)<0xe0) { \
+ (c)=(((c)&0x1f)<<6)|((s)[(i)++]&0x3f); \
+ } else if((c)<0xf0) { \
+ /* no need for (c&0xf) because the upper bits are truncated after <<12 in the cast to (UChar) */ \
+ (c)=(UChar)(((c)<<12)|(((s)[i]&0x3f)<<6)|((s)[(i)+1]&0x3f)); \
+ (i)+=2; \
+ } else { \
+ (c)=(((c)&7)<<18)|(((s)[i]&0x3f)<<12)|(((s)[(i)+1]&0x3f)<<6)|((s)[(i)+2]&0x3f); \
+ (i)+=3; \
+ } \
+ } \
+} UPRV_BLOCK_MACRO_END
+
+/**
+ * Get a code point from a string at a code point boundary offset,
+ * and advance the offset to the next code point boundary.
+ * (Post-incrementing forward iteration.)
+ * "Safe" macro, checks for illegal sequences and for string boundaries.
+ *
+ * The length can be negative for a NUL-terminated string.
+ *
+ * The offset may point to the lead byte of a multi-byte sequence,
+ * in which case the macro will read the whole sequence.
+ * If the offset points to a trail byte or an illegal UTF-8 sequence, then
+ * c is set to a negative value.
+ *
+ * @param s const uint8_t * string
+ * @param i int32_t string offset, must be i<length
+ * @param length int32_t string length
+ * @param c output UChar32 variable, set to <0 in case of an error
+ * @see U8_NEXT_UNSAFE
+ * @stable ICU 2.4
+ */
+#define U8_NEXT(s, i, length, c) U8_INTERNAL_NEXT_OR_SUB(s, i, length, c, U_SENTINEL)
+
+/**
+ * Get a code point from a string at a code point boundary offset,
+ * and advance the offset to the next code point boundary.
+ * (Post-incrementing forward iteration.)
+ * "Safe" macro, checks for illegal sequences and for string boundaries.
+ *
+ * The length can be negative for a NUL-terminated string.
+ *
+ * The offset may point to the lead byte of a multi-byte sequence,
+ * in which case the macro will read the whole sequence.
+ * If the offset points to a trail byte or an illegal UTF-8 sequence, then
+ * c is set to U+FFFD.
+ *
+ * This macro does not distinguish between a real U+FFFD in the text
+ * and U+FFFD returned for an ill-formed sequence.
+ * Use U8_NEXT() if that distinction is important.
+ *
+ * @param s const uint8_t * string
+ * @param i int32_t string offset, must be i<length
+ * @param length int32_t string length
+ * @param c output UChar32 variable, set to U+FFFD in case of an error
+ * @see U8_NEXT
+ * @stable ICU 51
+ */
+#define U8_NEXT_OR_FFFD(s, i, length, c) U8_INTERNAL_NEXT_OR_SUB(s, i, length, c, 0xfffd)
+
+/** @internal */
+#define U8_INTERNAL_NEXT_OR_SUB(s, i, length, c, sub) UPRV_BLOCK_MACRO_BEGIN { \
+ (c)=(uint8_t)(s)[(i)++]; \
+ if(!U8_IS_SINGLE(c)) { \
+ uint8_t __t = 0; \
+ if((i)!=(length) && \
+ /* fetch/validate/assemble all but last trail byte */ \
+ ((c)>=0xe0 ? \
+ ((c)<0xf0 ? /* U+0800..U+FFFF except surrogates */ \
+ U8_LEAD3_T1_BITS[(c)&=0xf]&(1<<((__t=(s)[i])>>5)) && \
+ (__t&=0x3f, 1) \
+ : /* U+10000..U+10FFFF */ \
+ ((c)-=0xf0)<=4 && \
+ U8_LEAD4_T1_BITS[(__t=(s)[i])>>4]&(1<<(c)) && \
+ ((c)=((c)<<6)|(__t&0x3f), ++(i)!=(length)) && \
+ (__t=(s)[i]-0x80)<=0x3f) && \
+ /* valid second-to-last trail byte */ \
+ ((c)=((c)<<6)|__t, ++(i)!=(length)) \
+ : /* U+0080..U+07FF */ \
+ (c)>=0xc2 && ((c)&=0x1f, 1)) && \
+ /* last trail byte */ \
+ (__t=(s)[i]-0x80)<=0x3f && \
+ ((c)=((c)<<6)|__t, ++(i), 1)) { \
+ } else { \
+ (c)=(sub); /* ill-formed*/ \
+ } \
+ } \
+} UPRV_BLOCK_MACRO_END
+
+/**
+ * Append a code point to a string, overwriting 1 to 4 bytes.
+ * The offset points to the current end of the string contents
+ * and is advanced (post-increment).
+ * "Unsafe" macro, assumes a valid code point and sufficient space in the string.
+ * Otherwise, the result is undefined.
+ *
+ * @param s const uint8_t * string buffer
+ * @param i string offset
+ * @param c code point to append
+ * @see U8_APPEND
+ * @stable ICU 2.4
+ */
+#define U8_APPEND_UNSAFE(s, i, c) UPRV_BLOCK_MACRO_BEGIN { \
+ uint32_t __uc=(c); \
+ if(__uc<=0x7f) { \
+ (s)[(i)++]=(uint8_t)__uc; \
+ } else { \
+ if(__uc<=0x7ff) { \
+ (s)[(i)++]=(uint8_t)((__uc>>6)|0xc0); \
+ } else { \
+ if(__uc<=0xffff) { \
+ (s)[(i)++]=(uint8_t)((__uc>>12)|0xe0); \
+ } else { \
+ (s)[(i)++]=(uint8_t)((__uc>>18)|0xf0); \
+ (s)[(i)++]=(uint8_t)(((__uc>>12)&0x3f)|0x80); \
+ } \
+ (s)[(i)++]=(uint8_t)(((__uc>>6)&0x3f)|0x80); \
+ } \
+ (s)[(i)++]=(uint8_t)((__uc&0x3f)|0x80); \
+ } \
+} UPRV_BLOCK_MACRO_END
+
+/**
+ * Append a code point to a string, overwriting 1 to 4 bytes.
+ * The offset points to the current end of the string contents
+ * and is advanced (post-increment).
+ * "Safe" macro, checks for a valid code point.
+ * If a non-ASCII code point is written, checks for sufficient space in the string.
+ * If the code point is not valid or trail bytes do not fit,
+ * then isError is set to TRUE.
+ *
+ * @param s const uint8_t * string buffer
+ * @param i int32_t string offset, must be i<capacity
+ * @param capacity int32_t size of the string buffer
+ * @param c UChar32 code point to append
+ * @param isError output UBool set to TRUE if an error occurs, otherwise not modified
+ * @see U8_APPEND_UNSAFE
+ * @stable ICU 2.4
+ */
+#define U8_APPEND(s, i, capacity, c, isError) UPRV_BLOCK_MACRO_BEGIN { \
+ uint32_t __uc=(c); \
+ if(__uc<=0x7f) { \
+ (s)[(i)++]=(uint8_t)__uc; \
+ } else if(__uc<=0x7ff && (i)+1<(capacity)) { \
+ (s)[(i)++]=(uint8_t)((__uc>>6)|0xc0); \
+ (s)[(i)++]=(uint8_t)((__uc&0x3f)|0x80); \
+ } else if((__uc<=0xd7ff || (0xe000<=__uc && __uc<=0xffff)) && (i)+2<(capacity)) { \
+ (s)[(i)++]=(uint8_t)((__uc>>12)|0xe0); \
+ (s)[(i)++]=(uint8_t)(((__uc>>6)&0x3f)|0x80); \
+ (s)[(i)++]=(uint8_t)((__uc&0x3f)|0x80); \
+ } else if(0xffff<__uc && __uc<=0x10ffff && (i)+3<(capacity)) { \
+ (s)[(i)++]=(uint8_t)((__uc>>18)|0xf0); \
+ (s)[(i)++]=(uint8_t)(((__uc>>12)&0x3f)|0x80); \
+ (s)[(i)++]=(uint8_t)(((__uc>>6)&0x3f)|0x80); \
+ (s)[(i)++]=(uint8_t)((__uc&0x3f)|0x80); \
+ } else { \
+ (isError)=TRUE; \
+ } \
+} UPRV_BLOCK_MACRO_END
+
+/**
+ * Advance the string offset from one code point boundary to the next.
+ * (Post-incrementing iteration.)
+ * "Unsafe" macro, assumes well-formed UTF-8.
+ *
+ * @param s const uint8_t * string
+ * @param i string offset
+ * @see U8_FWD_1
+ * @stable ICU 2.4
+ */
+#define U8_FWD_1_UNSAFE(s, i) UPRV_BLOCK_MACRO_BEGIN { \
+ (i)+=1+U8_COUNT_TRAIL_BYTES_UNSAFE((s)[i]); \
+} UPRV_BLOCK_MACRO_END
+
+/**
+ * Advance the string offset from one code point boundary to the next.
+ * (Post-incrementing iteration.)
+ * "Safe" macro, checks for illegal sequences and for string boundaries.
+ *
+ * The length can be negative for a NUL-terminated string.
+ *
+ * @param s const uint8_t * string
+ * @param i int32_t string offset, must be i<length
+ * @param length int32_t string length
+ * @see U8_FWD_1_UNSAFE
+ * @stable ICU 2.4
+ */
+#define U8_FWD_1(s, i, length) UPRV_BLOCK_MACRO_BEGIN { \
+ uint8_t __b=(s)[(i)++]; \
+ if(U8_IS_LEAD(__b) && (i)!=(length)) { \
+ uint8_t __t1=(s)[i]; \
+ if((0xe0<=__b && __b<0xf0)) { \
+ if(U8_IS_VALID_LEAD3_AND_T1(__b, __t1) && \
+ ++(i)!=(length) && U8_IS_TRAIL((s)[i])) { \
+ ++(i); \
+ } \
+ } else if(__b<0xe0) { \
+ if(U8_IS_TRAIL(__t1)) { \
+ ++(i); \
+ } \
+ } else /* c>=0xf0 */ { \
+ if(U8_IS_VALID_LEAD4_AND_T1(__b, __t1) && \
+ ++(i)!=(length) && U8_IS_TRAIL((s)[i]) && \
+ ++(i)!=(length) && U8_IS_TRAIL((s)[i])) { \
+ ++(i); \
+ } \
+ } \
+ } \
+} UPRV_BLOCK_MACRO_END
+
+/**
+ * Advance the string offset from one code point boundary to the n-th next one,
+ * i.e., move forward by n code points.
+ * (Post-incrementing iteration.)
+ * "Unsafe" macro, assumes well-formed UTF-8.
+ *
+ * @param s const uint8_t * string
+ * @param i string offset
+ * @param n number of code points to skip
+ * @see U8_FWD_N
+ * @stable ICU 2.4
+ */
+#define U8_FWD_N_UNSAFE(s, i, n) UPRV_BLOCK_MACRO_BEGIN { \
+ int32_t __N=(n); \
+ while(__N>0) { \
+ U8_FWD_1_UNSAFE(s, i); \
+ --__N; \
+ } \
+} UPRV_BLOCK_MACRO_END
+
+/**
+ * Advance the string offset from one code point boundary to the n-th next one,
+ * i.e., move forward by n code points.
+ * (Post-incrementing iteration.)
+ * "Safe" macro, checks for illegal sequences and for string boundaries.
+ *
+ * The length can be negative for a NUL-terminated string.
+ *
+ * @param s const uint8_t * string
+ * @param i int32_t string offset, must be i<length
+ * @param length int32_t string length
+ * @param n number of code points to skip
+ * @see U8_FWD_N_UNSAFE
+ * @stable ICU 2.4
+ */
+#define U8_FWD_N(s, i, length, n) UPRV_BLOCK_MACRO_BEGIN { \
+ int32_t __N=(n); \
+ while(__N>0 && ((i)<(length) || ((length)<0 && (s)[i]!=0))) { \
+ U8_FWD_1(s, i, length); \
+ --__N; \
+ } \
+} UPRV_BLOCK_MACRO_END
+
+/**
+ * Adjust a random-access offset to a code point boundary
+ * at the start of a code point.
+ * If the offset points to a UTF-8 trail byte,
+ * then the offset is moved backward to the corresponding lead byte.
+ * Otherwise, it is not modified.
+ * "Unsafe" macro, assumes well-formed UTF-8.
+ *
+ * @param s const uint8_t * string
+ * @param i string offset
+ * @see U8_SET_CP_START
+ * @stable ICU 2.4
+ */
+#define U8_SET_CP_START_UNSAFE(s, i) UPRV_BLOCK_MACRO_BEGIN { \
+ while(U8_IS_TRAIL((s)[i])) { --(i); } \
+} UPRV_BLOCK_MACRO_END
+
+/**
+ * Adjust a random-access offset to a code point boundary
+ * at the start of a code point.
+ * If the offset points to a UTF-8 trail byte,
+ * then the offset is moved backward to the corresponding lead byte.
+ * Otherwise, it is not modified.
+ *
+ * "Safe" macro, checks for illegal sequences and for string boundaries.
+ * Unlike U8_TRUNCATE_IF_INCOMPLETE(), this macro always reads s[i].
+ *
+ * @param s const uint8_t * string
+ * @param start int32_t starting string offset (usually 0)
+ * @param i int32_t string offset, must be start<=i
+ * @see U8_SET_CP_START_UNSAFE
+ * @see U8_TRUNCATE_IF_INCOMPLETE
+ * @stable ICU 2.4
+ */
+#define U8_SET_CP_START(s, start, i) UPRV_BLOCK_MACRO_BEGIN { \
+ if(U8_IS_TRAIL((s)[(i)])) { \
+ (i)=utf8_back1SafeBody(s, start, (i)); \
+ } \
+} UPRV_BLOCK_MACRO_END
+
+/**
+ * If the string ends with a UTF-8 byte sequence that is valid so far
+ * but incomplete, then reduce the length of the string to end before
+ * the lead byte of that incomplete sequence.
+ * For example, if the string ends with E1 80, the length is reduced by 2.
+ *
+ * In all other cases (the string ends with a complete sequence, or it is not
+ * possible for any further trail byte to extend the trailing sequence)
+ * the length remains unchanged.
+ *
+ * Useful for processing text split across multiple buffers
+ * (save the incomplete sequence for later)
+ * and for optimizing iteration
+ * (check for string length only once per character).
+ *
+ * "Safe" macro, checks for illegal sequences and for string boundaries.
+ * Unlike U8_SET_CP_START(), this macro never reads s[length].
+ *
+ * (In UTF-16, simply check for U16_IS_LEAD(last code unit).)
+ *
+ * @param s const uint8_t * string
+ * @param start int32_t starting string offset (usually 0)
+ * @param length int32_t string length (usually start<=length)
+ * @see U8_SET_CP_START
+ * @stable ICU 61
+ */
+#define U8_TRUNCATE_IF_INCOMPLETE(s, start, length) UPRV_BLOCK_MACRO_BEGIN { \
+ if((length)>(start)) { \
+ uint8_t __b1=s[(length)-1]; \
+ if(U8_IS_SINGLE(__b1)) { \
+ /* common ASCII character */ \
+ } else if(U8_IS_LEAD(__b1)) { \
+ --(length); \
+ } else if(U8_IS_TRAIL(__b1) && ((length)-2)>=(start)) { \
+ uint8_t __b2=s[(length)-2]; \
+ if(0xe0<=__b2 && __b2<=0xf4) { \
+ if(__b2<0xf0 ? U8_IS_VALID_LEAD3_AND_T1(__b2, __b1) : \
+ U8_IS_VALID_LEAD4_AND_T1(__b2, __b1)) { \
+ (length)-=2; \
+ } \
+ } else if(U8_IS_TRAIL(__b2) && ((length)-3)>=(start)) { \
+ uint8_t __b3=s[(length)-3]; \
+ if(0xf0<=__b3 && __b3<=0xf4 && U8_IS_VALID_LEAD4_AND_T1(__b3, __b2)) { \
+ (length)-=3; \
+ } \
+ } \
+ } \
+ } \
+} UPRV_BLOCK_MACRO_END
+
+/* definitions with backward iteration -------------------------------------- */
+
+/**
+ * Move the string offset from one code point boundary to the previous one
+ * and get the code point between them.
+ * (Pre-decrementing backward iteration.)
+ * "Unsafe" macro, assumes well-formed UTF-8.
+ *
+ * The input offset may be the same as the string length.
+ * If the offset is behind a multi-byte sequence, then the macro will read
+ * the whole sequence.
+ * If the offset is behind a lead byte, then that itself
+ * will be returned as the code point.
+ * The result is undefined if the offset is behind an illegal UTF-8 sequence.
+ *
+ * @param s const uint8_t * string
+ * @param i string offset
+ * @param c output UChar32 variable
+ * @see U8_PREV
+ * @stable ICU 2.4
+ */
+#define U8_PREV_UNSAFE(s, i, c) UPRV_BLOCK_MACRO_BEGIN { \
+ (c)=(uint8_t)(s)[--(i)]; \
+ if(U8_IS_TRAIL(c)) { \
+ uint8_t __b, __count=1, __shift=6; \
+\
+ /* c is a trail byte */ \
+ (c)&=0x3f; \
+ for(;;) { \
+ __b=(s)[--(i)]; \
+ if(__b>=0xc0) { \
+ U8_MASK_LEAD_BYTE(__b, __count); \
+ (c)|=(UChar32)__b<<__shift; \
+ break; \
+ } else { \
+ (c)|=(UChar32)(__b&0x3f)<<__shift; \
+ ++__count; \
+ __shift+=6; \
+ } \
+ } \
+ } \
+} UPRV_BLOCK_MACRO_END
+
+/**
+ * Move the string offset from one code point boundary to the previous one
+ * and get the code point between them.
+ * (Pre-decrementing backward iteration.)
+ * "Safe" macro, checks for illegal sequences and for string boundaries.
+ *
+ * The input offset may be the same as the string length.
+ * If the offset is behind a multi-byte sequence, then the macro will read
+ * the whole sequence.
+ * If the offset is behind a lead byte, then that itself
+ * will be returned as the code point.
+ * If the offset is behind an illegal UTF-8 sequence, then c is set to a negative value.
+ *
+ * @param s const uint8_t * string
+ * @param start int32_t starting string offset (usually 0)
+ * @param i int32_t string offset, must be start<i
+ * @param c output UChar32 variable, set to <0 in case of an error
+ * @see U8_PREV_UNSAFE
+ * @stable ICU 2.4
+ */
+#define U8_PREV(s, start, i, c) UPRV_BLOCK_MACRO_BEGIN { \
+ (c)=(uint8_t)(s)[--(i)]; \
+ if(!U8_IS_SINGLE(c)) { \
+ (c)=utf8_prevCharSafeBody((const uint8_t *)s, start, &(i), c, -1); \
+ } \
+} UPRV_BLOCK_MACRO_END
+
+/**
+ * Move the string offset from one code point boundary to the previous one
+ * and get the code point between them.
+ * (Pre-decrementing backward iteration.)
+ * "Safe" macro, checks for illegal sequences and for string boundaries.
+ *
+ * The input offset may be the same as the string length.
+ * If the offset is behind a multi-byte sequence, then the macro will read
+ * the whole sequence.
+ * If the offset is behind a lead byte, then that itself
+ * will be returned as the code point.
+ * If the offset is behind an illegal UTF-8 sequence, then c is set to U+FFFD.
+ *
+ * This macro does not distinguish between a real U+FFFD in the text
+ * and U+FFFD returned for an ill-formed sequence.
+ * Use U8_PREV() if that distinction is important.
+ *
+ * @param s const uint8_t * string
+ * @param start int32_t starting string offset (usually 0)
+ * @param i int32_t string offset, must be start<i
+ * @param c output UChar32 variable, set to U+FFFD in case of an error
+ * @see U8_PREV
+ * @stable ICU 51
+ */
+#define U8_PREV_OR_FFFD(s, start, i, c) UPRV_BLOCK_MACRO_BEGIN { \
+ (c)=(uint8_t)(s)[--(i)]; \
+ if(!U8_IS_SINGLE(c)) { \
+ (c)=utf8_prevCharSafeBody((const uint8_t *)s, start, &(i), c, -3); \
+ } \
+} UPRV_BLOCK_MACRO_END
+
+/**
+ * Move the string offset from one code point boundary to the previous one.
+ * (Pre-decrementing backward iteration.)
+ * The input offset may be the same as the string length.
+ * "Unsafe" macro, assumes well-formed UTF-8.
+ *
+ * @param s const uint8_t * string
+ * @param i string offset
+ * @see U8_BACK_1
+ * @stable ICU 2.4
+ */
+#define U8_BACK_1_UNSAFE(s, i) UPRV_BLOCK_MACRO_BEGIN { \
+ while(U8_IS_TRAIL((s)[--(i)])) {} \
+} UPRV_BLOCK_MACRO_END
+
+/**
+ * Move the string offset from one code point boundary to the previous one.
+ * (Pre-decrementing backward iteration.)
+ * The input offset may be the same as the string length.
+ * "Safe" macro, checks for illegal sequences and for string boundaries.
+ *
+ * @param s const uint8_t * string
+ * @param start int32_t starting string offset (usually 0)
+ * @param i int32_t string offset, must be start<i
+ * @see U8_BACK_1_UNSAFE
+ * @stable ICU 2.4
+ */
+#define U8_BACK_1(s, start, i) UPRV_BLOCK_MACRO_BEGIN { \
+ if(U8_IS_TRAIL((s)[--(i)])) { \
+ (i)=utf8_back1SafeBody(s, start, (i)); \
+ } \
+} UPRV_BLOCK_MACRO_END
+
+/**
+ * Move the string offset from one code point boundary to the n-th one before it,
+ * i.e., move backward by n code points.
+ * (Pre-decrementing backward iteration.)
+ * The input offset may be the same as the string length.
+ * "Unsafe" macro, assumes well-formed UTF-8.
+ *
+ * @param s const uint8_t * string
+ * @param i string offset
+ * @param n number of code points to skip
+ * @see U8_BACK_N
+ * @stable ICU 2.4
+ */
+#define U8_BACK_N_UNSAFE(s, i, n) UPRV_BLOCK_MACRO_BEGIN { \
+ int32_t __N=(n); \
+ while(__N>0) { \
+ U8_BACK_1_UNSAFE(s, i); \
+ --__N; \
+ } \
+} UPRV_BLOCK_MACRO_END
+
+/**
+ * Move the string offset from one code point boundary to the n-th one before it,
+ * i.e., move backward by n code points.
+ * (Pre-decrementing backward iteration.)
+ * The input offset may be the same as the string length.
+ * "Safe" macro, checks for illegal sequences and for string boundaries.
+ *
+ * @param s const uint8_t * string
+ * @param start int32_t index of the start of the string
+ * @param i int32_t string offset, must be start<i
+ * @param n number of code points to skip
+ * @see U8_BACK_N_UNSAFE
+ * @stable ICU 2.4
+ */
+#define U8_BACK_N(s, start, i, n) UPRV_BLOCK_MACRO_BEGIN { \
+ int32_t __N=(n); \
+ while(__N>0 && (i)>(start)) { \
+ U8_BACK_1(s, start, i); \
+ --__N; \
+ } \
+} UPRV_BLOCK_MACRO_END
+
+/**
+ * Adjust a random-access offset to a code point boundary after a code point.
+ * If the offset is behind a partial multi-byte sequence,
+ * then the offset is incremented to behind the whole sequence.
+ * Otherwise, it is not modified.
+ * The input offset may be the same as the string length.
+ * "Unsafe" macro, assumes well-formed UTF-8.
+ *
+ * @param s const uint8_t * string
+ * @param i string offset
+ * @see U8_SET_CP_LIMIT
+ * @stable ICU 2.4
+ */
+#define U8_SET_CP_LIMIT_UNSAFE(s, i) UPRV_BLOCK_MACRO_BEGIN { \
+ U8_BACK_1_UNSAFE(s, i); \
+ U8_FWD_1_UNSAFE(s, i); \
+} UPRV_BLOCK_MACRO_END
+
+/**
+ * Adjust a random-access offset to a code point boundary after a code point.
+ * If the offset is behind a partial multi-byte sequence,
+ * then the offset is incremented to behind the whole sequence.
+ * Otherwise, it is not modified.
+ * The input offset may be the same as the string length.
+ * "Safe" macro, checks for illegal sequences and for string boundaries.
+ *
+ * The length can be negative for a NUL-terminated string.
+ *
+ * @param s const uint8_t * string
+ * @param start int32_t starting string offset (usually 0)
+ * @param i int32_t string offset, must be start<=i<=length
+ * @param length int32_t string length
+ * @see U8_SET_CP_LIMIT_UNSAFE
+ * @stable ICU 2.4
+ */
+#define U8_SET_CP_LIMIT(s, start, i, length) UPRV_BLOCK_MACRO_BEGIN { \
+ if((start)<(i) && ((i)<(length) || (length)<0)) { \
+ U8_BACK_1(s, start, i); \
+ U8_FWD_1(s, i, length); \
+ } \
+} UPRV_BLOCK_MACRO_END
+
+#endif
diff --git a/test/README.md b/test/README.md
index 64892b5576..a6e9080a40 100644
--- a/test/README.md
+++ b/test/README.md
@@ -77,11 +77,14 @@ To run all legacy Vim tests:
make oldtest
-To run a *single* legacy test set `TEST_FILE`, for example:
+To run a *single* legacy test file you can use either:
- TEST_FILE=test_syntax.res make oldtest
+ make oldtest TEST_FILE=test_syntax.vim
+
+or:
+
+ make src/nvim/testdir/test_syntax.vim
-- The `.res` extension (instead of `.vim`) is required.
- Specify only the test file name, not the full path.
@@ -126,7 +129,7 @@ end)
To run only test with filter name:
- TEST_TAG='foo.*api' make functionaltest
+ TEST_FILTER='foo.*api' make functionaltest
### Filter by file
@@ -315,11 +318,12 @@ Number; !must be defined to function properly):
- `NVIM_TEST_RUN_TESTTEST` (U) (1): allows running
`test/unit/testtest_spec.lua` used to check how testing infrastructure works.
-- `NVIM_TEST_TRACE_LEVEL` (U) (N): specifies unit tests tracing level: `0`
- disables tracing (the fastest, but you get no data if tests crash and there
- was no core dump generated), `1` or empty/undefined leaves only C function
- cals and returns in the trace (faster then recording everything), `2` records
- all function calls, returns and lua source lines exuecuted.
+- `NVIM_TEST_TRACE_LEVEL` (U) (N): specifies unit tests tracing level:
+ - `0` disables tracing (the fastest, but you get no data if tests crash and
+ there no core dump was generated),
+ - `1` leaves only C function calls and returns in the trace (faster than
+ recording everything),
+ - `2` records all function calls, returns and executed Lua source lines.
- `NVIM_TEST_TRACE_ON_ERROR` (U) (1): makes unit tests yield trace on error in
addition to regular error message.
diff --git a/test/busted/outputHandlers/TAP.lua b/test/busted/outputHandlers/TAP.lua
index 8dc4ff55b6..5de48c0ad3 100644
--- a/test/busted/outputHandlers/TAP.lua
+++ b/test/busted/outputHandlers/TAP.lua
@@ -7,7 +7,7 @@ return function(options)
local handler = require 'busted.outputHandlers.TAP'(options)
local suiteEnd = function()
- io.write(global_helpers.read_nvim_log())
+ io.write(global_helpers.read_nvim_log(nil, true))
return nil, true
end
busted.subscribe({ 'suite', 'end' }, suiteEnd)
diff --git a/test/busted/outputHandlers/nvim.lua b/test/busted/outputHandlers/nvim.lua
index 1b500fc999..5456e9ca98 100644
--- a/test/busted/outputHandlers/nvim.lua
+++ b/test/busted/outputHandlers/nvim.lua
@@ -196,7 +196,7 @@ return function(options)
local tests = (testCount == 1 and 'test' or 'tests')
local files = (fileCount == 1 and 'file' or 'files')
io.write(globalTeardown)
- io.write(global_helpers.read_nvim_log())
+ io.write(global_helpers.read_nvim_log(nil, true))
io.write(suiteEndString:format(testCount, tests, fileCount, files, elapsedTime_ms))
io.write(getSummaryString())
io.flush()
@@ -227,8 +227,12 @@ return function(options)
return nil, true
end
+ local function write_status(element, string)
+ io.write(timeString:format(getElapsedTime(element)) .. ' ' .. string)
+ io.flush()
+ end
+
handler.testEnd = function(element, _parent, status, _debug)
- local elapsedTime_ms = getElapsedTime(element)
local string
fileTestCount = fileTestCount + 1
@@ -241,45 +245,21 @@ return function(options)
string = skippedString
elseif status == 'failure' then
failureCount = failureCount + 1
- string = nil
+ string = failureString .. failureDescription(handler.failures[#handler.failures])
elseif status == 'error' then
errorCount = errorCount + 1
- string = nil
- end
-
- if string ~= nil then
- if elapsedTime_ms == elapsedTime_ms then
- string = timeString:format(elapsedTime_ms) .. ' ' .. string
- end
- io.write(string)
- io.flush()
+ string = errorString .. failureDescription(handler.errors[#handler.errors])
+ else
+ string = "unexpected test status! ("..status..")"
end
+ write_status(element, string)
return nil, true
end
- handler.testFailure = function(_element, _parent, _message, _debug)
- io.write(failureString)
- io.flush()
-
- io.write(failureDescription(handler.failures[#handler.failures]))
- io.flush()
- return nil, true
- end
-
- handler.testError = function(_element, _parent, _message, _debug)
- io.write(errorString)
- io.flush()
-
- io.write(failureDescription(handler.errors[#handler.errors]))
- io.flush()
- return nil, true
- end
-
handler.error = function(element, _parent, _message, _debug)
if element.descriptor ~= 'it' then
- io.write(failureDescription(handler.errors[#handler.errors]))
- io.flush()
+ write_status(element, failureDescription(handler.errors[#handler.errors]))
errorCount = errorCount + 1
end
@@ -293,8 +273,6 @@ return function(options)
busted.subscribe({ 'file', 'end' }, handler.fileEnd)
busted.subscribe({ 'test', 'start' }, handler.testStart, { predicate = handler.cancelOnPending })
busted.subscribe({ 'test', 'end' }, handler.testEnd, { predicate = handler.cancelOnPending })
- busted.subscribe({ 'failure', 'it' }, handler.testFailure)
- busted.subscribe({ 'error', 'it' }, handler.testError)
busted.subscribe({ 'failure' }, handler.error)
busted.subscribe({ 'error' }, handler.error)
diff --git a/test/functional/api/extmark_spec.lua b/test/functional/api/extmark_spec.lua
new file mode 100644
index 0000000000..9ea35e50a2
--- /dev/null
+++ b/test/functional/api/extmark_spec.lua
@@ -0,0 +1,1477 @@
+local helpers = require('test.functional.helpers')(after_each)
+local Screen = require('test.functional.ui.screen')
+
+local request = helpers.request
+local eq = helpers.eq
+local ok = helpers.ok
+local curbufmeths = helpers.curbufmeths
+local bufmeths = helpers.bufmeths
+local pcall_err = helpers.pcall_err
+local insert = helpers.insert
+local feed = helpers.feed
+local clear = helpers.clear
+local command = helpers.command
+local meths = helpers.meths
+
+local function expect(contents)
+ return eq(contents, helpers.curbuf_contents())
+end
+
+local function check_undo_redo(ns, mark, sr, sc, er, ec) --s = start, e = end
+ local rv = curbufmeths.get_extmark_by_id(ns, mark)
+ eq({er, ec}, rv)
+ feed("u")
+ rv = curbufmeths.get_extmark_by_id(ns, mark)
+ eq({sr, sc}, rv)
+ feed("<c-r>")
+ rv = curbufmeths.get_extmark_by_id(ns, mark)
+ eq({er, ec}, rv)
+end
+
+local function set_extmark(ns_id, id, line, col, opts)
+ if opts == nil then
+ opts = {}
+ end
+ return curbufmeths.set_extmark(ns_id, id, line, col, opts)
+end
+
+local function get_extmarks(ns_id, start, end_, opts)
+ if opts == nil then
+ opts = {}
+ end
+ return curbufmeths.get_extmarks(ns_id, start, end_, opts)
+end
+
+local function batch_set(ns_id, positions)
+ local ids = {}
+ for _, pos in ipairs(positions) do
+ table.insert(ids, set_extmark(ns_id, 0, pos[1], pos[2]))
+ end
+ return ids
+end
+
+local function batch_check(ns_id, ids, positions)
+ local actual, expected = {}, {}
+ for i,id in ipairs(ids) do
+ expected[id] = positions[i]
+ end
+ for _, mark in pairs(get_extmarks(ns_id, 0, -1, {})) do
+ actual[mark[1]] = {mark[2], mark[3]}
+ end
+ eq(expected, actual)
+end
+
+local function batch_check_undo_redo(ns_id, ids, before, after)
+ batch_check(ns_id, ids, after)
+ feed("u")
+ batch_check(ns_id, ids, before)
+ feed("<c-r>")
+ batch_check(ns_id, ids, after)
+end
+
+describe('API/extmarks', function()
+ local screen
+ local marks, positions, init_text, row, col
+ local ns, ns2
+
+ before_each(function()
+ -- Initialize some namespaces and insert 12345 into a buffer
+ marks = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}
+ positions = {{0, 0,}, {0, 2}, {0, 3}}
+
+ init_text = "12345"
+ row = 0
+ col = 2
+
+ clear()
+
+ insert(init_text)
+ ns = request('nvim_create_namespace', "my-fancy-plugin")
+ ns2 = request('nvim_create_namespace', "my-fancy-plugin2")
+ end)
+
+ it('adds, updates and deletes marks', function()
+ local rv = set_extmark(ns, marks[1], positions[1][1], positions[1][2])
+ eq(marks[1], rv)
+ rv = curbufmeths.get_extmark_by_id(ns, marks[1])
+ eq({positions[1][1], positions[1][2]}, rv)
+ -- Test adding a second mark on same row works
+ rv = set_extmark(ns, marks[2], positions[2][1], positions[2][2])
+ eq(marks[2], rv)
+
+ -- Test an update, (same pos)
+ rv = set_extmark(ns, marks[1], positions[1][1], positions[1][2])
+ eq(marks[1], rv)
+ rv = curbufmeths.get_extmark_by_id(ns, marks[2])
+ eq({positions[2][1], positions[2][2]}, rv)
+ -- Test an update, (new pos)
+ row = positions[1][1]
+ col = positions[1][2] + 1
+ rv = set_extmark(ns, marks[1], row, col)
+ eq(marks[1], rv)
+ rv = curbufmeths.get_extmark_by_id(ns, marks[1])
+ eq({row, col}, rv)
+
+ -- remove the test marks
+ eq(true, curbufmeths.del_extmark(ns, marks[1]))
+ eq(false, curbufmeths.del_extmark(ns, marks[1]))
+ eq(true, curbufmeths.del_extmark(ns, marks[2]))
+ eq(false, curbufmeths.del_extmark(ns, marks[3]))
+ eq(false, curbufmeths.del_extmark(ns, 1000))
+ end)
+
+ it('can clear a specific namespace range', function()
+ set_extmark(ns, 1, 0, 1)
+ set_extmark(ns2, 1, 0, 1)
+ -- force a new undo buffer
+ feed('o<esc>')
+ curbufmeths.clear_namespace(ns2, 0, -1)
+ eq({{1, 0, 1}}, get_extmarks(ns, {0, 0}, {-1, -1}))
+ eq({}, get_extmarks(ns2, {0, 0}, {-1, -1}))
+ feed('u')
+ eq({{1, 0, 1}}, get_extmarks(ns, {0, 0}, {-1, -1}))
+ eq({}, get_extmarks(ns2, {0, 0}, {-1, -1}))
+ feed('<c-r>')
+ eq({{1, 0, 1}}, get_extmarks(ns, {0, 0}, {-1, -1}))
+ eq({}, get_extmarks(ns2, {0, 0}, {-1, -1}))
+ end)
+
+ it('can clear a namespace range using 0,-1', function()
+ set_extmark(ns, 1, 0, 1)
+ set_extmark(ns2, 1, 0, 1)
+ -- force a new undo buffer
+ feed('o<esc>')
+ curbufmeths.clear_namespace(-1, 0, -1)
+ eq({}, get_extmarks(ns, {0, 0}, {-1, -1}))
+ eq({}, get_extmarks(ns2, {0, 0}, {-1, -1}))
+ feed('u')
+ eq({}, get_extmarks(ns, {0, 0}, {-1, -1}))
+ eq({}, get_extmarks(ns2, {0, 0}, {-1, -1}))
+ feed('<c-r>')
+ eq({}, get_extmarks(ns, {0, 0}, {-1, -1}))
+ eq({}, get_extmarks(ns2, {0, 0}, {-1, -1}))
+ end)
+
+ it('querying for information and ranges', function()
+ --marks = {1, 2, 3}
+ --positions = {{0, 0,}, {0, 2}, {0, 3}}
+ -- add some more marks
+ for i, m in ipairs(marks) do
+ if positions[i] ~= nil then
+ local rv = set_extmark(ns, m, positions[i][1], positions[i][2])
+ eq(m, rv)
+ end
+ end
+
+ -- {0, 0} and {-1, -1} work as extreme values
+ eq({{1, 0, 0}}, get_extmarks(ns, {0, 0}, {0, 0}))
+ eq({}, get_extmarks(ns, {-1, -1}, {-1, -1}))
+ local rv = get_extmarks(ns, {0, 0}, {-1, -1})
+ for i, m in ipairs(marks) do
+ if positions[i] ~= nil then
+ eq({m, positions[i][1], positions[i][2]}, rv[i])
+ end
+ end
+
+ -- 0 and -1 works as short hand extreme values
+ eq({{1, 0, 0}}, get_extmarks(ns, 0, 0))
+ eq({}, get_extmarks(ns, -1, -1))
+ rv = get_extmarks(ns, 0, -1)
+ for i, m in ipairs(marks) do
+ if positions[i] ~= nil then
+ eq({m, positions[i][1], positions[i][2]}, rv[i])
+ end
+ end
+
+ -- next with mark id
+ rv = get_extmarks(ns, marks[1], {-1, -1}, {limit=1})
+ eq({{marks[1], positions[1][1], positions[1][2]}}, rv)
+ rv = get_extmarks(ns, marks[2], {-1, -1}, {limit=1})
+ eq({{marks[2], positions[2][1], positions[2][2]}}, rv)
+ -- next with positional when mark exists at position
+ rv = get_extmarks(ns, positions[1], {-1, -1}, {limit=1})
+ eq({{marks[1], positions[1][1], positions[1][2]}}, rv)
+ -- next with positional index (no mark at position)
+ rv = get_extmarks(ns, {positions[1][1], positions[1][2] +1}, {-1, -1}, {limit=1})
+ eq({{marks[2], positions[2][1], positions[2][2]}}, rv)
+ -- next with Extremity index
+ rv = get_extmarks(ns, {0,0}, {-1, -1}, {limit=1})
+ eq({{marks[1], positions[1][1], positions[1][2]}}, rv)
+
+ -- nextrange with mark id
+ rv = get_extmarks(ns, marks[1], marks[3])
+ eq({marks[1], positions[1][1], positions[1][2]}, rv[1])
+ eq({marks[2], positions[2][1], positions[2][2]}, rv[2])
+ -- nextrange with `limit`
+ rv = get_extmarks(ns, marks[1], marks[3], {limit=2})
+ eq(2, #rv)
+ -- nextrange with positional when mark exists at position
+ rv = get_extmarks(ns, positions[1], positions[3])
+ eq({marks[1], positions[1][1], positions[1][2]}, rv[1])
+ eq({marks[2], positions[2][1], positions[2][2]}, rv[2])
+ rv = get_extmarks(ns, positions[2], positions[3])
+ eq(2, #rv)
+ -- nextrange with positional index (no mark at position)
+ local lower = {positions[1][1], positions[2][2] -1}
+ local upper = {positions[2][1], positions[3][2] - 1}
+ rv = get_extmarks(ns, lower, upper)
+ eq({{marks[2], positions[2][1], positions[2][2]}}, rv)
+ lower = {positions[3][1], positions[3][2] + 1}
+ upper = {positions[3][1], positions[3][2] + 2}
+ rv = get_extmarks(ns, lower, upper)
+ eq({}, rv)
+ -- nextrange with extremity index
+ lower = {positions[2][1], positions[2][2]+1}
+ upper = {-1, -1}
+ rv = get_extmarks(ns, lower, upper)
+ eq({{marks[3], positions[3][1], positions[3][2]}}, rv)
+
+ -- prev with mark id
+ rv = get_extmarks(ns, marks[3], {0, 0}, {limit=1})
+ eq({{marks[3], positions[3][1], positions[3][2]}}, rv)
+ rv = get_extmarks(ns, marks[2], {0, 0}, {limit=1})
+ eq({{marks[2], positions[2][1], positions[2][2]}}, rv)
+ -- prev with positional when mark exists at position
+ rv = get_extmarks(ns, positions[3], {0, 0}, {limit=1})
+ eq({{marks[3], positions[3][1], positions[3][2]}}, rv)
+ -- prev with positional index (no mark at position)
+ rv = get_extmarks(ns, {positions[1][1], positions[1][2] +1}, {0, 0}, {limit=1})
+ eq({{marks[1], positions[1][1], positions[1][2]}}, rv)
+ -- prev with Extremity index
+ rv = get_extmarks(ns, {-1,-1}, {0,0}, {limit=1})
+ eq({{marks[3], positions[3][1], positions[3][2]}}, rv)
+
+ -- prevrange with mark id
+ rv = get_extmarks(ns, marks[3], marks[1])
+ eq({marks[3], positions[3][1], positions[3][2]}, rv[1])
+ eq({marks[2], positions[2][1], positions[2][2]}, rv[2])
+ eq({marks[1], positions[1][1], positions[1][2]}, rv[3])
+ -- prevrange with limit
+ rv = get_extmarks(ns, marks[3], marks[1], {limit=2})
+ eq(2, #rv)
+ -- prevrange with positional when mark exists at position
+ rv = get_extmarks(ns, positions[3], positions[1])
+ eq({{marks[3], positions[3][1], positions[3][2]},
+ {marks[2], positions[2][1], positions[2][2]},
+ {marks[1], positions[1][1], positions[1][2]}}, rv)
+ rv = get_extmarks(ns, positions[2], positions[1])
+ eq(2, #rv)
+ -- prevrange with positional index (no mark at position)
+ lower = {positions[2][1], positions[2][2] + 1}
+ upper = {positions[3][1], positions[3][2] + 1}
+ rv = get_extmarks(ns, upper, lower)
+ eq({{marks[3], positions[3][1], positions[3][2]}}, rv)
+ lower = {positions[3][1], positions[3][2] + 1}
+ upper = {positions[3][1], positions[3][2] + 2}
+ rv = get_extmarks(ns, upper, lower)
+ eq({}, rv)
+ -- prevrange with extremity index
+ lower = {0,0}
+ upper = {positions[2][1], positions[2][2] - 1}
+ rv = get_extmarks(ns, upper, lower)
+ eq({{marks[1], positions[1][1], positions[1][2]}}, rv)
+ end)
+
+ it('querying for information with limit', function()
+ -- add some more marks
+ for i, m in ipairs(marks) do
+ if positions[i] ~= nil then
+ local rv = set_extmark(ns, m, positions[i][1], positions[i][2])
+ eq(m, rv)
+ end
+ end
+
+ local rv = get_extmarks(ns, {0, 0}, {-1, -1}, {limit=1})
+ eq(1, #rv)
+ rv = get_extmarks(ns, {0, 0}, {-1, -1}, {limit=2})
+ eq(2, #rv)
+ rv = get_extmarks(ns, {0, 0}, {-1, -1}, {limit=3})
+ eq(3, #rv)
+
+ -- now in reverse
+ rv = get_extmarks(ns, {0, 0}, {-1, -1}, {limit=1})
+ eq(1, #rv)
+ rv = get_extmarks(ns, {0, 0}, {-1, -1}, {limit=2})
+ eq(2, #rv)
+ rv = get_extmarks(ns, {0, 0}, {-1, -1}, {limit=3})
+ eq(3, #rv)
+ end)
+
+ it('get_marks works when mark col > upper col', function()
+ feed('A<cr>12345<esc>')
+ feed('A<cr>12345<esc>')
+ set_extmark(ns, 10, 0, 2) -- this shouldn't be found
+ set_extmark(ns, 11, 2, 1) -- this shouldn't be found
+ set_extmark(ns, marks[1], 0, 4) -- check col > our upper bound
+ set_extmark(ns, marks[2], 1, 1) -- check col < lower bound
+ set_extmark(ns, marks[3], 2, 0) -- check is inclusive
+ eq({{marks[1], 0, 4},
+ {marks[2], 1, 1},
+ {marks[3], 2, 0}},
+ get_extmarks(ns, {0, 3}, {2, 0}))
+ end)
+
+ it('get_marks works in reverse when mark col < lower col', function()
+ feed('A<cr>12345<esc>')
+ feed('A<cr>12345<esc>')
+ set_extmark(ns, 10, 0, 1) -- this shouldn't be found
+ set_extmark(ns, 11, 2, 4) -- this shouldn't be found
+ set_extmark(ns, marks[1], 2, 1) -- check col < our lower bound
+ set_extmark(ns, marks[2], 1, 4) -- check col > upper bound
+ set_extmark(ns, marks[3], 0, 2) -- check is inclusive
+ local rv = get_extmarks(ns, {2, 3}, {0, 2})
+ eq({{marks[1], 2, 1},
+ {marks[2], 1, 4},
+ {marks[3], 0, 2}},
+ rv)
+ end)
+
+ it('get_marks limit=0 returns nothing', function()
+ set_extmark(ns, marks[1], positions[1][1], positions[1][2])
+ local rv = get_extmarks(ns, {-1, -1}, {-1, -1}, {limit=0})
+ eq({}, rv)
+ end)
+
+
+ it('marks move with line insertations', function()
+ set_extmark(ns, marks[1], 0, 0)
+ feed("yyP")
+ check_undo_redo(ns, marks[1], 0, 0, 1, 0)
+ end)
+
+ it('marks move with multiline insertations', function()
+ feed("a<cr>22<cr>33<esc>")
+ set_extmark(ns, marks[1], 1, 1)
+ feed('ggVGyP')
+ check_undo_redo(ns, marks[1], 1, 1, 4, 1)
+ end)
+
+ it('marks move with line join', function()
+ -- do_join in ops.c
+ feed("a<cr>222<esc>")
+ set_extmark(ns, marks[1], 1, 0)
+ feed('ggJ')
+ check_undo_redo(ns, marks[1], 1, 0, 0, 6)
+ end)
+
+ it('join works when no marks are present', function()
+ screen = Screen.new(15, 10)
+ screen:attach()
+ feed("a<cr>1<esc>")
+ feed('kJ')
+ -- This shouldn't seg fault
+ screen:expect([[
+ 12345^ 1 |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ |
+ ]])
+ end)
+
+ it('marks move with multiline join', function()
+ -- do_join in ops.c
+ feed("a<cr>222<cr>333<cr>444<esc>")
+ set_extmark(ns, marks[1], 3, 0)
+ feed('2GVGJ')
+ check_undo_redo(ns, marks[1], 3, 0, 1, 8)
+ end)
+
+ it('marks move with line deletes', function()
+ feed("a<cr>222<cr>333<cr>444<esc>")
+ set_extmark(ns, marks[1], 2, 1)
+ feed('ggjdd')
+ check_undo_redo(ns, marks[1], 2, 1, 1, 1)
+ end)
+
+ it('marks move with multiline deletes', function()
+ feed("a<cr>222<cr>333<cr>444<esc>")
+ set_extmark(ns, marks[1], 3, 0)
+ feed('gg2dd')
+ check_undo_redo(ns, marks[1], 3, 0, 1, 0)
+ -- regression test, undoing multiline delete when mark is on row 1
+ feed('ugg3dd')
+ check_undo_redo(ns, marks[1], 3, 0, 0, 0)
+ end)
+
+ it('marks move with open line', function()
+ -- open_line in misc1.c
+ -- testing marks below are also moved
+ feed("yyP")
+ set_extmark(ns, marks[1], 0, 4)
+ set_extmark(ns, marks[2], 1, 4)
+ feed('1G<s-o><esc>')
+ check_undo_redo(ns, marks[1], 0, 4, 1, 4)
+ check_undo_redo(ns, marks[2], 1, 4, 2, 4)
+ feed('2Go<esc>')
+ check_undo_redo(ns, marks[1], 1, 4, 1, 4)
+ check_undo_redo(ns, marks[2], 2, 4, 3, 4)
+ end)
+
+ it('marks move with char inserts', function()
+ -- insertchar in edit.c (the ins_str branch)
+ screen = Screen.new(15, 10)
+ screen:attach()
+ set_extmark(ns, marks[1], 0, 3)
+ feed('0')
+ insert('abc')
+ screen:expect([[
+ ab^c12345 |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ |
+ ]])
+ local rv = curbufmeths.get_extmark_by_id(ns, marks[1])
+ eq({0, 6}, rv)
+ check_undo_redo(ns, marks[1], 0, 3, 0, 6)
+ end)
+
+ -- gravity right as definted in tk library
+ it('marks have gravity right', function()
+ -- insertchar in edit.c (the ins_str branch)
+ set_extmark(ns, marks[1], 0, 2)
+ feed('03l')
+ insert("X")
+ check_undo_redo(ns, marks[1], 0, 2, 0, 2)
+
+ -- check multibyte chars
+ feed('03l<esc>')
+ insert("~~")
+ check_undo_redo(ns, marks[1], 0, 2, 0, 2)
+ end)
+
+ it('we can insert multibyte chars', function()
+ -- insertchar in edit.c
+ feed('a<cr>12345<esc>')
+ set_extmark(ns, marks[1], 1, 2)
+ -- Insert a fullwidth (two col) tilde, NICE
+ feed('0i~<esc>')
+ check_undo_redo(ns, marks[1], 1, 2, 1, 5)
+ end)
+
+ it('marks move with blockwise inserts', function()
+ -- op_insert in ops.c
+ feed('a<cr>12345<esc>')
+ set_extmark(ns, marks[1], 1, 2)
+ feed('0<c-v>lkI9<esc>')
+ check_undo_redo(ns, marks[1], 1, 2, 1, 3)
+ end)
+
+ it('marks move with line splits (using enter)', function()
+ -- open_line in misc1.c
+ -- testing marks below are also moved
+ feed("yyP")
+ set_extmark(ns, marks[1], 0, 4)
+ set_extmark(ns, marks[2], 1, 4)
+ feed('1Gla<cr><esc>')
+ check_undo_redo(ns, marks[1], 0, 4, 1, 2)
+ check_undo_redo(ns, marks[2], 1, 4, 2, 4)
+ end)
+
+ it('marks at last line move on insert new line', function()
+ -- open_line in misc1.c
+ set_extmark(ns, marks[1], 0, 4)
+ feed('0i<cr><esc>')
+ check_undo_redo(ns, marks[1], 0, 4, 1, 4)
+ end)
+
+ it('yet again marks move with line splits', function()
+ -- the first test above wasn't catching all errors..
+ feed("A67890<esc>")
+ set_extmark(ns, marks[1], 0, 4)
+ feed("04li<cr><esc>")
+ check_undo_redo(ns, marks[1], 0, 4, 1, 0)
+ end)
+
+ it('and one last time line splits...', function()
+ set_extmark(ns, marks[1], 0, 1)
+ set_extmark(ns, marks[2], 0, 2)
+ feed("02li<cr><esc>")
+ check_undo_redo(ns, marks[1], 0, 1, 0, 1)
+ check_undo_redo(ns, marks[2], 0, 2, 1, 0)
+ end)
+
+ it('multiple marks move with mark splits', function()
+ set_extmark(ns, marks[1], 0, 1)
+ set_extmark(ns, marks[2], 0, 3)
+ feed("0li<cr><esc>")
+ check_undo_redo(ns, marks[1], 0, 1, 1, 0)
+ check_undo_redo(ns, marks[2], 0, 3, 1, 2)
+ end)
+
+ it('deleting right before a mark works', function()
+ -- op_delete in ops.c
+ set_extmark(ns, marks[1], 0, 2)
+ feed('0lx')
+ check_undo_redo(ns, marks[1], 0, 2, 0, 1)
+ end)
+
+ it('deleting right after a mark works', function()
+ -- op_delete in ops.c
+ set_extmark(ns, marks[1], 0, 2)
+ feed('02lx')
+ check_undo_redo(ns, marks[1], 0, 2, 0, 2)
+ end)
+
+ it('marks move with char deletes', function()
+ -- op_delete in ops.c
+ set_extmark(ns, marks[1], 0, 2)
+ feed('02dl')
+ check_undo_redo(ns, marks[1], 0, 2, 0, 0)
+ -- from the other side (nothing should happen)
+ feed('$x')
+ check_undo_redo(ns, marks[1], 0, 0, 0, 0)
+ end)
+
+ it('marks move with char deletes over a range', function()
+ -- op_delete in ops.c
+ set_extmark(ns, marks[1], 0, 2)
+ set_extmark(ns, marks[2], 0, 3)
+ feed('0l3dl<esc>')
+ check_undo_redo(ns, marks[1], 0, 2, 0, 1)
+ check_undo_redo(ns, marks[2], 0, 3, 0, 1)
+ -- delete 1, nothing should happen to our marks
+ feed('u')
+ feed('$x')
+ check_undo_redo(ns, marks[2], 0, 3, 0, 3)
+ end)
+
+ it('deleting marks at end of line works', function()
+ set_extmark(ns, marks[1], 0, 4)
+ feed('$x')
+ check_undo_redo(ns, marks[1], 0, 4, 0, 4)
+ -- check the copy happened correctly on delete at eol
+ feed('$x')
+ check_undo_redo(ns, marks[1], 0, 4, 0, 3)
+ feed('u')
+ check_undo_redo(ns, marks[1], 0, 4, 0, 4)
+ end)
+
+ it('marks move with blockwise deletes', function()
+ -- op_delete in ops.c
+ feed('a<cr>12345<esc>')
+ set_extmark(ns, marks[1], 1, 4)
+ feed('h<c-v>hhkd')
+ check_undo_redo(ns, marks[1], 1, 4, 1, 1)
+ end)
+
+ it('marks move with blockwise deletes over a range', function()
+ -- op_delete in ops.c
+ feed('a<cr>12345<esc>')
+ set_extmark(ns, marks[1], 0, 1)
+ set_extmark(ns, marks[2], 0, 3)
+ set_extmark(ns, marks[3], 1, 2)
+ feed('0<c-v>k3lx')
+ check_undo_redo(ns, marks[1], 0, 1, 0, 0)
+ check_undo_redo(ns, marks[2], 0, 3, 0, 0)
+ check_undo_redo(ns, marks[3], 1, 2, 1, 0)
+ -- delete 1, nothing should happen to our marks
+ feed('u')
+ feed('$<c-v>jx')
+ check_undo_redo(ns, marks[2], 0, 3, 0, 3)
+ check_undo_redo(ns, marks[3], 1, 2, 1, 2)
+ end)
+
+ it('works with char deletes over multilines', function()
+ feed('a<cr>12345<cr>test-me<esc>')
+ set_extmark(ns, marks[1], 2, 5)
+ feed('gg')
+ feed('dv?-m?<cr>')
+ check_undo_redo(ns, marks[1], 2, 5, 0, 0)
+ end)
+
+ it('marks outside of deleted range move with visual char deletes', function()
+ -- op_delete in ops.c
+ set_extmark(ns, marks[1], 0, 3)
+ feed('0vx<esc>')
+ check_undo_redo(ns, marks[1], 0, 3, 0, 2)
+
+ feed("u")
+ feed('0vlx<esc>')
+ check_undo_redo(ns, marks[1], 0, 3, 0, 1)
+
+ feed("u")
+ feed('0v2lx<esc>')
+ check_undo_redo(ns, marks[1], 0, 3, 0, 0)
+
+ -- from the other side (nothing should happen)
+ feed('$vx')
+ check_undo_redo(ns, marks[1], 0, 0, 0, 0)
+ end)
+
+ it('marks outside of deleted range move with char deletes', function()
+ -- op_delete in ops.c
+ set_extmark(ns, marks[1], 0, 3)
+ feed('0x<esc>')
+ check_undo_redo(ns, marks[1], 0, 3, 0, 2)
+
+ feed("u")
+ feed('02x<esc>')
+ check_undo_redo(ns, marks[1], 0, 3, 0, 1)
+
+ feed("u")
+ feed('0v3lx<esc>')
+ check_undo_redo(ns, marks[1], 0, 3, 0, 0)
+
+ -- from the other side (nothing should happen)
+ feed("u")
+ feed('$vx')
+ check_undo_redo(ns, marks[1], 0, 3, 0, 3)
+ end)
+
+ it('marks move with P(backward) paste', function()
+ -- do_put in ops.c
+ feed('0iabc<esc>')
+ set_extmark(ns, marks[1], 0, 7)
+ feed('0veyP')
+ check_undo_redo(ns, marks[1], 0, 7, 0, 15)
+ end)
+
+ it('marks move with p(forward) paste', function()
+ -- do_put in ops.c
+ feed('0iabc<esc>')
+ set_extmark(ns, marks[1], 0, 7)
+ feed('0veyp')
+ check_undo_redo(ns, marks[1], 0, 7, 0, 15)
+ end)
+
+ it('marks move with blockwise P(backward) paste', function()
+ -- do_put in ops.c
+ feed('a<cr>12345<esc>')
+ set_extmark(ns, marks[1], 1, 4)
+ feed('<c-v>hhkyP<esc>')
+ check_undo_redo(ns, marks[1], 1, 4, 1, 7)
+ end)
+
+ it('marks move with blockwise p(forward) paste', function()
+ -- do_put in ops.c
+ feed('a<cr>12345<esc>')
+ set_extmark(ns, marks[1], 1, 4)
+ feed('<c-v>hhkyp<esc>')
+ check_undo_redo(ns, marks[1], 1, 4, 1, 7)
+ end)
+
+ describe('multiline regions', function()
+ before_each(function()
+ feed('dd')
+ -- Achtung: code has been spiced with some unicode,
+ -- to make life more interesting.
+ -- luacheck whines about TABs inside strings for whatever reason.
+ -- luacheck: push ignore 621
+ insert([[
+ static int nlua_rpcrequest(lua_State *lstate)
+ {
+ Ãf (!nlua_is_deferred_safe(lstate)) {
+ // strictly not allowed
+ Яetörn luaL_error(lstate, e_luv_api_disabled, "rpcrequest");
+ }
+ return nlua_rpc(lstate, true);
+ }]])
+ -- luacheck: pop
+ end)
+
+ it('delete', function()
+ local pos1 = {
+ {2, 4}, {2, 12}, {2, 13}, {2, 14}, {2, 25},
+ {4, 8}, {4, 10}, {4, 20},
+ {5, 3}, {6, 10}
+ }
+ local ids = batch_set(ns, pos1)
+ batch_check(ns, ids, pos1)
+ feed('3Gfiv2+ftd')
+ batch_check_undo_redo(ns, ids, pos1, {
+ {2, 4}, {2, 12}, {2, 13}, {2, 13}, {2, 13},
+ {2, 13}, {2, 15}, {2, 25},
+ {3, 3}, {4, 10}
+ })
+ end)
+
+ -- TODO(bfredl): add more tests!
+ end)
+
+ it('replace works', function()
+ set_extmark(ns, marks[1], 0, 2)
+ feed('0r2')
+ check_undo_redo(ns, marks[1], 0, 2, 0, 2)
+ end)
+
+ it('blockwise replace works', function()
+ feed('a<cr>12345<esc>')
+ set_extmark(ns, marks[1], 0, 2)
+ feed('0<c-v>llkr1<esc>')
+ check_undo_redo(ns, marks[1], 0, 2, 0, 3)
+ end)
+
+ it('shift line', function()
+ -- shift_line in ops.c
+ feed(':set shiftwidth=4<cr><esc>')
+ set_extmark(ns, marks[1], 0, 2)
+ feed('0>>')
+ check_undo_redo(ns, marks[1], 0, 2, 0, 6)
+ expect(' 12345')
+
+ feed('>>')
+ -- this is counter-intuitive. But what happens
+ -- is that 4 spaces gets extended to one tab (== 8 spaces)
+ check_undo_redo(ns, marks[1], 0, 6, 0, 3)
+ expect('\t12345')
+
+ feed('<LT><LT>') -- have to escape, same as <<
+ check_undo_redo(ns, marks[1], 0, 3, 0, 6)
+ end)
+
+ it('blockwise shift', function()
+ -- shift_block in ops.c
+ feed(':set shiftwidth=4<cr><esc>')
+ feed('a<cr>12345<esc>')
+ set_extmark(ns, marks[1], 1, 2)
+ feed('0<c-v>k>')
+ check_undo_redo(ns, marks[1], 1, 2, 1, 6)
+ feed('<c-v>j>')
+ expect('\t12345\n\t12345')
+ check_undo_redo(ns, marks[1], 1, 6, 1, 3)
+
+ feed('<c-v>j<LT>')
+ check_undo_redo(ns, marks[1], 1, 3, 1, 6)
+ end)
+
+ it('tab works with expandtab', function()
+ -- ins_tab in edit.c
+ feed(':set expandtab<cr><esc>')
+ feed(':set shiftwidth=2<cr><esc>')
+ set_extmark(ns, marks[1], 0, 2)
+ feed('0i<tab><tab><esc>')
+ check_undo_redo(ns, marks[1], 0, 2, 0, 6)
+ end)
+
+ it('tabs work', function()
+ -- ins_tab in edit.c
+ feed(':set noexpandtab<cr><esc>')
+ feed(':set shiftwidth=2<cr><esc>')
+ feed(':set softtabstop=2<cr><esc>')
+ feed(':set tabstop=8<cr><esc>')
+ set_extmark(ns, marks[1], 0, 2)
+ feed('0i<tab><esc>')
+ check_undo_redo(ns, marks[1], 0, 2, 0, 4)
+ feed('0iX<tab><esc>')
+ check_undo_redo(ns, marks[1], 0, 4, 0, 6)
+ end)
+
+ it('marks move when using :move', function()
+ set_extmark(ns, marks[1], 0, 0)
+ feed('A<cr>2<esc>:1move 2<cr><esc>')
+ check_undo_redo(ns, marks[1], 0, 0, 1, 0)
+ -- test codepath when moving lines up
+ feed(':2move 0<cr><esc>')
+ check_undo_redo(ns, marks[1], 1, 0, 0, 0)
+ end)
+
+ it('marks move when using :move part 2', function()
+ -- make sure we didn't get lucky with the math...
+ feed('A<cr>2<cr>3<cr>4<cr>5<cr>6<esc>')
+ set_extmark(ns, marks[1], 1, 0)
+ feed(':2,3move 5<cr><esc>')
+ check_undo_redo(ns, marks[1], 1, 0, 3, 0)
+ -- test codepath when moving lines up
+ feed(':4,5move 1<cr><esc>')
+ check_undo_redo(ns, marks[1], 3, 0, 1, 0)
+ end)
+
+ it('undo and redo of set and unset marks', function()
+ -- Force a new undo head
+ feed('o<esc>')
+ set_extmark(ns, marks[1], 0, 1)
+ feed('o<esc>')
+ set_extmark(ns, marks[2], 0, -1)
+ set_extmark(ns, marks[3], 0, -1)
+
+ feed("u")
+ local rv = get_extmarks(ns, {0, 0}, {-1, -1})
+ eq(3, #rv)
+
+ feed("<c-r>")
+ rv = get_extmarks(ns, {0, 0}, {-1, -1})
+ eq(3, #rv)
+
+ -- Test updates
+ feed('o<esc>')
+ set_extmark(ns, marks[1], positions[1][1], positions[1][2])
+ rv = get_extmarks(ns, marks[1], marks[1], {limit=1})
+ eq(1, #rv)
+ feed("u")
+ feed("<c-r>")
+ -- old value is NOT kept in history
+ check_undo_redo(ns, marks[1], positions[1][1], positions[1][2], positions[1][1], positions[1][2])
+
+ -- Test unset
+ feed('o<esc>')
+ curbufmeths.del_extmark(ns, marks[3])
+ feed("u")
+ rv = get_extmarks(ns, {0, 0}, {-1, -1})
+ -- undo does NOT restore deleted marks
+ eq(2, #rv)
+ feed("<c-r>")
+ rv = get_extmarks(ns, {0, 0}, {-1, -1})
+ eq(2, #rv)
+ end)
+
+ it('undo and redo of marks deleted during edits', function()
+ -- test extmark_adjust
+ feed('A<cr>12345<esc>')
+ set_extmark(ns, marks[1], 1, 2)
+ feed('dd')
+ check_undo_redo(ns, marks[1], 1, 2, 1, 0)
+ end)
+
+ it('namespaces work properly', function()
+ local rv = set_extmark(ns, marks[1], positions[1][1], positions[1][2])
+ eq(1, rv)
+ rv = set_extmark(ns2, marks[1], positions[1][1], positions[1][2])
+ eq(1, rv)
+ rv = get_extmarks(ns, {0, 0}, {-1, -1})
+ eq(1, #rv)
+ rv = get_extmarks(ns2, {0, 0}, {-1, -1})
+ eq(1, #rv)
+
+ -- Set more marks for testing the ranges
+ set_extmark(ns, marks[2], positions[2][1], positions[2][2])
+ set_extmark(ns, marks[3], positions[3][1], positions[3][2])
+ set_extmark(ns2, marks[2], positions[2][1], positions[2][2])
+ set_extmark(ns2, marks[3], positions[3][1], positions[3][2])
+
+ -- get_next (limit set)
+ rv = get_extmarks(ns, {0, 0}, positions[2], {limit=1})
+ eq(1, #rv)
+ rv = get_extmarks(ns2, {0, 0}, positions[2], {limit=1})
+ eq(1, #rv)
+ -- get_prev (limit set)
+ rv = get_extmarks(ns, positions[1], {0, 0}, {limit=1})
+ eq(1, #rv)
+ rv = get_extmarks(ns2, positions[1], {0, 0}, {limit=1})
+ eq(1, #rv)
+
+ -- get_next (no limit)
+ rv = get_extmarks(ns, positions[1], positions[2])
+ eq(2, #rv)
+ rv = get_extmarks(ns2, positions[1], positions[2])
+ eq(2, #rv)
+ -- get_prev (no limit)
+ rv = get_extmarks(ns, positions[2], positions[1])
+ eq(2, #rv)
+ rv = get_extmarks(ns2, positions[2], positions[1])
+ eq(2, #rv)
+
+ curbufmeths.del_extmark(ns, marks[1])
+ rv = get_extmarks(ns, {0, 0}, {-1, -1})
+ eq(2, #rv)
+ curbufmeths.del_extmark(ns2, marks[1])
+ rv = get_extmarks(ns2, {0, 0}, {-1, -1})
+ eq(2, #rv)
+ end)
+
+ it('mark set can create unique identifiers', function()
+ -- create mark with id 1
+ eq(1, set_extmark(ns, 1, positions[1][1], positions[1][2]))
+ -- ask for unique id, it should be the next one, i e 2
+ eq(2, set_extmark(ns, 0, positions[1][1], positions[1][2]))
+ eq(3, set_extmark(ns, 3, positions[2][1], positions[2][2]))
+ eq(4, set_extmark(ns, 0, positions[1][1], positions[1][2]))
+
+ -- mixing manual and allocated id:s are not recommened, but it should
+ -- do something reasonable
+ eq(6, set_extmark(ns, 6, positions[2][1], positions[2][2]))
+ eq(7, set_extmark(ns, 0, positions[1][1], positions[1][2]))
+ eq(8, set_extmark(ns, 0, positions[1][1], positions[1][2]))
+ end)
+
+ it('auto indenting with enter works', function()
+ -- op_reindent in ops.c
+ feed(':set cindent<cr><esc>')
+ feed(':set autoindent<cr><esc>')
+ feed(':set shiftwidth=2<cr><esc>')
+ feed("0iint <esc>A {1M1<esc>b<esc>")
+ -- Set the mark on the M, should move..
+ set_extmark(ns, marks[1], 0, 12)
+ -- Set the mark before the cursor, should stay there
+ set_extmark(ns, marks[2], 0, 10)
+ feed("i<cr><esc>")
+ local rv = curbufmeths.get_extmark_by_id(ns, marks[1])
+ eq({1, 3}, rv)
+ rv = curbufmeths.get_extmark_by_id(ns, marks[2])
+ eq({0, 10}, rv)
+ check_undo_redo(ns, marks[1], 0, 12, 1, 3)
+ end)
+
+ it('auto indenting entire line works', function()
+ feed(':set cindent<cr><esc>')
+ feed(':set autoindent<cr><esc>')
+ feed(':set shiftwidth=2<cr><esc>')
+ -- <c-f> will force an indent of 2
+ feed("0iint <esc>A {<cr><esc>0i1M1<esc>")
+ set_extmark(ns, marks[1], 1, 1)
+ feed("0i<c-f><esc>")
+ local rv = curbufmeths.get_extmark_by_id(ns, marks[1])
+ eq({1, 3}, rv)
+ check_undo_redo(ns, marks[1], 1, 1, 1, 3)
+ -- now check when cursor at eol
+ feed("uA<c-f><esc>")
+ rv = curbufmeths.get_extmark_by_id(ns, marks[1])
+ eq({1, 3}, rv)
+ end)
+
+ it('removing auto indenting with <C-D> works', function()
+ feed(':set cindent<cr><esc>')
+ feed(':set autoindent<cr><esc>')
+ feed(':set shiftwidth=2<cr><esc>')
+ feed("0i<tab><esc>")
+ set_extmark(ns, marks[1], 0, 3)
+ feed("bi<c-d><esc>")
+ local rv = curbufmeths.get_extmark_by_id(ns, marks[1])
+ eq({0, 1}, rv)
+ check_undo_redo(ns, marks[1], 0, 3, 0, 1)
+ -- check when cursor at eol
+ feed("uA<c-d><esc>")
+ rv = curbufmeths.get_extmark_by_id(ns, marks[1])
+ eq({0, 1}, rv)
+ end)
+
+ it('indenting multiple lines with = works', function()
+ feed(':set cindent<cr><esc>')
+ feed(':set autoindent<cr><esc>')
+ feed(':set shiftwidth=2<cr><esc>')
+ feed("0iint <esc>A {<cr><bs>1M1<cr><bs>2M2<esc>")
+ set_extmark(ns, marks[1], 1, 1)
+ set_extmark(ns, marks[2], 2, 1)
+ feed('=gg')
+ check_undo_redo(ns, marks[1], 1, 1, 1, 3)
+ check_undo_redo(ns, marks[2], 2, 1, 2, 5)
+ end)
+
+ it('substitutes by deleting inside the replace matches', function()
+ -- do_sub in ex_cmds.c
+ set_extmark(ns, marks[1], 0, 2)
+ set_extmark(ns, marks[2], 0, 3)
+ feed(':s/34/xx<cr>')
+ check_undo_redo(ns, marks[1], 0, 2, 0, 4)
+ check_undo_redo(ns, marks[2], 0, 3, 0, 4)
+ end)
+
+ it('substitutes when insert text > deleted', function()
+ -- do_sub in ex_cmds.c
+ set_extmark(ns, marks[1], 0, 2)
+ set_extmark(ns, marks[2], 0, 3)
+ feed(':s/34/xxx<cr>')
+ check_undo_redo(ns, marks[1], 0, 2, 0, 5)
+ check_undo_redo(ns, marks[2], 0, 3, 0, 5)
+ end)
+
+ it('substitutes when marks around eol', function()
+ -- do_sub in ex_cmds.c
+ set_extmark(ns, marks[1], 0, 4)
+ set_extmark(ns, marks[2], 0, 5)
+ feed(':s/5/xxx<cr>')
+ check_undo_redo(ns, marks[1], 0, 4, 0, 7)
+ check_undo_redo(ns, marks[2], 0, 5, 0, 7)
+ end)
+
+ it('substitutes over range insert text > deleted', function()
+ -- do_sub in ex_cmds.c
+ feed('A<cr>x34xx<esc>')
+ feed('A<cr>xxx34<esc>')
+ set_extmark(ns, marks[1], 0, 2)
+ set_extmark(ns, marks[2], 1, 1)
+ set_extmark(ns, marks[3], 2, 4)
+ feed(':1,3s/34/xxx<cr><esc>')
+ check_undo_redo(ns, marks[1], 0, 2, 0, 5)
+ check_undo_redo(ns, marks[2], 1, 1, 1, 4)
+ check_undo_redo(ns, marks[3], 2, 4, 2, 6)
+ end)
+
+ it('substitutes multiple matches in a line', function()
+ -- do_sub in ex_cmds.c
+ feed('ddi3x3x3<esc>')
+ set_extmark(ns, marks[1], 0, 0)
+ set_extmark(ns, marks[2], 0, 2)
+ set_extmark(ns, marks[3], 0, 4)
+ feed(':s/3/yy/g<cr><esc>')
+ check_undo_redo(ns, marks[1], 0, 0, 0, 2)
+ check_undo_redo(ns, marks[2], 0, 2, 0, 5)
+ check_undo_redo(ns, marks[3], 0, 4, 0, 8)
+ end)
+
+ it('substitions over multiple lines with newline in pattern', function()
+ feed('A<cr>67890<cr>xx<esc>')
+ set_extmark(ns, marks[1], 0, 3)
+ set_extmark(ns, marks[2], 0, 4)
+ set_extmark(ns, marks[3], 1, 0)
+ set_extmark(ns, marks[4], 1, 5)
+ set_extmark(ns, marks[5], 2, 0)
+ feed([[:1,2s:5\n:5 <cr>]])
+ check_undo_redo(ns, marks[1], 0, 3, 0, 3)
+ check_undo_redo(ns, marks[2], 0, 4, 0, 6)
+ check_undo_redo(ns, marks[3], 1, 0, 0, 6)
+ check_undo_redo(ns, marks[4], 1, 5, 0, 11)
+ check_undo_redo(ns, marks[5], 2, 0, 1, 0)
+ end)
+
+ it('inserting', function()
+ feed('A<cr>67890<cr>xx<esc>')
+ set_extmark(ns, marks[1], 0, 3)
+ set_extmark(ns, marks[2], 0, 4)
+ set_extmark(ns, marks[3], 1, 0)
+ set_extmark(ns, marks[4], 1, 5)
+ set_extmark(ns, marks[5], 2, 0)
+ set_extmark(ns, marks[6], 1, 2)
+ feed([[:1,2s:5\n67:X<cr>]])
+ check_undo_redo(ns, marks[1], 0, 3, 0, 3)
+ check_undo_redo(ns, marks[2], 0, 4, 0, 5)
+ check_undo_redo(ns, marks[3], 1, 0, 0, 5)
+ check_undo_redo(ns, marks[4], 1, 5, 0, 8)
+ check_undo_redo(ns, marks[5], 2, 0, 1, 0)
+ check_undo_redo(ns, marks[6], 1, 2, 0, 5)
+ end)
+
+ it('substitions with multiple newlines in pattern', function()
+ feed('A<cr>67890<cr>xx<esc>')
+ set_extmark(ns, marks[1], 0, 4)
+ set_extmark(ns, marks[2], 0, 5)
+ set_extmark(ns, marks[3], 1, 0)
+ set_extmark(ns, marks[4], 1, 5)
+ set_extmark(ns, marks[5], 2, 0)
+ feed([[:1,2s:\n.*\n:X<cr>]])
+ check_undo_redo(ns, marks[1], 0, 4, 0, 4)
+ check_undo_redo(ns, marks[2], 0, 5, 0, 6)
+ check_undo_redo(ns, marks[3], 1, 0, 0, 6)
+ check_undo_redo(ns, marks[4], 1, 5, 0, 6)
+ check_undo_redo(ns, marks[5], 2, 0, 0, 6)
+ end)
+
+ it('substitions over multiple lines with replace in substition', function()
+ feed('A<cr>67890<cr>xx<esc>')
+ set_extmark(ns, marks[1], 0, 1)
+ set_extmark(ns, marks[2], 0, 2)
+ set_extmark(ns, marks[3], 0, 4)
+ set_extmark(ns, marks[4], 1, 0)
+ set_extmark(ns, marks[5], 2, 0)
+ feed([[:1,2s:3:\r<cr>]])
+ check_undo_redo(ns, marks[1], 0, 1, 0, 1)
+ check_undo_redo(ns, marks[2], 0, 2, 1, 0)
+ check_undo_redo(ns, marks[3], 0, 4, 1, 1)
+ check_undo_redo(ns, marks[4], 1, 0, 2, 0)
+ check_undo_redo(ns, marks[5], 2, 0, 3, 0)
+ feed('u')
+ feed([[:1,2s:3:\rxx<cr>]])
+ eq({1, 3}, curbufmeths.get_extmark_by_id(ns, marks[3]))
+ end)
+
+ it('substitions over multiple lines with replace in substition', function()
+ feed('A<cr>x3<cr>xx<esc>')
+ set_extmark(ns, marks[1], 1, 0)
+ set_extmark(ns, marks[2], 1, 1)
+ set_extmark(ns, marks[3], 1, 2)
+ feed([[:2,2s:3:\r<cr>]])
+ check_undo_redo(ns, marks[1], 1, 0, 1, 0)
+ check_undo_redo(ns, marks[2], 1, 1, 2, 0)
+ check_undo_redo(ns, marks[3], 1, 2, 2, 0)
+ end)
+
+ it('substitions over multiple lines with replace in substition', function()
+ feed('A<cr>x3<cr>xx<esc>')
+ set_extmark(ns, marks[1], 0, 1)
+ set_extmark(ns, marks[2], 0, 2)
+ set_extmark(ns, marks[3], 0, 4)
+ set_extmark(ns, marks[4], 1, 1)
+ set_extmark(ns, marks[5], 2, 0)
+ feed([[:1,2s:3:\r<cr>]])
+ check_undo_redo(ns, marks[1], 0, 1, 0, 1)
+ check_undo_redo(ns, marks[2], 0, 2, 1, 0)
+ check_undo_redo(ns, marks[3], 0, 4, 1, 1)
+ check_undo_redo(ns, marks[4], 1, 1, 3, 0)
+ check_undo_redo(ns, marks[5], 2, 0, 4, 0)
+ feed('u')
+ feed([[:1,2s:3:\rxx<cr>]])
+ check_undo_redo(ns, marks[3], 0, 4, 1, 3)
+ end)
+
+ it('substitions with newline in match and sub, delta is 0', function()
+ feed('A<cr>67890<cr>xx<esc>')
+ set_extmark(ns, marks[1], 0, 3)
+ set_extmark(ns, marks[2], 0, 4)
+ set_extmark(ns, marks[3], 0, 5)
+ set_extmark(ns, marks[4], 1, 0)
+ set_extmark(ns, marks[5], 1, 5)
+ set_extmark(ns, marks[6], 2, 0)
+ feed([[:1,1s:5\n:\r<cr>]])
+ check_undo_redo(ns, marks[1], 0, 3, 0, 3)
+ check_undo_redo(ns, marks[2], 0, 4, 1, 0)
+ check_undo_redo(ns, marks[3], 0, 5, 1, 0)
+ check_undo_redo(ns, marks[4], 1, 0, 1, 0)
+ check_undo_redo(ns, marks[5], 1, 5, 1, 5)
+ check_undo_redo(ns, marks[6], 2, 0, 2, 0)
+ end)
+
+ it('substitions with newline in match and sub, delta > 0', function()
+ feed('A<cr>67890<cr>xx<esc>')
+ set_extmark(ns, marks[1], 0, 3)
+ set_extmark(ns, marks[2], 0, 4)
+ set_extmark(ns, marks[3], 0, 5)
+ set_extmark(ns, marks[4], 1, 0)
+ set_extmark(ns, marks[5], 1, 5)
+ set_extmark(ns, marks[6], 2, 0)
+ feed([[:1,1s:5\n:\r\r<cr>]])
+ check_undo_redo(ns, marks[1], 0, 3, 0, 3)
+ check_undo_redo(ns, marks[2], 0, 4, 2, 0)
+ check_undo_redo(ns, marks[3], 0, 5, 2, 0)
+ check_undo_redo(ns, marks[4], 1, 0, 2, 0)
+ check_undo_redo(ns, marks[5], 1, 5, 2, 5)
+ check_undo_redo(ns, marks[6], 2, 0, 3, 0)
+ end)
+
+ it('substitions with newline in match and sub, delta < 0', function()
+ feed('A<cr>67890<cr>xx<cr>xx<esc>')
+ set_extmark(ns, marks[1], 0, 3)
+ set_extmark(ns, marks[2], 0, 4)
+ set_extmark(ns, marks[3], 0, 5)
+ set_extmark(ns, marks[4], 1, 0)
+ set_extmark(ns, marks[5], 1, 5)
+ set_extmark(ns, marks[6], 2, 1)
+ set_extmark(ns, marks[7], 3, 0)
+ feed([[:1,2s:5\n.*\n:\r<cr>]])
+ check_undo_redo(ns, marks[1], 0, 3, 0, 3)
+ check_undo_redo(ns, marks[2], 0, 4, 1, 0)
+ check_undo_redo(ns, marks[3], 0, 5, 1, 0)
+ check_undo_redo(ns, marks[4], 1, 0, 1, 0)
+ check_undo_redo(ns, marks[5], 1, 5, 1, 0)
+ check_undo_redo(ns, marks[6], 2, 1, 1, 1)
+ check_undo_redo(ns, marks[7], 3, 0, 2, 0)
+ end)
+
+ it('substitions with backrefs, newline inserted into sub', function()
+ feed('A<cr>67890<cr>xx<cr>xx<esc>')
+ set_extmark(ns, marks[1], 0, 3)
+ set_extmark(ns, marks[2], 0, 4)
+ set_extmark(ns, marks[3], 0, 5)
+ set_extmark(ns, marks[4], 1, 0)
+ set_extmark(ns, marks[5], 1, 5)
+ set_extmark(ns, marks[6], 2, 0)
+ feed([[:1,1s:5\(\n\):\0\1<cr>]])
+ check_undo_redo(ns, marks[1], 0, 3, 0, 3)
+ check_undo_redo(ns, marks[2], 0, 4, 2, 0)
+ check_undo_redo(ns, marks[3], 0, 5, 2, 0)
+ check_undo_redo(ns, marks[4], 1, 0, 2, 0)
+ check_undo_redo(ns, marks[5], 1, 5, 2, 5)
+ check_undo_redo(ns, marks[6], 2, 0, 3, 0)
+ end)
+
+ it('substitions a ^', function()
+ set_extmark(ns, marks[1], 0, 0)
+ set_extmark(ns, marks[2], 0, 1)
+ feed([[:s:^:x<cr>]])
+ check_undo_redo(ns, marks[1], 0, 0, 0, 1)
+ check_undo_redo(ns, marks[2], 0, 1, 0, 2)
+ end)
+
+ it('using <c-a> without increase in order of magnitude', function()
+ -- do_addsub in ops.c
+ feed('ddiabc998xxx<esc>Tc')
+ set_extmark(ns, marks[1], 0, 2)
+ set_extmark(ns, marks[2], 0, 3)
+ set_extmark(ns, marks[3], 0, 5)
+ set_extmark(ns, marks[4], 0, 6)
+ set_extmark(ns, marks[5], 0, 7)
+ feed('<c-a>')
+ check_undo_redo(ns, marks[1], 0, 2, 0, 2)
+ check_undo_redo(ns, marks[2], 0, 3, 0, 6)
+ check_undo_redo(ns, marks[3], 0, 5, 0, 6)
+ check_undo_redo(ns, marks[4], 0, 6, 0, 6)
+ check_undo_redo(ns, marks[5], 0, 7, 0, 7)
+ end)
+
+ it('using <c-a> when increase in order of magnitude', function()
+ -- do_addsub in ops.c
+ feed('ddiabc999xxx<esc>Tc')
+ set_extmark(ns, marks[1], 0, 2)
+ set_extmark(ns, marks[2], 0, 3)
+ set_extmark(ns, marks[3], 0, 5)
+ set_extmark(ns, marks[4], 0, 6)
+ set_extmark(ns, marks[5], 0, 7)
+ feed('<c-a>')
+ check_undo_redo(ns, marks[1], 0, 2, 0, 2)
+ check_undo_redo(ns, marks[2], 0, 3, 0, 7)
+ check_undo_redo(ns, marks[3], 0, 5, 0, 7)
+ check_undo_redo(ns, marks[4], 0, 6, 0, 7)
+ check_undo_redo(ns, marks[5], 0, 7, 0, 8)
+ end)
+
+ it('using <c-a> when negative and without decrease in order of magnitude', function()
+ feed('ddiabc-999xxx<esc>T-')
+ set_extmark(ns, marks[1], 0, 2)
+ set_extmark(ns, marks[2], 0, 3)
+ set_extmark(ns, marks[3], 0, 6)
+ set_extmark(ns, marks[4], 0, 7)
+ set_extmark(ns, marks[5], 0, 8)
+ feed('<c-a>')
+ check_undo_redo(ns, marks[1], 0, 2, 0, 2)
+ check_undo_redo(ns, marks[2], 0, 3, 0, 7)
+ check_undo_redo(ns, marks[3], 0, 6, 0, 7)
+ check_undo_redo(ns, marks[4], 0, 7, 0, 7)
+ check_undo_redo(ns, marks[5], 0, 8, 0, 8)
+ end)
+
+ it('using <c-a> when negative and decrease in order of magnitude', function()
+ feed('ddiabc-1000xxx<esc>T-')
+ set_extmark(ns, marks[1], 0, 2)
+ set_extmark(ns, marks[2], 0, 3)
+ set_extmark(ns, marks[3], 0, 7)
+ set_extmark(ns, marks[4], 0, 8)
+ set_extmark(ns, marks[5], 0, 9)
+ feed('<c-a>')
+ check_undo_redo(ns, marks[1], 0, 2, 0, 2)
+ check_undo_redo(ns, marks[2], 0, 3, 0, 7)
+ check_undo_redo(ns, marks[3], 0, 7, 0, 7)
+ check_undo_redo(ns, marks[4], 0, 8, 0, 7)
+ check_undo_redo(ns, marks[5], 0, 9, 0, 8)
+ end)
+
+ it('using <c-x> without decrease in order of magnitude', function()
+ -- do_addsub in ops.c
+ feed('ddiabc999xxx<esc>Tc')
+ set_extmark(ns, marks[1], 0, 2)
+ set_extmark(ns, marks[2], 0, 3)
+ set_extmark(ns, marks[3], 0, 5)
+ set_extmark(ns, marks[4], 0, 6)
+ set_extmark(ns, marks[5], 0, 7)
+ feed('<c-x>')
+ check_undo_redo(ns, marks[1], 0, 2, 0, 2)
+ check_undo_redo(ns, marks[2], 0, 3, 0, 6)
+ check_undo_redo(ns, marks[3], 0, 5, 0, 6)
+ check_undo_redo(ns, marks[4], 0, 6, 0, 6)
+ check_undo_redo(ns, marks[5], 0, 7, 0, 7)
+ end)
+
+ it('using <c-x> when decrease in order of magnitude', function()
+ -- do_addsub in ops.c
+ feed('ddiabc1000xxx<esc>Tc')
+ set_extmark(ns, marks[1], 0, 2)
+ set_extmark(ns, marks[2], 0, 3)
+ set_extmark(ns, marks[3], 0, 6)
+ set_extmark(ns, marks[4], 0, 7)
+ set_extmark(ns, marks[5], 0, 8)
+ feed('<c-x>')
+ check_undo_redo(ns, marks[1], 0, 2, 0, 2)
+ check_undo_redo(ns, marks[2], 0, 3, 0, 6)
+ check_undo_redo(ns, marks[3], 0, 6, 0, 6)
+ check_undo_redo(ns, marks[4], 0, 7, 0, 6)
+ check_undo_redo(ns, marks[5], 0, 8, 0, 7)
+ end)
+
+ it('using <c-x> when negative and without increase in order of magnitude', function()
+ feed('ddiabc-998xxx<esc>T-')
+ set_extmark(ns, marks[1], 0, 2)
+ set_extmark(ns, marks[2], 0, 3)
+ set_extmark(ns, marks[3], 0, 6)
+ set_extmark(ns, marks[4], 0, 7)
+ set_extmark(ns, marks[5], 0, 8)
+ feed('<c-x>')
+ check_undo_redo(ns, marks[1], 0, 2, 0, 2)
+ check_undo_redo(ns, marks[2], 0, 3, 0, 7)
+ check_undo_redo(ns, marks[3], 0, 6, 0, 7)
+ check_undo_redo(ns, marks[4], 0, 7, 0, 7)
+ check_undo_redo(ns, marks[5], 0, 8, 0, 8)
+ end)
+
+ it('using <c-x> when negative and increase in order of magnitude', function()
+ feed('ddiabc-999xxx<esc>T-')
+ set_extmark(ns, marks[1], 0, 2)
+ set_extmark(ns, marks[2], 0, 3)
+ set_extmark(ns, marks[3], 0, 6)
+ set_extmark(ns, marks[4], 0, 7)
+ set_extmark(ns, marks[5], 0, 8)
+ feed('<c-x>')
+ check_undo_redo(ns, marks[1], 0, 2, 0, 2)
+ check_undo_redo(ns, marks[2], 0, 3, 0, 8)
+ check_undo_redo(ns, marks[3], 0, 6, 0, 8)
+ check_undo_redo(ns, marks[4], 0, 7, 0, 8)
+ check_undo_redo(ns, marks[5], 0, 8, 0, 9)
+ end)
+
+ 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(curbufmeths.get_extmark_by_id, ns_invalid, marks[1]))
+ end)
+
+ it('when col = line-length, set the mark on eol', function()
+ set_extmark(ns, marks[1], 0, -1)
+ local rv = curbufmeths.get_extmark_by_id(ns, marks[1])
+ eq({0, init_text:len()}, rv)
+ -- Test another
+ set_extmark(ns, marks[1], 0, -1)
+ rv = curbufmeths.get_extmark_by_id(ns, marks[1])
+ eq({0, init_text:len()}, rv)
+ end)
+
+ 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))
+ 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({}, curbufmeths.get_extmark_by_id(ns, marks[1]))
+ end)
+
+ it('bug from check_col in extmark_set', function()
+ -- This bug was caused by extmark_set always using check_col. check_col
+ -- always uses the current buffer. This wasn't working during undo so we
+ -- now use check_col and check_lnum only when they are required.
+ feed('A<cr>67890<cr>xx<esc>')
+ feed('A<cr>12345<cr>67890<cr>xx<esc>')
+ set_extmark(ns, marks[1], 3, 4)
+ feed([[:1,5s:5\n:5 <cr>]])
+ check_undo_redo(ns, marks[1], 3, 4, 2, 6)
+ end)
+
+ it('in read-only buffer', function()
+ command("view! runtime/doc/help.txt")
+ eq(true, curbufmeths.get_option('ro'))
+ local id = set_extmark(ns, 0, 0, 2)
+ eq({{id, 0, 2}}, get_extmarks(ns,0, -1))
+ end)
+
+ it('can set a mark to other buffer', function()
+ local buf = request('nvim_create_buf', 0, 1)
+ request('nvim_buf_set_lines', buf, 0, -1, 1, {"", ""})
+ local id = bufmeths.set_extmark(buf, ns, 0, 1, 0, {})
+ eq({{id, 1, 0}}, bufmeths.get_extmarks(buf, ns, 0, -1, {}))
+ end)
+
+ it('does not crash with append/delete/undo seqence', function()
+ meths.exec([[
+ let ns = nvim_create_namespace('myplugin')
+ call nvim_buf_set_extmark(0, ns, 0, 0, 0, {})
+ call append(0, '')
+ %delete
+ undo]],false)
+ eq(2, meths.eval('1+1')) -- did not crash
+ end)
+end)
+
+describe('Extmarks buffer api with many marks', function()
+ local ns1
+ local ns2
+ local ns_marks = {}
+ before_each(function()
+ clear()
+ ns1 = request('nvim_create_namespace', "ns1")
+ ns2 = request('nvim_create_namespace', "ns2")
+ ns_marks = {[ns1]={}, [ns2]={}}
+ local lines = {}
+ for i = 1,30 do
+ lines[#lines+1] = string.rep("x ",i)
+ end
+ curbufmeths.set_lines(0, -1, true, lines)
+ local ns = ns1
+ local q = 0
+ for i = 0,29 do
+ for j = 0,i do
+ local id = set_extmark(ns,0, i,j)
+ eq(nil, ns_marks[ns][id])
+ ok(id > 0)
+ ns_marks[ns][id] = {i,j}
+ ns = ns1+ns2-ns
+ q = q + 1
+ end
+ end
+ eq(233, #ns_marks[ns1])
+ eq(232, #ns_marks[ns2])
+
+ end)
+
+ local function get_marks(ns)
+ local mark_list = get_extmarks(ns, 0, -1)
+ local marks = {}
+ for _, mark in ipairs(mark_list) do
+ local id, row, col = unpack(mark)
+ eq(nil, marks[id], "duplicate mark")
+ marks[id] = {row,col}
+ end
+ return marks
+ end
+
+ it("can get marks", function()
+ eq(ns_marks[ns1], get_marks(ns1))
+ eq(ns_marks[ns2], get_marks(ns2))
+ end)
+
+ it("can clear all marks in ns", function()
+ curbufmeths.clear_namespace(ns1, 0, -1)
+ eq({}, get_marks(ns1))
+ eq(ns_marks[ns2], get_marks(ns2))
+ curbufmeths.clear_namespace(ns2, 0, -1)
+ eq({}, get_marks(ns1))
+ eq({}, get_marks(ns2))
+ end)
+
+ it("can clear line range", function()
+ curbufmeths.clear_namespace(ns1, 10, 20)
+ for id, mark in pairs(ns_marks[ns1]) do
+ if 10 <= mark[1] and mark[1] < 20 then
+ ns_marks[ns1][id] = nil
+ end
+ end
+ eq(ns_marks[ns1], get_marks(ns1))
+ eq(ns_marks[ns2], get_marks(ns2))
+ end)
+
+ it("can delete line", function()
+ feed('10Gdd')
+ for _, marks in pairs(ns_marks) do
+ for id, mark in pairs(marks) do
+ if mark[1] == 9 then
+ marks[id] = {9,0}
+ elseif mark[1] >= 10 then
+ mark[1] = mark[1] - 1
+ end
+ end
+ end
+ eq(ns_marks[ns1], get_marks(ns1))
+ eq(ns_marks[ns2], get_marks(ns2))
+ end)
+
+ it("can delete lines", function()
+ feed('10G10dd')
+ for _, marks in pairs(ns_marks) do
+ for id, mark in pairs(marks) do
+ if 9 <= mark[1] and mark[1] < 19 then
+ marks[id] = {9,0}
+ elseif mark[1] >= 19 then
+ mark[1] = mark[1] - 10
+ end
+ end
+ end
+ eq(ns_marks[ns1], get_marks(ns1))
+ eq(ns_marks[ns2], get_marks(ns2))
+ end)
+
+ it("can wipe buffer", function()
+ command('bwipe!')
+ eq({}, get_marks(ns1))
+ eq({}, get_marks(ns2))
+ end)
+end)
diff --git a/test/functional/api/highlight_spec.lua b/test/functional/api/highlight_spec.lua
index 5297c6454e..a9d4c72d31 100644
--- a/test/functional/api/highlight_spec.lua
+++ b/test/functional/api/highlight_spec.lua
@@ -4,6 +4,9 @@ local Screen = require('test.functional.ui.screen')
local eq, eval = helpers.eq, helpers.eval
local command = helpers.command
local meths = helpers.meths
+local funcs = helpers.funcs
+local pcall_err = helpers.pcall_err
+local ok = helpers.ok
describe('API: highlight',function()
local expected_rgb = {
@@ -67,6 +70,22 @@ describe('API: highlight',function()
eq(false, err)
eq('Invalid highlight id: -1',
string.match(emsg, 'Invalid.*'))
+
+ -- Test highlight group without ctermbg value.
+ command('hi Normal ctermfg=red ctermbg=yellow')
+ command('hi NewConstant ctermfg=green guifg=white guibg=blue')
+ hl_id = eval("hlID('NewConstant')")
+ eq({foreground = 10,}, meths.get_hl_by_id(hl_id, false))
+
+ -- Test highlight group without ctermfg value.
+ command('hi clear NewConstant')
+ command('hi NewConstant ctermbg=Magenta guifg=white guibg=blue')
+ eq({background = 13,}, meths.get_hl_by_id(hl_id, false))
+
+ -- Test highlight group with ctermfg and ctermbg values.
+ command('hi clear NewConstant')
+ command('hi NewConstant ctermfg=green ctermbg=Magenta guifg=white guibg=blue')
+ eq({foreground = 10, background = 13,}, meths.get_hl_by_id(hl_id, false))
end)
it("nvim_get_hl_by_name", function()
@@ -110,4 +129,20 @@ describe('API: highlight',function()
meths.get_hl_by_name('cursorline', 0));
end)
+
+ 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(0, funcs.hlID("Shrubbery"))
+
+ local hl_id = meths.get_hl_id_by_name("Shrubbery")
+ ok(hl_id > 0)
+ eq(hl_id, funcs.hlID("Shrubbery"))
+
+ command('hi Shrubbery guifg=#888888 guibg=#888888')
+ eq({foreground=tonumber("0x888888"), background=tonumber("0x888888")},
+ meths.get_hl_by_id(hl_id, true))
+ eq({foreground=tonumber("0x888888"), background=tonumber("0x888888")},
+ meths.get_hl_by_name("Shrubbery", true))
+ end)
end)
diff --git a/test/functional/api/keymap_spec.lua b/test/functional/api/keymap_spec.lua
index 773207d360..5da2c6b531 100644
--- a/test/functional/api/keymap_spec.lua
+++ b/test/functional/api/keymap_spec.lua
@@ -21,6 +21,7 @@ describe('nvim_get_keymap', function()
local foo_bar_string = 'nnoremap foo bar'
local foo_bar_map_table = {
lhs='foo',
+ script=0,
silent=0,
rhs='bar',
expr=0,
@@ -245,6 +246,7 @@ describe('nvim_get_keymap', function()
it('works correctly despite various &cpo settings', function()
local cpo_table = {
+ script=0,
silent=0,
expr=0,
sid=0,
@@ -302,6 +304,7 @@ describe('nvim_get_keymap', function()
lhs='| |',
rhs='| |',
mode='n',
+ script=0,
silent=0,
expr=0,
sid=0,
@@ -343,6 +346,7 @@ describe('nvim_set_keymap, nvim_del_keymap', function()
to_return.noremap = not opts.noremap and 0 or 1
to_return.lhs = lhs
to_return.rhs = rhs
+ to_return.script = 0
to_return.silent = not opts.silent and 0 or 1
to_return.nowait = not opts.nowait and 0 or 1
to_return.expr = not opts.expr and 0 or 1
@@ -585,13 +589,13 @@ describe('nvim_set_keymap, nvim_del_keymap', function()
end)
it('can set <expr> mappings whose RHS change dynamically', function()
- meths.command_output([[
+ meths.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', {}))
diff --git a/test/functional/api/menu_spec.lua b/test/functional/api/menu_spec.lua
index 2cfa0e3e47..34a92477f3 100644
--- a/test/functional/api/menu_spec.lua
+++ b/test/functional/api/menu_spec.lua
@@ -15,10 +15,6 @@ describe("update_menu notification", function()
screen:attach()
end)
- after_each(function()
- screen:detach()
- end)
-
local function expect_sent(expected)
screen:expect{condition=function()
if screen.update_menu ~= expected then
diff --git a/test/functional/api/proc_spec.lua b/test/functional/api/proc_spec.lua
index 063d382790..d828bdf948 100644
--- a/test/functional/api/proc_spec.lua
+++ b/test/functional/api/proc_spec.lua
@@ -10,7 +10,7 @@ local request = helpers.request
local retry = helpers.retry
local NIL = helpers.NIL
-describe('api', function()
+describe('API', function()
before_each(clear)
describe('nvim_get_proc_children', function()
diff --git a/test/functional/api/rpc_fixture.lua b/test/functional/api/rpc_fixture.lua
index 87f5a91115..94df751363 100644
--- a/test/functional/api/rpc_fixture.lua
+++ b/test/functional/api/rpc_fixture.lua
@@ -1,12 +1,8 @@
-local deps_prefix = (os.getenv('DEPS_PREFIX') and os.getenv('DEPS_PREFIX')
- or './.deps/usr')
-
-package.path = deps_prefix .. '/share/lua/5.1/?.lua;' ..
- deps_prefix .. '/share/lua/5.1/?/init.lua;' ..
- package.path
-
-package.cpath = deps_prefix .. '/lib/lua/5.1/?.so;' ..
- package.cpath
+--- RPC server fixture.
+--
+-- Lua's paths are passed as arguments to reflect the path in the test itself.
+package.path = arg[1]
+package.cpath = arg[2]
local mpack = require('mpack')
local StdioStream = require('nvim.stdio_stream')
diff --git a/test/functional/api/server_requests_spec.lua b/test/functional/api/server_requests_spec.lua
index e275d8cd35..237a4b01e4 100644
--- a/test/functional/api/server_requests_spec.lua
+++ b/test/functional/api/server_requests_spec.lua
@@ -241,10 +241,14 @@ describe('server -> client', function()
\ 'rpc': v:true
\ }
]])
- meths.set_var("args", {helpers.test_lua_prg,
- 'test/functional/api/rpc_fixture.lua'})
+ meths.set_var("args", {
+ helpers.test_lua_prg,
+ 'test/functional/api/rpc_fixture.lua',
+ package.path,
+ package.cpath,
+ })
jobid = eval("jobstart(g:args, g:job_opts)")
- neq(0, 'jobid')
+ neq(0, jobid)
end)
after_each(function()
@@ -254,7 +258,11 @@ describe('server -> client', function()
if helpers.pending_win32(pending) then return end
it('rpc and text stderr can be combined', function()
- eq("ok",funcs.rpcrequest(jobid, "poll"))
+ local status, rv = pcall(funcs.rpcrequest, jobid, 'poll')
+ if not status then
+ error(string.format('missing nvim Lua module? (%s)', rv))
+ end
+ eq('ok', rv)
funcs.rpcnotify(jobid, "ping")
eq({'notification', 'pong', {}}, next_msg())
eq("done!",funcs.rpcrequest(jobid, "write_stderr", "fluff\n"))
@@ -309,8 +317,7 @@ describe('server -> client', function()
set_session(server)
local status, address = pcall(funcs.serverstart, "127.0.0.1:")
if not status then
- pending('no ipv4 stack', function() end)
- return
+ pending('no ipv4 stack')
end
eq('127.0.0.1:', string.sub(address,1,10))
connect_test(server, 'tcp', address)
@@ -321,8 +328,7 @@ describe('server -> client', function()
set_session(server)
local status, address = pcall(funcs.serverstart, '::1:')
if not status then
- pending('no ipv6 stack', function() end)
- return
+ pending('no ipv6 stack')
end
eq('::1:', string.sub(address,1,4))
connect_test(server, 'tcp', address)
@@ -339,11 +345,6 @@ describe('server -> client', function()
describe('connecting to its own pipe address', function()
it('does not deadlock', function()
- if not helpers.isCI('travis') and helpers.is_os('mac') then
- -- It does, in fact, deadlock on QuickBuild. #6851
- pending("deadlocks on QuickBuild", function() end)
- return
- end
local address = funcs.serverlist()[1]
local first = string.sub(address,1,1)
ok(first == '/' or first == '\\')
diff --git a/test/functional/api/tabpage_spec.lua b/test/functional/api/tabpage_spec.lua
index ed7ce72597..20b3163d95 100644
--- a/test/functional/api/tabpage_spec.lua
+++ b/test/functional/api/tabpage_spec.lua
@@ -24,6 +24,10 @@ describe('api/tabpage', function()
nvim('set_current_win', win3)
eq(win3, tabpage('get_win', tab2))
end)
+
+ it('validates args', function()
+ eq('Invalid tabpage id: 23', pcall_err(tabpage, 'list_wins', 23))
+ end)
end)
describe('{get,set,del}_var', function()
diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua
index 8b77dbcaa6..72e810e3e4 100644
--- a/test/functional/api/vim_spec.lua
+++ b/test/functional/api/vim_spec.lua
@@ -16,11 +16,14 @@ local parse_context = helpers.parse_context
local request = helpers.request
local source = helpers.source
local next_msg = helpers.next_msg
+local tmpname = helpers.tmpname
+local write_file = helpers.write_file
local pcall_err = helpers.pcall_err
local format_string = helpers.format_string
local intchar2lua = helpers.intchar2lua
local mergedicts_copy = helpers.mergedicts_copy
+local endswith = helpers.endswith
describe('API', function()
before_each(clear)
@@ -74,9 +77,127 @@ describe('API', function()
eq({mode='i', blocking=false}, nvim("get_mode"))
end)
+ describe('nvim_exec', function()
+ it('one-line input', function()
+ nvim('exec', "let x1 = 'a'", 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))
+ end)
+
+ it('multiline input', function()
+ -- Heredoc + empty lines.
+ nvim('exec', "let x2 = 'a'\n", false)
+ eq('a', nvim('get_var', 'x2'))
+ nvim('exec','lua <<EOF\n\n\n\ny=3\n\n\nEOF', false)
+ eq(3, nvim('eval', "luaeval('y')"))
+
+ eq('', nvim('exec', 'lua <<EOF\ny=3\nEOF', false))
+ eq(3, nvim('eval', "luaeval('y')"))
+
+ -- Multiple statements
+ nvim('exec', 'let x1=1\nlet x2=2\nlet x3=3\n', 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)
+ eq(nvim('get_current_line'), '')
+ nvim('exec', 'call Foo()', false)
+ eq(nvim('get_current_line'), 'xxx')
+
+ -- Autocmds
+ nvim('exec','autocmd BufAdd * :let x1 = "Hello"', false)
+ nvim('command', 'new foo')
+ eq('Hello', request('nvim_eval', 'g:x1'))
+ end)
+
+ it('non-ASCII input', function()
+ nvim('exec', [=[
+ new
+ exe "normal! i ax \n Ax "
+ :%s/ax/--a1234--/g | :%s/Ax/--A1234--/g
+ ]=], false)
+ nvim('command', '1')
+ eq(' --a1234-- ', nvim('get_current_line'))
+ nvim('command', '2')
+ eq(' --A1234-- ', nvim('get_current_line'))
+
+ nvim('exec', [[
+ new
+ call setline(1,['xxx'])
+ call feedkeys('r')
+ call feedkeys('ñ', 'xt')
+ ]], false)
+ eq('ñxx', nvim('get_current_line'))
+ end)
+
+ it('execution error', function()
+ eq('Vim:E492: Not an editor command: bogus_command',
+ pcall_err(request, 'nvim_exec', 'bogus_command', false))
+ eq('', nvim('eval', 'v:errmsg')) -- v:errmsg was not updated.
+ eq('', eval('v:exception'))
+
+ eq('Vim(buffer):E86: Buffer 23487 does not exist',
+ pcall_err(request, 'nvim_exec', 'buffer 23487', false))
+ eq('', eval('v:errmsg')) -- v:errmsg was not updated.
+ eq('', eval('v:exception'))
+ end)
+
+ it('recursion', function()
+ local fname = tmpname()
+ write_file(fname, 'let x1 = "set from :source file"\n')
+ -- nvim_exec
+ -- :source
+ -- nvim_exec
+ request('nvim_exec', [[
+ 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)
+ 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('overwritten', request('nvim_get_var', 'x4'))
+ eq('overwritten', request('nvim_get_var', 'x5'))
+ os.remove(fname)
+ end)
+
+ it('traceback', 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)
+ 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'..
+ '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))
+ 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))
+ end)
+ end)
+
describe('nvim_command', function()
it('works', function()
- local fname = helpers.tmpname()
+ local fname = tmpname()
nvim('command', 'new')
nvim('command', 'edit '..fname)
nvim('command', 'normal itesting\napi')
@@ -313,41 +434,44 @@ describe('API', function()
end)
end)
- describe('nvim_execute_lua', function()
+ describe('nvim_exec_lua', function()
it('works', function()
- meths.execute_lua('vim.api.nvim_set_var("test", 3)', {})
+ meths.exec_lua('vim.api.nvim_set_var("test", 3)', {})
eq(3, meths.get_var('test'))
- eq(17, meths.execute_lua('a, b = ...\nreturn a + b', {10,7}))
+ eq(17, meths.exec_lua('a, b = ...\nreturn a + b', {10,7}))
- eq(NIL, meths.execute_lua('function xx(a,b)\nreturn a..b\nend',{}))
+ eq(NIL, meths.exec_lua('function xx(a,b)\nreturn a..b\nend',{}))
+ eq("xy", meths.exec_lua('return xx(...)', {'x','y'}))
+
+ -- Deprecated name: nvim_execute_lua.
eq("xy", meths.execute_lua('return xx(...)', {'x','y'}))
end)
it('reports errors', function()
eq([[Error loading lua: [string "<nvim>"]:1: '=' expected near '+']],
- pcall_err(meths.execute_lua, 'a+*b', {}))
+ pcall_err(meths.exec_lua, 'a+*b', {}))
eq([[Error loading lua: [string "<nvim>"]:1: unexpected symbol near '1']],
- pcall_err(meths.execute_lua, '1+2', {}))
+ pcall_err(meths.exec_lua, '1+2', {}))
eq([[Error loading lua: [string "<nvim>"]:1: unexpected symbol]],
- pcall_err(meths.execute_lua, 'aa=bb\0', {}))
+ pcall_err(meths.exec_lua, 'aa=bb\0', {}))
eq([[Error executing lua: [string "<nvim>"]:1: attempt to call global 'bork' (a nil value)]],
- pcall_err(meths.execute_lua, 'bork()', {}))
+ pcall_err(meths.exec_lua, 'bork()', {}))
eq('Error executing lua: [string "<nvim>"]:1: did\nthe\nfail',
- pcall_err(meths.execute_lua, 'error("did\\nthe\\nfail")', {}))
+ pcall_err(meths.exec_lua, 'error("did\\nthe\\nfail")', {}))
end)
it('uses native float values', function()
- eq(2.5, meths.execute_lua("return select(1, ...)", {2.5}))
- eq("2.5", meths.execute_lua("return vim.inspect(...)", {2.5}))
+ eq(2.5, meths.exec_lua("return select(1, ...)", {2.5}))
+ eq("2.5", meths.exec_lua("return vim.inspect(...)", {2.5}))
-- "special" float values are still accepted as return values.
- eq(2.5, meths.execute_lua("return vim.api.nvim_eval('2.5')", {}))
- eq("{\n [false] = 2.5,\n [true] = 3\n}", meths.execute_lua("return vim.inspect(vim.api.nvim_eval('2.5'))", {}))
+ eq(2.5, meths.exec_lua("return vim.api.nvim_eval('2.5')", {}))
+ eq("{\n [false] = 2.5,\n [true] = 3\n}", meths.exec_lua("return vim.inspect(vim.api.nvim_eval('2.5'))", {}))
end)
end)
@@ -358,6 +482,11 @@ describe('API', function()
eq(true, status) -- nvim_input() did not fail.
eq("E117:", v_errnum) -- v:errmsg was updated.
end)
+
+ it('does not crash even if trans_special result is largest #11788, #12287', function()
+ command("call nvim_input('<M-'.nr2char(0x40000000).'>')")
+ eq(1, eval('1'))
+ end)
end)
describe('nvim_paste', function()
@@ -447,13 +576,35 @@ describe('API', function()
eq({0,7,1,0}, funcs.getpos('.'))
eq(false, nvim('get_option', 'paste'))
end)
+ it('Replace-mode', function()
+ -- Within single line
+ nvim('put', {'aabbccdd', 'eeffgghh', 'iijjkkll'}, "c", true, false)
+ command('normal l')
+ command('startreplace')
+ nvim('paste', '123456', true, -1)
+ expect([[
+ a123456d
+ eeffgghh
+ iijjkkll]])
+ command('%delete _')
+ -- Across lines
+ nvim('put', {'aabbccdd', 'eeffgghh', 'iijjkkll'}, "c", true, false)
+ command('normal l')
+ command('startreplace')
+ nvim('paste', '123\n456', true, -1)
+ expect([[
+ a123
+ 456d
+ eeffgghh
+ iijjkkll]])
+ end)
it('crlf=false does not break lines at CR, CRLF', function()
nvim('paste', 'line 1\r\n\r\rline 2\nline 3\rline 4\r', false, -1)
expect('line 1\r\n\r\rline 2\nline 3\rline 4\r')
eq({0,3,14,0}, funcs.getpos('.'))
end)
it('vim.paste() failure', function()
- nvim('execute_lua', 'vim.paste = (function(lines, phase) error("fake fail") end)', {})
+ nvim('exec_lua', 'vim.paste = (function(lines, phase) error("fake fail") end)', {})
eq([[Error executing lua: [string "<nvim>"]:1: fake fail]],
pcall_err(request, 'nvim_paste', 'line 1\nline 2\nline 3', false, 1))
end)
@@ -677,7 +828,7 @@ describe('API', function()
ok(nil ~= string.find(rv, 'noequalalways\n'..
'\tLast set from API client %(channel id %d+%)'))
- nvim('execute_lua', 'vim.api.nvim_set_option("equalalways", true)', {})
+ nvim('exec_lua', 'vim.api.nvim_set_option("equalalways", true)', {})
status, rv = pcall(nvim, 'command_output',
'verbose set equalalways?')
eq(true, status)
@@ -1662,7 +1813,7 @@ describe('API', function()
eq({id=1}, meths.get_current_buf())
end)
- it("doesn't cause BufEnter or BufWinEnter autocmds", function()
+ it("does not trigger BufEnter, BufWinEnter", function()
command("let g:fired = v:false")
command("au BufEnter,BufWinEnter * let g:fired = v:true")
@@ -1672,7 +1823,7 @@ describe('API', function()
eq(false, eval('g:fired'))
end)
- it('|scratch-buffer|', function()
+ it('scratch-buffer', function()
eq({id=2}, meths.create_buf(false, true))
eq({id=3}, meths.create_buf(true, true))
eq({id=4}, meths.create_buf(true, true))
@@ -1680,7 +1831,7 @@ describe('API', function()
eq(' 1 %a "[No Name]" line 1\n'..
' 3 h "[Scratch]" line 0\n'..
' 4 h "[Scratch]" line 0',
- meths.command_output("ls"))
+ meths.exec('ls', true))
-- current buffer didn't change
eq({id=1}, meths.get_current_buf())
@@ -1699,6 +1850,7 @@ describe('API', function()
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'))
end
--
@@ -1714,8 +1866,9 @@ describe('API', function()
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'))
- -- scratch buffer can be wiped without error
+ -- Scratch buffer can be wiped without error.
command('bwipe')
screen:expect([[
^ |
@@ -1730,4 +1883,27 @@ describe('API', function()
command('silent! call nvim_create_buf(0, 1)')
end)
end)
+
+ describe('nvim_get_runtime_file', function()
+ it('works', function()
+ eq({}, meths.get_runtime_file("bork.borkbork", false))
+ eq({}, meths.get_runtime_file("bork.borkbork", true))
+ eq(1, #meths.get_runtime_file("autoload/msgpack.vim", false))
+ eq(1, #meths.get_runtime_file("autoload/msgpack.vim", true))
+ local val = meths.get_runtime_file("autoload/remote/*.vim", true)
+ eq(2, #val)
+ local p = helpers.alter_slashes
+ if endswith(val[1], "define.vim") then
+ ok(endswith(val[1], p("autoload/remote/define.vim")))
+ ok(endswith(val[2], p("autoload/remote/host.vim")))
+ else
+ ok(endswith(val[1], p("autoload/remote/host.vim")))
+ ok(endswith(val[2], p("autoload/remote/define.vim")))
+ end
+ val = meths.get_runtime_file("autoload/remote/*.vim", false)
+ eq(1, #val)
+ ok(endswith(val[1], p("autoload/remote/define.vim"))
+ or endswith(val[1], p("autoload/remote/host.vim")))
+ end)
+ end)
end)
diff --git a/test/functional/api/window_spec.lua b/test/functional/api/window_spec.lua
index 17e0d3235c..8c7c3208c0 100644
--- a/test/functional/api/window_spec.lua
+++ b/test/functional/api/window_spec.lua
@@ -55,8 +55,8 @@ describe('API/win', function()
end)
it('validates args', function()
- eq('Invalid buffer id', pcall_err(window, 'set_buf', nvim('get_current_win'), 23))
- eq('Invalid window id', pcall_err(window, 'set_buf', 23, nvim('get_current_buf')))
+ 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)
end)
@@ -73,7 +73,7 @@ describe('API/win', function()
it('does not leak memory when using invalid window ID with invalid pos',
function()
- eq('Invalid window id', pcall_err(meths.win_set_cursor, 1, {"b\na"}))
+ eq('Invalid window id: 1', pcall_err(meths.win_set_cursor, 1, {"b\na"}))
end)
it('updates the screen, and also when the window is unfocused', function()
diff --git a/test/functional/autocmd/autocmd_spec.lua b/test/functional/autocmd/autocmd_spec.lua
index 805db9dd78..e62d3bb66b 100644
--- a/test/functional/autocmd/autocmd_spec.lua
+++ b/test/functional/autocmd/autocmd_spec.lua
@@ -1,6 +1,7 @@
local helpers = require('test.functional.helpers')(after_each)
local Screen = require('test.functional.ui.screen')
+local assert_visible = helpers.assert_visible
local dedent = helpers.dedent
local eq = helpers.eq
local eval = helpers.eval
@@ -18,26 +19,86 @@ local source = helpers.source
describe('autocmd', function()
before_each(clear)
- it(':tabnew triggers events in the correct order', function()
+ it(':tabnew, :split, :close events order, <afile>', function()
local expected = {
- 'WinLeave',
- 'TabLeave',
- 'WinEnter',
- 'TabNew',
- 'TabEnter',
- 'BufLeave',
- 'BufEnter'
+ {'WinLeave', ''},
+ {'TabLeave', ''},
+ {'WinEnter', ''},
+ {'TabNew', 'testfile1'}, -- :tabnew
+ {'TabEnter', ''},
+ {'BufLeave', ''},
+ {'BufEnter', 'testfile1'}, -- :split
+ {'WinLeave', 'testfile1'},
+ {'WinEnter', 'testfile1'},
+ {'WinLeave', 'testfile1'},
+ {'WinClosed', '1002'}, -- :close, WinClosed <afile> = window-id
+ {'WinEnter', 'testfile1'},
+ {'WinLeave', 'testfile1'}, -- :bdelete
+ {'WinEnter', 'testfile1'},
+ {'BufLeave', 'testfile1'},
+ {'BufEnter', 'testfile2'},
+ {'WinClosed', '1000'},
}
- command('let g:foo = []')
- command('autocmd BufEnter * :call add(g:foo, "BufEnter")')
- command('autocmd BufLeave * :call add(g:foo, "BufLeave")')
- command('autocmd TabEnter * :call add(g:foo, "TabEnter")')
- command('autocmd TabLeave * :call add(g:foo, "TabLeave")')
- command('autocmd TabNew * :call add(g:foo, "TabNew")')
- command('autocmd WinEnter * :call add(g:foo, "WinEnter")')
- command('autocmd WinLeave * :call add(g:foo, "WinLeave")')
- command('tabnew')
- assert.same(expected, eval('g:foo'))
+ command('let g:evs = []')
+ command('autocmd BufEnter * :call add(g:evs, ["BufEnter", expand("<afile>")])')
+ command('autocmd BufLeave * :call add(g:evs, ["BufLeave", expand("<afile>")])')
+ command('autocmd TabEnter * :call add(g:evs, ["TabEnter", expand("<afile>")])')
+ command('autocmd TabLeave * :call add(g:evs, ["TabLeave", expand("<afile>")])')
+ command('autocmd TabNew * :call add(g:evs, ["TabNew", expand("<afile>")])')
+ command('autocmd WinEnter * :call add(g:evs, ["WinEnter", expand("<afile>")])')
+ command('autocmd WinLeave * :call add(g:evs, ["WinLeave", expand("<afile>")])')
+ command('autocmd WinClosed * :call add(g:evs, ["WinClosed", expand("<afile>")])')
+ command('tabnew testfile1')
+ command('split')
+ command('close')
+ command('new testfile2')
+ command('bdelete 1')
+ eq(expected, eval('g:evs'))
+ end)
+
+ it('WinClosed is non-recursive', function()
+ command('let g:triggered = 0')
+ command('autocmd WinClosed * :let g:triggered+=1 | :bdelete 2')
+ command('new testfile2')
+ command('new testfile3')
+
+ -- All 3 buffers are visible.
+ assert_visible(1, true)
+ assert_visible(2, true)
+ assert_visible(3, true)
+
+ -- Trigger WinClosed, which also deletes buffer/window 2.
+ command('bdelete 1')
+
+ -- Buffers 1 and 2 were closed but WinClosed was triggered only once.
+ eq(1, eval('g:triggered'))
+ assert_visible(1, false)
+ assert_visible(2, false)
+ assert_visible(3, true)
+ end)
+
+ it('WinClosed from a different tabpage', function()
+ command('let g:evs = []')
+ command('edit tesfile1')
+ command('autocmd WinClosed <buffer> :call add(g:evs, ["WinClosed", expand("<abuf>")])')
+ local buf1 = eval("bufnr('%')")
+ command('new')
+ local buf2 = eval("bufnr('%')")
+ command('autocmd WinClosed <buffer> :call add(g:evs, ["WinClosed", expand("<abuf>")])'
+ -- Attempt recursion.
+ ..' | bdelete '..buf2)
+ command('tabedit testfile2')
+ command('tabedit testfile3')
+ command('bdelete '..buf2)
+ -- Non-recursive: only triggered once.
+ eq({
+ {'WinClosed', '2'},
+ }, eval('g:evs'))
+ command('bdelete '..buf1)
+ eq({
+ {'WinClosed', '2'},
+ {'WinClosed', '1'},
+ }, eval('g:evs'))
end)
it('v:vim_did_enter is 1 after VimEnter', function()
@@ -219,7 +280,7 @@ describe('autocmd', function()
eq(7, eval('g:test'))
-- API calls are blocked when aucmd_win is not in scope
- eq('Vim(call):E5555: API call: Invalid window id',
+ eq('Vim(call):E5555: API call: Invalid window id: 1001',
pcall_err(command, "call nvim_set_current_win(g:winid)"))
-- second time aucmd_win is needed, a different code path is invoked
@@ -257,7 +318,7 @@ describe('autocmd', function()
eq(0, eval('g:had_value'))
eq(7, eval('g:test'))
- eq('Vim(call):E5555: API call: Invalid window id',
+ eq('Vim(call):E5555: API call: Invalid window id: 1001',
pcall_err(command, "call nvim_set_current_win(g:winid)"))
end)
diff --git a/test/functional/autocmd/cmdline_spec.lua b/test/functional/autocmd/cmdline_spec.lua
index 51b7b819e9..8ec06dc148 100644
--- a/test/functional/autocmd/cmdline_spec.lua
+++ b/test/functional/autocmd/cmdline_spec.lua
@@ -33,7 +33,7 @@ describe('cmdline autocommands', function()
eq({'notification', 'CmdlineEnter', {{cmdtype=':', cmdlevel=1}}}, next_msg())
-- note: feed('bork<c-c>') might not consume 'bork'
- -- due to out-of-band interupt handling
+ -- due to out-of-band interrupt handling
feed('bork<esc>')
eq({'notification', 'CmdlineLeave',
{{cmdtype=':', cmdlevel=1, abort=true}}}, next_msg())
diff --git a/test/functional/autocmd/tabclose_spec.lua b/test/functional/autocmd/tabclose_spec.lua
index b7c33dc3d8..92d860c628 100644
--- a/test/functional/autocmd/tabclose_spec.lua
+++ b/test/functional/autocmd/tabclose_spec.lua
@@ -11,10 +11,10 @@ describe('TabClosed', function()
repeat
nvim('command', 'tabnew')
until nvim('eval', 'tabpagenr()') == 6 -- current tab is now 6
- eq("tabclosed:6:6:5", nvim('command_output', 'tabclose')) -- close last 6, current tab is now 5
- eq("tabclosed:5:5:4", nvim('command_output', 'close')) -- close last window on tab, closes tab
- eq("tabclosed:2:2:3", nvim('command_output', '2tabclose')) -- close tab 2, current tab is now 3
- eq("tabclosed:1:1:2\ntabclosed:1:1:1", nvim('command_output', 'tabonly')) -- close tabs 1 and 2
+ eq("tabclosed:6:6:5", nvim('exec', 'tabclose', true)) -- close last 6, current tab is now 5
+ eq("tabclosed:5:5:4", nvim('exec', 'close', true)) -- close last window on tab, closes tab
+ eq("tabclosed:2:2:3", nvim('exec', '2tabclose', true)) -- close tab 2, current tab is now 3
+ eq("tabclosed:1:1:2\ntabclosed:1:1:1", nvim('exec', 'tabonly', true)) -- close tabs 1 and 2
end)
it('is triggered when closing a window via bdelete from another tab', function()
@@ -23,7 +23,7 @@ describe('TabClosed', function()
nvim('command', '1tabedit Xtestfile')
nvim('command', 'normal! 1gt')
eq({1, 3}, nvim('eval', '[tabpagenr(), tabpagenr("$")]'))
- eq("tabclosed:2:2:1\ntabclosed:2:2:1", nvim('command_output', 'bdelete Xtestfile'))
+ eq("tabclosed:2:2:1\ntabclosed:2:2:1", nvim('exec', 'bdelete Xtestfile', true))
eq({1, 1}, nvim('eval', '[tabpagenr(), tabpagenr("$")]'))
end)
@@ -35,7 +35,7 @@ describe('TabClosed', function()
-- Only one tab is closed, and the alternate file is used for the other.
eq({2, 3}, nvim('eval', '[tabpagenr(), tabpagenr("$")]'))
- eq("tabclosed:2:2:2", nvim('command_output', 'bdelete Xtestfile2'))
+ eq("tabclosed:2:2:2", nvim('exec', 'bdelete Xtestfile2', true))
eq('Xtestfile1', nvim('eval', 'bufname("")'))
end)
end)
@@ -48,9 +48,9 @@ describe('TabClosed', function()
nvim('command', 'tabnew')
until nvim('eval', 'tabpagenr()') == 7 -- current tab is now 7
-- sanity check, we shouldn't match on tabs with numbers other than 2
- eq("tabclosed:7:7:6", nvim('command_output', 'tabclose'))
+ eq("tabclosed:7:7:6", nvim('exec', 'tabclose', true))
-- close tab page 2, current tab is now 5
- eq("tabclosed:2:2:5\ntabclosed:match", nvim('command_output', '2tabclose'))
+ eq("tabclosed:2:2:5\ntabclosed:match", nvim('exec', '2tabclose', true))
end)
end)
@@ -59,7 +59,7 @@ describe('TabClosed', function()
nvim('command', 'au! TabClosed * echom "tabclosed:".expand("<afile>").":".expand("<amatch>").":".tabpagenr()')
nvim('command', 'tabedit Xtestfile')
eq({2, 2}, nvim('eval', '[tabpagenr(), tabpagenr("$")]'))
- eq("tabclosed:2:2:1", nvim('command_output', 'close'))
+ eq("tabclosed:2:2:1", nvim('exec', 'close', true))
eq({1, 1}, nvim('eval', '[tabpagenr(), tabpagenr("$")]'))
end)
end)
diff --git a/test/functional/autocmd/tabnewentered_spec.lua b/test/functional/autocmd/tabnewentered_spec.lua
index 59cac07b34..dc2fd3e97d 100644
--- a/test/functional/autocmd/tabnewentered_spec.lua
+++ b/test/functional/autocmd/tabnewentered_spec.lua
@@ -1,5 +1,13 @@
local helpers = require('test.functional.helpers')(after_each)
-local clear, nvim, eq = helpers.clear, helpers.nvim, helpers.eq
+
+local clear = helpers.clear
+local command = helpers.command
+local dedent = helpers.dedent
+local eval = helpers.eval
+local eq = helpers.eq
+local feed = helpers.feed
+local nvim = helpers.nvim
+local redir_exec = helpers.redir_exec
describe('TabNewEntered', function()
describe('au TabNewEntered', function()
@@ -7,15 +15,15 @@ describe('TabNewEntered', function()
it('matches when entering any new tab', function()
clear()
nvim('command', 'au! TabNewEntered * echom "tabnewentered:".tabpagenr().":".bufnr("")')
- eq("tabnewentered:2:2", nvim('command_output', 'tabnew'))
- eq("tabnewentered:3:3", nvim('command_output', 'tabnew test.x2'))
+ eq("tabnewentered:2:2", nvim('exec', 'tabnew', true))
+ eq("tabnewentered:3:3", nvim('exec', 'tabnew test.x2', true))
end)
end)
describe('with FILE as <afile>', function()
it('matches when opening a new tab for FILE', function()
nvim('command', 'au! TabNewEntered Xtest-tabnewentered echom "tabnewentered:match"')
eq('tabnewentered:4:4\ntabnewentered:match',
- nvim('command_output', 'tabnew Xtest-tabnewentered'))
+ nvim('exec', 'tabnew Xtest-tabnewentered', true))
end)
end)
describe('with CTRL-W T', function()
@@ -24,8 +32,553 @@ describe('TabNewEntered', function()
nvim('command', 'au! TabNewEntered * echom "entered"')
nvim('command', 'tabnew test.x2')
nvim('command', 'split')
- eq('entered', nvim('command_output', 'execute "normal \\<C-W>T"'))
+ eq('entered', nvim('exec', 'execute "normal \\<C-W>T"', true))
end)
end)
end)
end)
+
+describe('TabEnter', function()
+ before_each(clear)
+ it('has correct previous tab when entering any new tab', function()
+ command('augroup TEMP')
+ nvim('command', 'au! TabEnter * echom "tabenter:".tabpagenr().":".tabpagenr(\'#\')')
+ command('augroup END')
+ eq("tabenter:2:1", nvim('exec', 'tabnew', true))
+ eq("tabenter:3:2", nvim('exec', 'tabnew test.x2', true))
+ command('augroup! TEMP')
+ end)
+ it('has correct previous tab when entering any preexisting tab', function()
+ command('tabnew')
+ command('tabnew')
+ command('augroup TEMP')
+ nvim('command', 'au! TabEnter * echom "tabenter:".tabpagenr().":".tabpagenr(\'#\')')
+ command('augroup END')
+ eq("tabenter:1:3", nvim('exec', 'tabnext', true))
+ eq("tabenter:2:1", nvim('exec', 'tabnext', true))
+ command('augroup! TEMP')
+ end)
+end)
+
+describe('tabpage/previous', function()
+ before_each(clear)
+ local function switches_to_previous_after_new_tab_creation_at_end(characters)
+ return function()
+ -- Add three tabs for a total of four
+ command('tabnew')
+ command('tabnew')
+ command('tabnew')
+
+ -- The previous tab is now the third.
+ eq(3, eval('tabpagenr(\'#\')'))
+
+ -- Switch to the previous (third) tab
+ feed(characters)
+
+ eq(dedent([=[
+
+
+ Tab page 1
+ [No Name]
+ Tab page 2
+ [No Name]
+ Tab page 3
+ > [No Name]
+ Tab page 4
+ # [No Name]]=]),
+ redir_exec('tabs')
+ )
+
+ -- The previous tab is now the fourth.
+ eq(4, eval('tabpagenr(\'#\')'))
+ end
+ end
+ it('switches to previous via g<Tab> after new tab creation at end',
+ switches_to_previous_after_new_tab_creation_at_end('g<Tab>'))
+ it('switches to previous via <C-W>g<Tab>. after new tab creation at end', switches_to_previous_after_new_tab_creation_at_end('<C-W>g<Tab>'))
+ it('switches to previous via <C-Tab>. after new tab creation at end', switches_to_previous_after_new_tab_creation_at_end('<C-Tab>'))
+ it('switches to previous via :tabn #<CR>. after new tab creation at end', switches_to_previous_after_new_tab_creation_at_end(':tabn #<CR>'))
+
+ local function switches_to_previous_after_new_tab_creation_in_middle(characters)
+ return function()
+ -- Add three tabs for a total of four
+ command('tabnew')
+ command('tabnew')
+ command('tabnew')
+ -- Switch to the second tab
+ command('tabnext 2')
+ -- Add a new tab after the second tab
+ command('tabnew')
+
+ -- The previous tab is now the second.
+ eq(2, eval('tabpagenr(\'#\')'))
+
+ -- Switch to the previous (second) tab
+ feed(characters)
+ eq(dedent([=[
+
+
+ Tab page 1
+ [No Name]
+ Tab page 2
+ > [No Name]
+ Tab page 3
+ # [No Name]
+ Tab page 4
+ [No Name]
+ Tab page 5
+ [No Name]]=]),
+ redir_exec('tabs')
+ )
+
+ -- The previous tab is now the third.
+ eq(3, eval('tabpagenr(\'#\')'))
+ end
+ end
+ it('switches to previous via g<Tab> after new tab creation in middle',
+ switches_to_previous_after_new_tab_creation_in_middle('g<Tab>'))
+ it('switches to previous via <C-W>g<Tab> after new tab creation in middle',
+ switches_to_previous_after_new_tab_creation_in_middle('<C-W>g<Tab>'))
+ it('switches to previous via <C-Tab> after new tab creation in middle',
+ switches_to_previous_after_new_tab_creation_in_middle('<C-Tab>'))
+ it('switches to previous via :tabn #<CR> after new tab creation in middle',
+ switches_to_previous_after_new_tab_creation_in_middle(':tabn #<CR>'))
+
+ local function switches_to_previous_after_switching_to_next_tab(characters)
+ return function()
+ -- Add three tabs for a total of four
+ command('tabnew')
+ command('tabnew')
+ command('tabnew')
+ -- Switch to the next (first) tab
+ command('tabnext')
+
+ -- The previous tab is now the fourth.
+ eq(4, eval('tabpagenr(\'#\')'))
+
+ -- Switch to the previous (fourth) tab
+ feed(characters)
+
+ eq(dedent([=[
+
+
+ Tab page 1
+ # [No Name]
+ Tab page 2
+ [No Name]
+ Tab page 3
+ [No Name]
+ Tab page 4
+ > [No Name]]=]),
+ redir_exec('tabs')
+ )
+
+ -- The previous tab is now the first.
+ eq(1, eval('tabpagenr(\'#\')'))
+ end
+ end
+ it('switches to previous via g<Tab> after switching to next tab',
+ switches_to_previous_after_switching_to_next_tab('g<Tab>'))
+ it('switches to previous via <C-W>g<Tab> after switching to next tab',
+ switches_to_previous_after_switching_to_next_tab('<C-W>g<Tab>'))
+ it('switches to previous via <C-Tab> after switching to next tab',
+ switches_to_previous_after_switching_to_next_tab('<C-Tab>'))
+ it('switches to previous via :tabn #<CR> after switching to next tab',
+ switches_to_previous_after_switching_to_next_tab(':tabn #<CR>'))
+
+ local function switches_to_previous_after_switching_to_last_tab(characters)
+ return function()
+ -- Add three tabs for a total of four
+ command('tabnew')
+ command('tabnew')
+ command('tabnew')
+ -- Switch to the next (first) tab
+ command('tabnext')
+ -- Switch to the last (fourth) tab.
+ command('tablast')
+
+ -- The previous tab is now the second.
+ eq(1, eval('tabpagenr(\'#\')'))
+
+ -- Switch to the previous (second) tab
+ feed(characters)
+
+ eq(dedent([=[
+
+
+ Tab page 1
+ > [No Name]
+ Tab page 2
+ [No Name]
+ Tab page 3
+ [No Name]
+ Tab page 4
+ # [No Name]]=]),
+ redir_exec('tabs')
+ )
+
+ -- The previous tab is now the fourth.
+ eq(4, eval('tabpagenr(\'#\')'))
+ end
+ end
+ it('switches to previous after switching to last tab',
+ switches_to_previous_after_switching_to_last_tab('g<Tab>'))
+ it('switches to previous after switching to last tab',
+ switches_to_previous_after_switching_to_last_tab('<C-W>g<Tab>'))
+ it('switches to previous after switching to last tab',
+ switches_to_previous_after_switching_to_last_tab('<C-Tab>'))
+ it('switches to previous after switching to last tab',
+ switches_to_previous_after_switching_to_last_tab(':tabn #<CR>'))
+
+ local function switches_to_previous_after_switching_to_previous_tab(characters)
+ return function()
+ -- Add three tabs for a total of four
+ command('tabnew')
+ command('tabnew')
+ command('tabnew')
+ -- Switch to the previous (third) tab
+ command('tabprevious')
+
+ -- The previous tab is now the fourth.
+ eq(4, eval('tabpagenr(\'#\')'))
+
+ -- Switch to the previous (fourth) tab
+ feed(characters)
+
+ eq(dedent([=[
+
+
+ Tab page 1
+ [No Name]
+ Tab page 2
+ [No Name]
+ Tab page 3
+ # [No Name]
+ Tab page 4
+ > [No Name]]=]),
+ redir_exec('tabs')
+ )
+
+ -- The previous tab is now the third.
+ eq(3, eval('tabpagenr(\'#\')'))
+ end
+ end
+ it('switches to previous via g<Tab> after switching to previous tab',
+ switches_to_previous_after_switching_to_previous_tab('g<Tab>'))
+ it('switches to previous via <C-W>g<Tab> after switching to previous tab',
+ switches_to_previous_after_switching_to_previous_tab('<C-W>g<Tab>'))
+ it('switches to previous via <C-Tab> after switching to previous tab',
+ switches_to_previous_after_switching_to_previous_tab('<C-Tab>'))
+ it('switches to previous via :tabn #<CR> after switching to previous tab',
+ switches_to_previous_after_switching_to_previous_tab(':tabn #<CR>'))
+
+ local function switches_to_previous_after_switching_to_first_tab(characters)
+ return function()
+ -- Add three tabs for a total of four
+ command('tabnew')
+ command('tabnew')
+ command('tabnew')
+ -- Switch to the previous (third) tab
+ command('tabprevious')
+ -- Switch to the first tab
+ command('tabfirst')
+
+ -- The previous tab is now the third.
+ eq(3, eval('tabpagenr(\'#\')'))
+
+ -- Switch to the previous (third) tab
+ feed(characters)
+
+ eq(dedent([=[
+
+
+ Tab page 1
+ # [No Name]
+ Tab page 2
+ [No Name]
+ Tab page 3
+ > [No Name]
+ Tab page 4
+ [No Name]]=]),
+ redir_exec('tabs')
+ )
+
+ -- The previous tab is now the first.
+ eq(1, eval('tabpagenr(\'#\')'))
+ end
+ end
+ it('switches to previous via g<Tab> after switching to first tab',
+ switches_to_previous_after_switching_to_first_tab('g<Tab>'))
+ it('switches to previous via <C-W>g<Tab> after switching to first tab',
+ switches_to_previous_after_switching_to_first_tab('<C-W>g<Tab>'))
+ it('switches to previous via <C-Tab> after switching to first tab',
+ switches_to_previous_after_switching_to_first_tab('<C-Tab>'))
+ it('switches to previous via :tabn #<CR> after switching to first tab',
+ switches_to_previous_after_switching_to_first_tab(':tabn #<CR>'))
+
+ local function switches_to_previous_after_numbered_tab_switch(characters)
+ return function()
+ -- Add three tabs for a total of four
+ command('tabnew')
+ command('tabnew')
+ command('tabnew')
+ -- Switch to the second tab
+ command('tabnext 2')
+
+ -- The previous tab is now the fourth.
+ eq(4, eval('tabpagenr(\'#\')'))
+
+ -- Switch to the previous (fourth) tab
+ feed(characters)
+
+ eq(dedent([=[
+
+
+ Tab page 1
+ [No Name]
+ Tab page 2
+ # [No Name]
+ Tab page 3
+ [No Name]
+ Tab page 4
+ > [No Name]]=]),
+ redir_exec('tabs')
+ )
+
+ -- The previous tab is now the second.
+ eq(2, eval('tabpagenr(\'#\')'))
+ end
+ end
+ it('switches to previous via g<Tab> after numbered tab switch',
+ switches_to_previous_after_numbered_tab_switch('g<Tab>'))
+ it('switches to previous via <C-W>g<Tab> after numbered tab switch',
+ switches_to_previous_after_numbered_tab_switch('<C-W>g<Tab>'))
+ it('switches to previous via <C-Tab> after numbered tab switch',
+ switches_to_previous_after_numbered_tab_switch('<C-Tab>'))
+ it('switches to previous via :tabn #<CR> after numbered tab switch',
+ switches_to_previous_after_numbered_tab_switch(':tabn #<CR>'))
+
+ local function switches_to_previous_after_switching_to_previous(characters1, characters2)
+ return function()
+ -- Add three tabs for a total of four
+ command('tabnew')
+ command('tabnew')
+ command('tabnew')
+ -- Switch to the second tab
+ command('tabnext 2')
+ -- Switch to the previous (fourth) tab
+ feed(characters1)
+
+ -- The previous tab is now the second.
+ eq(2, eval('tabpagenr(\'#\')'))
+
+ -- Switch to the previous (second) tab
+ feed(characters2)
+
+ eq(dedent([=[
+
+
+ Tab page 1
+ [No Name]
+ Tab page 2
+ > [No Name]
+ Tab page 3
+ [No Name]
+ Tab page 4
+ # [No Name]]=]),
+ redir_exec('tabs')
+ )
+
+ -- The previous tab is now the fourth.
+ eq(4, eval('tabpagenr(\'#\')'))
+ end
+ end
+ it('switches to previous via g<Tab> after switching to previous via g<Tab>',
+ switches_to_previous_after_switching_to_previous('g<Tab>', 'g<Tab>'))
+ it('switches to previous via <C-W>g<Tab> after switching to previous via g<Tab>',
+ switches_to_previous_after_switching_to_previous('g<Tab>', '<C-W>g<Tab>'))
+ it('switches to previous via <C-Tab> after switching to previous via g<Tab>',
+ switches_to_previous_after_switching_to_previous('g<Tab>', '<C-Tab>'))
+ it('switches to previous via :tabn #<CR> after switching to previous via g<Tab>',
+ switches_to_previous_after_switching_to_previous('g<Tab>', ':tabn #<CR>'))
+ it('switches to previous via g<Tab> after switching to previous via <C-W>g<Tab>',
+ switches_to_previous_after_switching_to_previous('<C-W>g<Tab>', 'g<Tab>'))
+ it('switches to previous via <C-W>g<Tab> after switching to previous via <C-W>g<Tab>',
+ switches_to_previous_after_switching_to_previous('<C-W>g<Tab>', '<C-W>g<Tab>'))
+ it('switches to previous via <C-Tab> after switching to previous via <C-W>g<Tab>',
+ switches_to_previous_after_switching_to_previous('<C-W>g<Tab>', '<C-Tab>'))
+ it('switches to previous via :tabn #<CR> after switching to previous via <C-W>g<Tab>',
+ switches_to_previous_after_switching_to_previous('<C-W>g<Tab>', ':tabn #<CR>'))
+ it('switches to previous via g<Tab> after switching to previous via <C-Tab>',
+ switches_to_previous_after_switching_to_previous('<C-Tab>', 'g<Tab>'))
+ it('switches to previous via <C-W>g<Tab> after switching to previous via <C-Tab>',
+ switches_to_previous_after_switching_to_previous('<C-Tab>', '<C-W>g<Tab>'))
+ it('switches to previous via <C-Tab> after switching to previous via <C-Tab>',
+ switches_to_previous_after_switching_to_previous('<C-Tab>', '<C-Tab>'))
+ it('switches to previous via :tabn #<CR> after switching to previous via <C-Tab>',
+ switches_to_previous_after_switching_to_previous('<C-Tab>', ':tabn #<CR>'))
+ it('switches to previous via g<Tab> after switching to previous via :tabn #<CR>',
+ switches_to_previous_after_switching_to_previous(':tabn #<CR>', 'g<Tab>'))
+ it('switches to previous via <C-W>g<Tab> after switching to previous via :tabn #<CR>',
+ switches_to_previous_after_switching_to_previous(':tabn #<CR>', '<C-W>g<Tab>'))
+ it('switches to previous via <C-Tab> after switching to previous via <C-Tab>',
+ switches_to_previous_after_switching_to_previous(':tabn #<CR>', '<C-Tab>'))
+ it('switches to previous via :tabn #<CR> after switching to previous via :tabn #<CR>',
+ switches_to_previous_after_switching_to_previous(':tabn #<CR>', ':tabn #<CR>'))
+
+ local function does_not_switch_to_previous_after_closing_current_tab(characters)
+ return function()
+ -- Add three tabs for a total of four
+ command('tabnew')
+ command('tabnew')
+ command('tabnew')
+ -- Close the current (fourth tab)
+ command('wincmd c')
+
+ -- The previous tab is now the "zeroth" -- there isn't one.
+ eq(0, eval('tabpagenr(\'#\')'))
+
+ -- At this point, switching to the "previous" (i.e. fourth) tab would mean
+ -- switching to either a dangling or a null pointer.
+ feed(characters)
+
+ eq(dedent([=[
+
+
+ Tab page 1
+ [No Name]
+ Tab page 2
+ [No Name]
+ Tab page 3
+ > [No Name]]=]),
+ redir_exec('tabs')
+ )
+
+ -- The previous tab is now the "zero".
+ eq(0, eval('tabpagenr(\'#\')'))
+ end
+ end
+ it('does not switch to previous via g<Tab> after closing current tab',
+ does_not_switch_to_previous_after_closing_current_tab('g<Tab>'))
+ it('does not switch to previous via <C-W>g<Tab> after closing current tab',
+ does_not_switch_to_previous_after_closing_current_tab('<C-W>g<Tab>'))
+ it('does not switch to previous via <C-Tab> after closing current tab',
+ does_not_switch_to_previous_after_closing_current_tab('<C-Tab>'))
+ it('does not switch to previous via :tabn #<CR> after closing current tab',
+ does_not_switch_to_previous_after_closing_current_tab(':tabn #<CR>'))
+
+ local function does_not_switch_to_previous_after_entering_operator_pending(characters)
+ return function()
+ -- Add three tabs for a total of four
+ command('tabnew')
+ command('tabnew')
+ command('tabnew')
+
+ -- The previous tab is now the third.
+ eq(3, eval('tabpagenr(\'#\')'))
+
+ -- Enter operator pending mode.
+ feed('d')
+ eq('no', eval('mode(1)'))
+
+ -- At this point switching to the previous tab should have no effect
+ -- other than leaving operator pending mode.
+ feed(characters)
+
+ -- Attempting to switch tabs returns us to normal mode.
+ eq('n', eval('mode()'))
+
+ -- The current tab is still the fourth.
+ eq(4, eval('tabpagenr()'))
+
+ -- The previous tab is still the third.
+ eq(3, eval('tabpagenr(\'#\')'))
+ end
+ end
+ it('does not switch to previous via g<Tab> after entering operator pending',
+ does_not_switch_to_previous_after_entering_operator_pending('g<Tab>'))
+ -- NOTE: When in operator pending mode, attempting to switch to previous has
+ -- the following effect:
+ -- - Ctrl-W exits operator pending mode
+ -- - g<Tab> switches to the previous tab
+ -- In other words, the effect of "<C-W>g<Tab>" is to switch to the
+ -- previous tab even from operator pending mode, but only thanks to the
+ -- fact that the suffix after "<C-W>" in "<C-W>g<Tab>" just happens to
+ -- be the same as the normal mode command to switch to the previous tab.
+ -- it('does not switch to previous via <C-W>g<Tab> after entering operator pending',
+ -- does_not_switch_to_previous_after_entering_operator_pending('<C-W>g<Tab>'))
+ it('does not switch to previous via <C-Tab> after entering operator pending',
+ does_not_switch_to_previous_after_entering_operator_pending('<C-Tab>'))
+ -- NOTE: When in operator pending mode, pressing : leaves operator pending
+ -- mode and enters command mode, so :tabn #<CR> does in fact switch
+ -- tabs.
+ -- it('does not switch to previous via :tabn #<CR> after entering operator pending',
+ -- does_not_switch_to_previous_after_entering_operator_pending(':tabn #<CR>'))
+
+ local function cmdline_win_prevents_tab_switch(characters, completion_visible)
+ return function()
+ -- Add three tabs for a total of four
+ command('tabnew')
+ command('tabnew')
+ command('tabnew')
+
+ -- The previous tab is now the third.
+ eq(3, eval('tabpagenr(\'#\')'))
+
+ -- Edit : command line in command-line window
+ feed('q:')
+
+ local cmdline_win_id = eval('win_getid()')
+
+ -- At this point switching to the previous tab should have no effect.
+ feed(characters)
+
+ -- Attempting to switch tabs maintains the current window.
+ eq(cmdline_win_id, eval('win_getid()'))
+ eq(completion_visible, eval('complete_info().pum_visible'))
+
+ -- The current tab is still the fourth.
+ eq(4, eval('tabpagenr()'))
+
+ -- The previous tab is still the third.
+ eq(3, eval('tabpagenr(\'#\')'))
+ end
+ end
+ it('cmdline-win prevents tab switch via g<Tab>',
+ cmdline_win_prevents_tab_switch('g<Tab>', 0))
+ it('cmdline-win prevents tab switch via <C-W>g<Tab>',
+ cmdline_win_prevents_tab_switch('<C-W>g<Tab>', 1))
+ it('cmdline-win prevents tab switch via <C-Tab>',
+ cmdline_win_prevents_tab_switch('<C-Tab>', 0))
+ it('cmdline-win prevents tab switch via :tabn #<CR>',
+ cmdline_win_prevents_tab_switch(':tabn #<CR>', 0))
+
+ it(':tabs indicates correct prevtab curwin', function()
+ -- Add three tabs for a total of four
+ command('tabnew')
+ command('tabnew')
+ command('split')
+ command('vsplit')
+ feed('<C-w>p')
+ command('tabnew')
+
+ -- The previous tab is now the three.
+ eq(3, eval('tabpagenr(\'#\')'))
+
+ eq(dedent([=[
+
+
+ Tab page 1
+ [No Name]
+ Tab page 2
+ [No Name]
+ Tab page 3
+ [No Name]
+ # [No Name]
+ [No Name]
+ Tab page 4
+ > [No Name]]=]),
+ redir_exec('tabs')
+ )
+ end)
+end)
diff --git a/test/functional/autocmd/textyankpost_spec.lua b/test/functional/autocmd/textyankpost_spec.lua
index 8c23b72cff..3898d59e58 100644
--- a/test/functional/autocmd/textyankpost_spec.lua
+++ b/test/functional/autocmd/textyankpost_spec.lua
@@ -27,7 +27,8 @@ describe('TextYankPost', function()
operator = 'y',
regcontents = { 'foo\nbar' },
regname = '',
- regtype = 'V'
+ regtype = 'V',
+ visual = false
}, eval('g:event'))
eq(1, eval('g:count'))
@@ -40,7 +41,8 @@ describe('TextYankPost', function()
operator = 'y',
regcontents = { 'baz ' },
regname = '',
- regtype = 'v'
+ regtype = 'v',
+ visual = false
}, eval('g:event'))
eq(2, eval('g:count'))
@@ -50,7 +52,8 @@ describe('TextYankPost', function()
operator = 'y',
regcontents = { 'foo', 'baz' },
regname = '',
- regtype = "\0223" -- ^V + block width
+ regtype = "\0223", -- ^V + block width
+ visual = true
}, eval('g:event'))
eq(3, eval('g:count'))
end)
@@ -62,7 +65,8 @@ describe('TextYankPost', function()
operator = 'y',
regcontents = { 'foo\nbar' },
regname = '',
- regtype = 'V'
+ regtype = 'V',
+ visual = false
}, eval('g:event'))
command('set debug=msg')
@@ -92,7 +96,8 @@ describe('TextYankPost', function()
operator = 'y',
regcontents = { 'foo\nbar' },
regname = '',
- regtype = 'V'
+ regtype = 'V',
+ visual = false
}, eval('g:event'))
eq(1, eval('g:count'))
eq({ 'foo\nbar' }, funcs.getreg('+',1,1))
@@ -105,7 +110,8 @@ describe('TextYankPost', function()
operator = 'd',
regcontents = { 'foo' },
regname = '',
- regtype = 'v'
+ regtype = 'v',
+ visual = false
}, eval('g:event'))
eq(1, eval('g:count'))
@@ -115,7 +121,8 @@ describe('TextYankPost', function()
operator = 'd',
regcontents = { '\nbar' },
regname = '',
- regtype = 'V'
+ regtype = 'V',
+ visual = false
}, eval('g:event'))
eq(2, eval('g:count'))
@@ -125,7 +132,8 @@ describe('TextYankPost', function()
operator = 'c',
regcontents = { 'baz' },
regname = '',
- regtype = 'v'
+ regtype = 'v',
+ visual = false
}, eval('g:event'))
eq(3, eval('g:count'))
end)
@@ -153,7 +161,8 @@ describe('TextYankPost', function()
operator = 'y',
regcontents = { 'bar' },
regname = 'b',
- regtype = 'v'
+ regtype = 'v',
+ visual = false
}, eval('g:event'))
feed('"*yy')
@@ -162,7 +171,8 @@ describe('TextYankPost', function()
operator = 'y',
regcontents = { 'foo\nbar' },
regname = '*',
- regtype = 'V'
+ regtype = 'V',
+ visual = false
}, eval('g:event'))
command("set clipboard=unnamed")
@@ -174,7 +184,8 @@ describe('TextYankPost', function()
operator = 'y',
regcontents = { 'foo\nbar' },
regname = '',
- regtype = 'V'
+ regtype = 'V',
+ visual = false
}, eval('g:event'))
feed('"*yy')
@@ -183,7 +194,8 @@ describe('TextYankPost', function()
operator = 'y',
regcontents = { 'foo\nbar' },
regname = '*',
- regtype = 'V'
+ regtype = 'V',
+ visual = false
}, eval('g:event'))
end)
@@ -194,7 +206,8 @@ describe('TextYankPost', function()
operator = 'd',
regcontents = { 'foo\nbar' },
regname = '+',
- regtype = 'V'
+ regtype = 'V',
+ visual = false
}, eval('g:event'))
eq(1, eval('g:count'))
@@ -204,7 +217,8 @@ describe('TextYankPost', function()
operator = 'y',
regcontents = { 'baz text' },
regname = '',
- regtype = 'V'
+ regtype = 'V',
+ visual = false
}, eval('g:event'))
eq(2, eval('g:count'))
@@ -214,7 +228,8 @@ describe('TextYankPost', function()
operator = 'y',
regcontents = { 'baz ' },
regname = '',
- regtype = 'v'
+ regtype = 'v',
+ visual = false
}, eval('g:event'))
eq(3, eval('g:count'))
@@ -224,7 +239,8 @@ describe('TextYankPost', function()
operator = 'd',
regcontents = { 'baz text' },
regname = '',
- regtype = 'V'
+ regtype = 'V',
+ visual = false
}, eval('g:event'))
eq(4, eval('g:count'))
end)
diff --git a/test/functional/autoread/focus_spec.lua b/test/functional/autoread/focus_spec.lua
new file mode 100644
index 0000000000..1d52e9948f
--- /dev/null
+++ b/test/functional/autoread/focus_spec.lua
@@ -0,0 +1,58 @@
+local helpers = require('test.functional.helpers')(after_each)
+local thelpers = require('test.functional.terminal.helpers')
+local lfs = require('lfs')
+local clear = helpers.clear
+local nvim_prog = helpers.nvim_prog
+local feed_command = helpers.feed_command
+local feed_data = thelpers.feed_data
+
+if helpers.pending_win32(pending) then return end
+
+describe('autoread TUI FocusGained/FocusLost', function()
+ local screen
+
+ before_each(function()
+ clear()
+ screen = thelpers.screen_setup(0, '["'..nvim_prog
+ ..'", "-u", "NONE", "-i", "NONE", "--cmd", "set noswapfile noshowcmd noruler"]')
+ end)
+
+ it('external file change', function()
+ local path = 'xtest-foo'
+ local expected_addition = [[
+ line 1
+ line 2
+ line 3
+ line 4
+ ]]
+
+ helpers.write_file(path, '')
+ lfs.touch(path, os.time() - 10)
+ feed_command('edit '..path)
+ feed_data('\027[O')
+
+ screen:expect{grid=[[
+ {1: } |
+ {4:~ }|
+ {4:~ }|
+ {4:~ }|
+ {5:xtest-foo }|
+ :edit xtest-foo |
+ {3:-- TERMINAL --} |
+ ]]}
+
+ helpers.write_file(path, expected_addition)
+
+ feed_data('\027[I')
+
+ screen:expect{grid=[[
+ {1:l}ine 1 |
+ line 2 |
+ line 3 |
+ line 4 |
+ {5:xtest-foo }|
+ "xtest-foo" 4L, 28C |
+ {3:-- TERMINAL --} |
+ ]]}
+ end)
+end)
diff --git a/test/functional/cmdline/ctrl_r_spec.lua b/test/functional/cmdline/ctrl_r_spec.lua
index d2dad23e98..a0f3955282 100644
--- a/test/functional/cmdline/ctrl_r_spec.lua
+++ b/test/functional/cmdline/ctrl_r_spec.lua
@@ -15,7 +15,7 @@ describe('cmdline CTRL-R', function()
-- <CR> inserted between lines, NOT after the final line.
eq('line1abc\rline2somemoretext', funcs.getcmdline())
- -- Yank 2 lines characterwise, then paste to cmdline.
+ -- Yank 2 lines charwise, then paste to cmdline.
feed([[<C-\><C-N>gg05lyvj:<C-R>0]])
-- <CR> inserted between lines, NOT after the final line.
eq('abc\rline2', funcs.getcmdline())
diff --git a/test/functional/cmdline/history_spec.lua b/test/functional/cmdline/history_spec.lua
index 20f9cf06a2..ee2d36f642 100644
--- a/test/functional/cmdline/history_spec.lua
+++ b/test/functional/cmdline/history_spec.lua
@@ -6,7 +6,7 @@ describe('history support code', function()
before_each(clear)
it('correctly clears start of the history', function()
- -- Regression test: check absense of the memory leak when clearing start of
+ -- Regression test: check absence of the memory leak when clearing start of
-- the history using ex_getln.c/clr_history().
eq(1, funcs.histadd(':', 'foo'))
eq(1, funcs.histdel(':'))
@@ -14,7 +14,7 @@ describe('history support code', function()
end)
it('correctly clears end of the history', function()
- -- Regression test: check absense of the memory leak when clearing end of
+ -- 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)
eq(1, funcs.histadd(':', 'foo'))
diff --git a/test/functional/core/fileio_spec.lua b/test/functional/core/fileio_spec.lua
index e6bce85b8a..f4c476560d 100644
--- a/test/functional/core/fileio_spec.lua
+++ b/test/functional/core/fileio_spec.lua
@@ -9,9 +9,12 @@ local nvim_prog = helpers.nvim_prog
local request = helpers.request
local retry = helpers.retry
local rmdir = helpers.rmdir
+local mkdir = helpers.mkdir
local sleep = helpers.sleep
local read_file = helpers.read_file
local trim = helpers.trim
+local currentdir = helpers.funcs.getcwd
+local iswin = helpers.iswin
describe('fileio', function()
before_each(function()
@@ -24,6 +27,7 @@ describe('fileio', function()
os.remove('Xtest_startup_file2')
os.remove('Xtest_теÑÑ‚.md')
rmdir('Xtest_startup_swapdir')
+ rmdir('Xtest_backupdir')
end)
it('fsync() codepaths #8304', function()
@@ -88,6 +92,27 @@ describe('fileio', function()
eq('foo', bar_contents);
end)
+ it('backup with full path #11214', function()
+ clear()
+ mkdir('Xtest_backupdir')
+ command('set backup')
+ command('set backupdir=Xtest_backupdir//')
+ 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',
+ iswin() and '[:/\\]' or '/', '%%') .. '~'
+ local foo_contents = trim(read_file('Xtest_backupdir/'..backup_file_name))
+ local foobar_contents = trim(read_file('Xtest_startup_file1'))
+
+ eq('foobar', foobar_contents);
+ eq('foo', foo_contents);
+ end)
+
it('readfile() on multibyte filename #10586', function()
clear()
local text = {
diff --git a/test/functional/core/job_spec.lua b/test/functional/core/job_spec.lua
index 9c37e55f42..57e6f4fd63 100644
--- a/test/functional/core/job_spec.lua
+++ b/test/functional/core/job_spec.lua
@@ -26,6 +26,7 @@ describe('jobs', function()
before_each(function()
clear()
+
channel = nvim('get_api_info')[1]
nvim('set_var', 'channel', channel)
source([[
@@ -48,6 +49,57 @@ describe('jobs', function()
]])
end)
+ it('must specify env option as a dict', function()
+ command("let g:job_opts.env = v:true")
+ local _, err = pcall(function()
+ if iswin() then
+ nvim('command', "let j = jobstart('set', g:job_opts)")
+ else
+ nvim('command', "let j = jobstart('env', g:job_opts)")
+ end
+ end)
+ ok(string.find(err, "E475: Invalid argument: env") ~= nil)
+ end)
+
+ it('append environment #env', function()
+ nvim('command', "let $VAR = 'abc'")
+ nvim('command', "let g:job_opts.env = {'TOTO': 'hello world'}")
+ if iswin() then
+ nvim('command', [[call jobstart('echo %TOTO% %VAR%', g:job_opts)]])
+ else
+ nvim('command', [[call jobstart('echo $TOTO $VAR', g:job_opts)]])
+ end
+
+ expect_msg_seq({
+ {'notification', 'stdout', {0, {'hello world abc', ''}}},
+ })
+ end)
+
+ it('replace environment #env', function()
+ nvim('command', "let $VAR = 'abc'")
+ nvim('command', "let g:job_opts.env = {'TOTO': 'hello world'}")
+ nvim('command', "let g:job_opts.clear_env = 1")
+
+ -- libuv ensures that certain "required" environment variables are
+ -- preserved if the user doesn't provide them in a custom environment
+ -- https://github.com/libuv/libuv/blob/635e0ce6073c5fbc96040e336b364c061441b54b/src/win/process.c#L672
+ -- https://github.com/libuv/libuv/blob/635e0ce6073c5fbc96040e336b364c061441b54b/src/win/process.c#L48-L60
+ --
+ -- Rather than expecting a completely empty environment, ensure that $VAR
+ -- is *not* in the environment but $TOTO is.
+ if iswin() then
+ nvim('command', [[call jobstart('echo %TOTO% %VAR%', g:job_opts)]])
+ expect_msg_seq({
+ {'notification', 'stdout', {0, {'hello world %VAR%', ''}}}
+ })
+ else
+ nvim('command', [[call jobstart('echo $TOTO $VAR', g:job_opts)]])
+ expect_msg_seq({
+ {'notification', 'stdout', {0, {'hello world', ''}}}
+ })
+ end
+ end)
+
it('uses &shell and &shellcmdflag if passed a string', function()
nvim('command', "let $VAR = 'abc'")
if iswin() then
@@ -133,11 +185,10 @@ describe('jobs', function()
return eval([[jobstart('')]])
end
local executable_jobid = new_job()
- local nonexecutable_jobid = eval("jobstart(['"..(iswin()
- and './test/functional/fixtures'
- or './test/functional/fixtures/non_executable.txt').."'])")
- eq(-1, nonexecutable_jobid)
- -- Should _not_ throw an error.
+
+ local exe = iswin() and './test/functional/fixtures' or './test/functional/fixtures/non_executable.txt'
+ eq("Vim:E475: Invalid value for argument cmd: '"..exe.."' is not executable",
+ pcall_err(eval, "jobstart(['"..exe.."'])"))
eq("", eval("v:errmsg"))
-- Non-executable job should not increment the job ids. #5465
eq(executable_jobid + 1, new_job())
@@ -202,8 +253,7 @@ describe('jobs', function()
if helpers.isCI('travis') and os.getenv('CC') == 'gcc-4.9'
and helpers.is_os('mac') then
-- XXX: Hangs Travis macOS since e9061117a5b8f195c3f26a5cb94e18ddd7752d86.
- pending("[Hangs on Travis macOS. #5002]", function() end)
- return
+ pending("[Hangs on Travis macOS. #5002]")
end
nvim('command', "let j = jobstart(['cat', '-'], g:job_opts)")
@@ -256,16 +306,16 @@ describe('jobs', function()
end))
end)
- it('disallows jobsend/stop on a non-existent job', function()
+ it('disallows jobsend on a non-existent job', function()
eq(false, pcall(eval, "jobsend(-1, 'lol')"))
- eq(false, pcall(eval, "jobstop(-1)"))
+ eq(0, eval('jobstop(-1)'))
end)
- it('disallows jobstop twice on the same job', function()
+ it('jobstop twice on the stopped or exited job return 0', function()
nvim('command', "let j = jobstart(['cat', '-'], g:job_opts)")
neq(0, eval('j'))
- eq(true, pcall(eval, "jobstop(j)"))
- eq(false, pcall(eval, "jobstop(j)"))
+ eq(1, eval("jobstop(j)"))
+ eq(0, eval("jobstop(j)"))
end)
it('will not leak memory if we leave a job running', function()
@@ -869,6 +919,13 @@ describe('jobs', function()
end)
end)
+ it('jobstop on same id before stopped', function()
+ nvim('command', 'let j = jobstart(["cat", "-"], g:job_opts)')
+ neq(0, eval('j'))
+
+ eq({1, 0}, eval('[jobstop(j), jobstop(j)]'))
+ end)
+
describe('running tty-test program', function()
if helpers.pending_win32(pending) then return end
local function next_chunk()
@@ -963,9 +1020,6 @@ describe("pty process teardown", function()
|
]])
end)
- after_each(function()
- screen:detach()
- end)
it("does not prevent/delay exit. #4798 #4900", function()
if helpers.pending_win32(pending) then return end
diff --git a/test/functional/core/main_spec.lua b/test/functional/core/main_spec.lua
index b793e531c9..37a9f0b836 100644
--- a/test/functional/core/main_spec.lua
+++ b/test/functional/core/main_spec.lua
@@ -67,7 +67,7 @@ describe('Command-line option', function()
|
|
]], {
- [1] = {foreground = 4210943},
+ [1] = {foreground = tonumber('0x4040ff'), fg_indexed=true},
[2] = {bold = true, reverse = true}
})
feed('i:cq<CR>')
diff --git a/test/functional/core/startup_spec.lua b/test/functional/core/startup_spec.lua
index cc10d36a10..9b0668f9e6 100644
--- a/test/functional/core/startup_spec.lua
+++ b/test/functional/core/startup_spec.lua
@@ -3,6 +3,7 @@ local Screen = require('test.functional.ui.screen')
local clear = helpers.clear
local command = helpers.command
+local ok = helpers.ok
local eq = helpers.eq
local matches = helpers.matches
local eval = helpers.eval
@@ -17,6 +18,7 @@ local rmdir = helpers.rmdir
local sleep = helpers.sleep
local iswin = helpers.iswin
local write_file = helpers.write_file
+local meths = helpers.meths
describe('startup', function()
before_each(function()
@@ -277,6 +279,32 @@ describe('startup', function()
[4] = {bold = true, foreground = Screen.colors.Blue1},
}})
end)
+
+ it('fixed hang issue with --headless (#11386)', function()
+ local expected = ''
+ local period = 100
+ for i = 1, period - 1 do
+ expected = expected .. i .. '\r\n'
+ end
+ expected = expected .. period
+ eq(
+ expected,
+ -- FIXME(codehex): We should really set a timeout for the system function.
+ -- If this test fails, there will be a waiting input state.
+ funcs.system({nvim_prog, '-u', 'NONE', '-c',
+ 'for i in range(1, 100) | echo i | endfor | quit',
+ '--headless'
+ })
+ )
+ end)
+
+ it("get command line arguments from v:argv", function()
+ local out = funcs.system({ nvim_prog, '-u', 'NONE', '-i', 'NONE', '--headless',
+ '--cmd', nvim_set,
+ '-c', [[echo v:argv[-1:] len(v:argv) > 1]],
+ '+q' })
+ eq('[\'+q\'] 1', out)
+ end)
end)
describe('sysinit', function()
@@ -330,4 +358,36 @@ 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)
+
+ it('fixed hang issue with -D (#12647)', function()
+ local screen
+ screen = Screen.new(60, 6)
+ screen:attach()
+ command([[let g:id = termopen('"]]..nvim_prog..
+ [[" -u NONE -i NONE --cmd "set noruler" -D')]])
+ screen:expect([[
+ ^ |
+ Entering Debug mode. Type "cont" to continue. |
+ cmd: augroup nvim_terminal |
+ > |
+ <" -u NONE -i NONE --cmd "set noruler" -D 1,0-1 All|
+ |
+ ]])
+ command([[call chansend(g:id, "cont\n")]])
+ screen:expect([[
+ ^ |
+ ~ |
+ [No Name] |
+ |
+ <" -u NONE -i NONE --cmd "set noruler" -D 1,0-1 All|
+ |
+ ]])
+ end)
+end)
+
+describe('clean', function()
+ clear()
+ ok(string.match(meths.get_option('runtimepath'), funcs.stdpath('config')) ~= nil)
+ clear('--clean')
+ ok(string.match(meths.get_option('runtimepath'), funcs.stdpath('config')) == nil)
end)
diff --git a/test/functional/eval/api_functions_spec.lua b/test/functional/eval/api_functions_spec.lua
index 4fbd08f102..ccd97fc8c7 100644
--- a/test/functional/eval/api_functions_spec.lua
+++ b/test/functional/eval/api_functions_spec.lua
@@ -44,7 +44,7 @@ describe('eval-API', function()
eq('Vim(call):E5555: API call: Wrong type for argument 1, expecting Buffer', err)
err = exc_exec('call nvim_buf_line_count(17)')
- eq('Vim(call):E5555: API call: Invalid buffer id', err)
+ eq('Vim(call):E5555: API call: Invalid buffer id: 17', err)
end)
@@ -144,7 +144,6 @@ describe('eval-API', function()
{5:~ }|
|
]])
- screen:detach()
end)
it('cannot be called from sandbox', function()
diff --git a/test/functional/eval/buf_functions_spec.lua b/test/functional/eval/buf_functions_spec.lua
index 37f4c89bfd..06841a4521 100644
--- a/test/functional/eval/buf_functions_spec.lua
+++ b/test/functional/eval/buf_functions_spec.lua
@@ -31,10 +31,12 @@ for _, func in ipairs({'bufname(%s)', 'bufnr(%s)', 'bufwinnr(%s)',
it('errors out when receives v:true/v:false/v:null', function()
-- Not compatible with Vim: in Vim it always results in buffer not found
-- without any error messages.
- for _, var in ipairs({'v:true', 'v:false', 'v:null'}) do
- eq('Vim(call):E5300: Expected a Number or a String',
+ for _, var in ipairs({'v:true', 'v:false'}) do
+ eq('Vim(call):E5299: Expected a Number or a String, Boolean found',
exc_exec('call ' .. func:format(var)))
end
+ eq('Vim(call):E5300: Expected a Number or a String',
+ exc_exec('call ' .. func:format('v:null')))
end)
it('errors out when receives invalid argument', function()
eq('Vim(call):E745: Expected a Number or a String, List found',
diff --git a/test/functional/eval/ctx_functions_spec.lua b/test/functional/eval/ctx_functions_spec.lua
index c81dad9645..f23adbc556 100644
--- a/test/functional/eval/ctx_functions_spec.lua
+++ b/test/functional/eval/ctx_functions_spec.lua
@@ -6,7 +6,7 @@ local command = helpers.command
local eq = helpers.eq
local eval = helpers.eval
local feed = helpers.feed
-local map = helpers.map
+local map = helpers.tbl_map
local nvim = helpers.nvim
local parse_context = helpers.parse_context
local redir_exec = helpers.redir_exec
diff --git a/test/functional/eval/environ_spec.lua b/test/functional/eval/environ_spec.lua
index 4c2adcf1bf..54d2dc960b 100644
--- a/test/functional/eval/environ_spec.lua
+++ b/test/functional/eval/environ_spec.lua
@@ -10,6 +10,7 @@ describe('environment variables', function()
eq("", environ()['EMPTY_VAR'])
eq(nil, environ()['DOES_NOT_EXIST'])
end)
+
it('exists() handles empty env variable', function()
clear({env={EMPTY_VAR=""}})
eq(1, exists('$EMPTY_VAR'))
diff --git a/test/functional/eval/fnamemodify_spec.lua b/test/functional/eval/fnamemodify_spec.lua
index fe6b50a544..d54a6db417 100644
--- a/test/functional/eval/fnamemodify_spec.lua
+++ b/test/functional/eval/fnamemodify_spec.lua
@@ -3,8 +3,14 @@ local clear = helpers.clear
local eq = helpers.eq
local iswin = helpers.iswin
local fnamemodify = helpers.funcs.fnamemodify
+local getcwd = helpers.funcs.getcwd
local command = helpers.command
local write_file = helpers.write_file
+local alter_slashes = helpers.alter_slashes
+
+local function eq_slashconvert(expected, got)
+ eq(alter_slashes(expected), alter_slashes(got))
+end
describe('fnamemodify()', function()
setup(function()
@@ -17,7 +23,7 @@ describe('fnamemodify()', function()
os.remove('Xtest-fnamemodify.txt')
end)
- it('works', function()
+ it('handles the root path', function()
local root = helpers.pathroot()
eq(root, fnamemodify([[/]], ':p:h'))
eq(root, fnamemodify([[/]], ':p'))
@@ -36,4 +42,115 @@ describe('fnamemodify()', function()
it(':8 works', function()
eq('Xtest-fnamemodify.txt', fnamemodify([[Xtest-fnamemodify.txt]], ':8'))
end)
+
+ it('handles examples from ":help filename-modifiers"', function()
+ local filename = "src/version.c"
+ local cwd = getcwd()
+
+ eq_slashconvert(cwd .. '/src/version.c', fnamemodify(filename, ':p'))
+
+ eq_slashconvert('src/version.c', fnamemodify(filename, ':p:.'))
+ eq_slashconvert(cwd .. '/src', fnamemodify(filename, ':p:h'))
+ eq_slashconvert(cwd .. '', fnamemodify(filename, ':p:h:h'))
+ eq('version.c', fnamemodify(filename, ':p:t'))
+ eq_slashconvert(cwd .. '/src/version', fnamemodify(filename, ':p:r'))
+
+ eq_slashconvert(cwd .. '/src/main.c', fnamemodify(filename, ':s?version?main?:p'))
+
+ local converted_cwd = cwd:gsub('/', '\\')
+ eq(converted_cwd .. '\\src\\version.c', fnamemodify(filename, ':p:gs?/?\\\\?'))
+
+ eq('src', fnamemodify(filename, ':h'))
+ eq('version.c', fnamemodify(filename, ':t'))
+ eq_slashconvert('src/version', fnamemodify(filename, ':r'))
+ eq('version', fnamemodify(filename, ':t:r'))
+ eq('c', fnamemodify(filename, ':e'))
+
+ eq_slashconvert('src/main.c', fnamemodify(filename, ':s?version?main?'))
+ end)
+
+ it('handles advanced examples from ":help filename-modifiers"', function()
+ local filename = "src/version.c.gz"
+
+ eq('gz', fnamemodify(filename, ':e'))
+ eq('c.gz', fnamemodify(filename, ':e:e'))
+ eq('c.gz', fnamemodify(filename, ':e:e:e'))
+
+ eq('c', fnamemodify(filename, ':e:e:r'))
+
+ eq_slashconvert('src/version.c', fnamemodify(filename, ':r'))
+ eq('c', fnamemodify(filename, ':r:e'))
+
+ eq_slashconvert('src/version', fnamemodify(filename, ':r:r'))
+ eq_slashconvert('src/version', fnamemodify(filename, ':r:r:r'))
+ end)
+
+ it('handles :h', function()
+ eq('.', fnamemodify('hello.txt', ':h'))
+
+ eq_slashconvert('path/to', fnamemodify('path/to/hello.txt', ':h'))
+ end)
+
+ it('handles :t', function()
+ eq('hello.txt', fnamemodify('hello.txt', ':t'))
+ eq_slashconvert('hello.txt', fnamemodify('path/to/hello.txt', ':t'))
+ end)
+
+ it('handles :r', function()
+ eq('hello', fnamemodify('hello.txt', ':r'))
+ eq_slashconvert('path/to/hello', fnamemodify('path/to/hello.txt', ':r'))
+ end)
+
+ it('handles :e', function()
+ eq('txt', fnamemodify('hello.txt', ':e'))
+ eq_slashconvert('txt', fnamemodify('path/to/hello.txt', ':e'))
+ end)
+
+ it('handles regex replacements', function()
+ eq('content-there-here.txt', fnamemodify('content-here-here.txt', ':s/here/there/'))
+ eq('content-there-there.txt', fnamemodify('content-here-here.txt', ':gs/here/there/'))
+ end)
+
+ it('handles shell escape', function()
+ local expected
+
+ if iswin() then
+ -- we expand with double-quotes on Windows
+ expected = [["hello there! quote ' newline]] .. '\n' .. [["]]
+ else
+ expected = [['hello there! quote '\'' newline]] .. '\n' .. [[']]
+ end
+
+ eq(expected, fnamemodify("hello there! quote ' newline\n", ':S'))
+ end)
+
+ it('can combine :e and :r', function()
+ -- simple, single extension filename
+ eq('c', fnamemodify('a.c', ':e'))
+ eq('c', fnamemodify('a.c', ':e:e'))
+ eq('c', fnamemodify('a.c', ':e:e:r'))
+ eq('c', fnamemodify('a.c', ':e:e:r:r'))
+
+ -- multi extension filename
+ eq('rb', fnamemodify('a.spec.rb', ':e:r'))
+ eq('rb', fnamemodify('a.spec.rb', ':e:r:r'))
+
+ eq('spec', fnamemodify('a.spec.rb', ':e:e:r'))
+ eq('spec', fnamemodify('a.spec.rb', ':e:e:r:r'))
+
+ eq('spec', fnamemodify('a.b.spec.rb', ':e:e:r'))
+ eq('b.spec', fnamemodify('a.b.spec.rb', ':e:e:e:r'))
+ eq('b', fnamemodify('a.b.spec.rb', ':e:e:e:r:r'))
+
+ eq('spec', fnamemodify('a.b.spec.rb', ':r:e'))
+ eq('b', fnamemodify('a.b.spec.rb', ':r:r:e'))
+
+ -- extraneous :e expansions
+ eq('c', fnamemodify('a.b.c.d.e', ':r:r:e'))
+ eq('b.c', fnamemodify('a.b.c.d.e', ':r:r:e:e'))
+
+ -- :e never includes the whole filename, so "a.b":e:e:e --> "b"
+ eq('b.c', fnamemodify('a.b.c.d.e', ':r:r:e:e:e'))
+ eq('b.c', fnamemodify('a.b.c.d.e', ':r:r:e:e:e:e'))
+ end)
end)
diff --git a/test/functional/eval/input_spec.lua b/test/functional/eval/input_spec.lua
index e774b939f7..14c02f9eb2 100644
--- a/test/functional/eval/input_spec.lua
+++ b/test/functional/eval/input_spec.lua
@@ -462,7 +462,7 @@ describe('confirm()', function()
-- With shortmess-=F
command('set shortmess-=F')
feed(':edit foo<cr>')
- check_and_clear('"foo" [New File] |\n')
+ check_and_clear('"foo" [New] |\n')
-- With shortmess+=F
command('set shortmess+=F')
diff --git a/test/functional/eval/let_spec.lua b/test/functional/eval/let_spec.lua
index 63e18f943f..5bc703b567 100644
--- a/test/functional/eval/let_spec.lua
+++ b/test/functional/eval/let_spec.lua
@@ -59,10 +59,6 @@ describe(':let', function()
end)
it("multibyte env var to child process #8398 #9267", function()
- if (not helpers.iswin()) and helpers.isCI() then
- -- Fails on non-Windows CI. Buffering/timing issue?
- pending('fails on unix CI', function() end)
- end
local cmd_get_child_env = "let g:env_from_child = system(['"..nvim_dir.."/printenv-test', 'NVIM_TEST'])"
command("let $NVIM_TEST = 'AìaB'")
command(cmd_get_child_env)
@@ -79,4 +75,19 @@ describe(':let', function()
command(cmd_get_child_env)
eq(eval('$NVIM_TEST'), eval('g:env_from_child'))
end)
+
+ it("release of list assigned to l: variable does not trigger assertion #12387, #12430", function()
+ source([[
+ func! s:f()
+ let l:x = [1]
+ let g:x = l:
+ endfunc
+ for _ in range(2)
+ call s:f()
+ endfor
+ call garbagecollect()
+ call feedkeys('i', 't')
+ ]])
+ eq(1, eval('1'))
+ end)
end)
diff --git a/test/functional/eval/map_functions_spec.lua b/test/functional/eval/map_functions_spec.lua
index 2747a94570..275c72d212 100644
--- a/test/functional/eval/map_functions_spec.lua
+++ b/test/functional/eval/map_functions_spec.lua
@@ -13,6 +13,7 @@ describe('maparg()', function()
local foo_bar_map_table = {
lhs='foo',
+ script=0,
silent=0,
rhs='bar',
expr=0,
@@ -147,6 +148,7 @@ describe('maparg()', function()
mode = 'n',
noremap = 1,
nowait = 0,
+ script=0,
sid = 0,
silent = 0,
lnum = 0,
diff --git a/test/functional/eval/null_spec.lua b/test/functional/eval/null_spec.lua
index afe999e1fa..db0a706319 100644
--- a/test/functional/eval/null_spec.lua
+++ b/test/functional/eval/null_spec.lua
@@ -47,10 +47,8 @@ describe('NULL', function()
-- Subjectable behaviour
- -- FIXME Should return 1
- null_expr_test('is equal to empty list', 'L == []', 0, 0)
- -- FIXME Should return 1
- null_expr_test('is equal to empty list (reverse order)', '[] == L', 0, 0)
+ 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_expr_test('can be indexed with error message for empty list', 'L[0]',
diff --git a/test/functional/eval/sort_spec.lua b/test/functional/eval/sort_spec.lua
index 82557575ce..e1cc2c2924 100644
--- a/test/functional/eval/sort_spec.lua
+++ b/test/functional/eval/sort_spec.lua
@@ -14,7 +14,7 @@ before_each(clear)
describe('sort()', function()
it('errors out when sorting special values', function()
- eq('Vim(call):E907: Using a special value as a Float',
+ eq('Vim(call):E362: Using a boolean value as a Float',
exc_exec('call sort([v:true, v:false], "f")'))
end)
@@ -30,6 +30,7 @@ describe('sort()', function()
errors[err] = true
end
eq({
+ ['E362: Using a boolean value as a Float']=true,
['E891: Using a Funcref as a Float']=true,
['E892: Using a String as a Float']=true,
['E893: Using a List as a Float']=true,
diff --git a/test/functional/eval/system_spec.lua b/test/functional/eval/system_spec.lua
index 0a478fd05c..8b18eff451 100644
--- a/test/functional/eval/system_spec.lua
+++ b/test/functional/eval/system_spec.lua
@@ -8,6 +8,7 @@ local command = helpers.command
local exc_exec = helpers.exc_exec
local iswin = helpers.iswin
local os_kill = helpers.os_kill
+local pcall_err = helpers.pcall_err
local Screen = require('test.functional.ui.screen')
@@ -32,8 +33,9 @@ describe('system()', function()
return nvim_dir..'/printargs-test' .. (iswin() and '.exe' or '')
end
- it('sets v:shell_error if cmd[0] is not executable', function()
- call('system', { 'this-should-not-exist' })
+ it('throws error if cmd[0] is not executable', function()
+ eq("Vim:E475: Invalid value for argument cmd: 'this-should-not-exist' is not executable",
+ pcall_err(call, 'system', { 'this-should-not-exist' }))
eq(-1, eval('v:shell_error'))
end)
@@ -48,7 +50,8 @@ describe('system()', function()
eq(0, eval('v:shell_error'))
-- Provoke a non-zero v:shell_error.
- call('system', { 'this-should-not-exist' })
+ eq("Vim:E475: Invalid value for argument cmd: 'this-should-not-exist' is not executable",
+ pcall_err(call, 'system', { 'this-should-not-exist' }))
local old_val = eval('v:shell_error')
eq(-1, old_val)
@@ -84,7 +87,7 @@ describe('system()', function()
it('does NOT run in shell', function()
if iswin() then
- eq("%PATH%\n", eval("system(['powershell', '-NoProfile', '-NoLogo', '-ExecutionPolicy', 'RemoteSigned', '-Command', 'echo', '%PATH%'])"))
+ eq("%PATH%\n", eval("system(['powershell', '-NoProfile', '-NoLogo', '-ExecutionPolicy', 'RemoteSigned', '-Command', 'Write-Output', '%PATH%'])"))
else
eq("* $PATH %PATH%\n", eval("system(['echo', '*', '$PATH', '%PATH%'])"))
end
@@ -121,10 +124,6 @@ describe('system()', function()
screen:attach()
end)
- after_each(function()
- screen:detach()
- end)
-
if iswin() then
local function test_more()
eq('root = true', eval([[get(split(system('"more" ".editorconfig"'), "\n"), 0, '')]]))
@@ -133,7 +132,7 @@ describe('system()', function()
eval([[system('"ping" "-n" "1" "127.0.0.1"')]])
eq(0, eval('v:shell_error'))
eq('"a b"\n', eval([[system('cmd /s/c "cmd /s/c "cmd /s/c "echo "a b""""')]]))
- eq('"a b"\n', eval([[system('powershell -NoProfile -NoLogo -ExecutionPolicy RemoteSigned -Command echo ''\^"a b\^"''')]]))
+ eq('"a b"\n', eval([[system('powershell -NoProfile -NoLogo -ExecutionPolicy RemoteSigned -Command Write-Output ''\^"a b\^"''')]]))
end
it('with shell=cmd.exe', function()
@@ -169,9 +168,9 @@ describe('system()', function()
it('works with powershell', function()
helpers.set_shell_powershell()
- eq('a\nb\n', eval([[system('echo a b')]]))
+ eq('a\nb\n', eval([[system('Write-Output a b')]]))
eq('C:\\\n', eval([[system('cd c:\; (Get-Location).Path')]]))
- eq('a b\n', eval([[system('echo "a b"')]]))
+ eq('a b\n', eval([[system('Write-Output "a b"')]]))
end)
end
diff --git a/test/functional/eval/timer_spec.lua b/test/functional/eval/timer_spec.lua
index 2ccb9cfbac..ef7df69fdb 100644
--- a/test/functional/eval/timer_spec.lua
+++ b/test/functional/eval/timer_spec.lua
@@ -111,7 +111,13 @@ describe('timers', function()
curbufmeths.set_lines(0, -1, true, {"ITEM 1", "ITEM 2"})
source([[
+ let g:cont = 0
func! AddItem(timer)
+ if !g:cont
+ return
+ endif
+ call timer_stop(a:timer)
+
call nvim_buf_set_lines(0, 2, 2, v:true, ['ITEM 3'])
" Meant to test for what Vim tests in Test_peek_and_get_char.
@@ -121,7 +127,7 @@ describe('timers', function()
endfunc
]])
nvim_async("command", "let g:c2 = getchar()")
- nvim_async("command", "call timer_start("..load_adjust(100)..", 'AddItem')")
+ nvim_async("command", "call timer_start("..load_adjust(100)..", 'AddItem', {'repeat': -1})")
screen:expect([[
ITEM 1 |
@@ -131,6 +137,7 @@ describe('timers', function()
{1:~ }|
^ |
]])
+ nvim_async("command", "let g:cont = 1")
screen:expect([[
ITEM 1 |
@@ -222,12 +229,17 @@ describe('timers', function()
source([[
let g:val = 0
func! MyHandler(timer)
+ while !g:val
+ return
+ endwhile
+ call timer_stop(a:timer)
+
echo "evil"
redraw
- let g:val = 1
+ let g:val = 2
endfunc
]])
- command("call timer_start(100, 'MyHandler', {'repeat': 1})")
+ command("call timer_start(100, 'MyHandler', {'repeat': -1})")
feed(":good")
screen:expect([[
|
@@ -237,6 +249,7 @@ describe('timers', function()
{0:~ }|
:good^ |
]])
+ command('let g:val = 1')
screen:expect{grid=[[
|
@@ -247,6 +260,6 @@ describe('timers', function()
:good^ |
]], intermediate=true, timeout=load_adjust(200)}
- eq(1, eval('g:val'))
+ eq(2, eval('g:val'))
end)
end)
diff --git a/test/functional/eval/uniq_spec.lua b/test/functional/eval/uniq_spec.lua
index 0e7a013e32..5cdba0a0f6 100644
--- a/test/functional/eval/uniq_spec.lua
+++ b/test/functional/eval/uniq_spec.lua
@@ -11,7 +11,7 @@ before_each(clear)
describe('uniq()', function()
it('errors out when processing special values', function()
- eq('Vim(call):E907: Using a special value as a Float',
+ eq('Vim(call):E362: Using a boolean value as a Float',
exc_exec('call uniq([v:true, v:false], "f")'))
end)
diff --git a/test/functional/eval/wait_spec.lua b/test/functional/eval/wait_spec.lua
index ad7d1574cb..ee95e02a7f 100644
--- a/test/functional/eval/wait_spec.lua
+++ b/test/functional/eval/wait_spec.lua
@@ -58,8 +58,11 @@ describe('wait()', function()
endfunction
]])
- nvim('set_var', 'counter', 0)
- eq(-1, call('wait', 20, 'Count() >= 5', 99999))
+ -- XXX: flaky (#11137)
+ helpers.retry(nil, nil, function()
+ nvim('set_var', 'counter', 0)
+ eq(-1, call('wait', 20, 'Count() >= 5', 99999))
+ end)
nvim('set_var', 'counter', 0)
eq(0, call('wait', 10000, 'Count() >= 5', 5))
diff --git a/test/functional/ex_cmds/dict_notifications_spec.lua b/test/functional/ex_cmds/dict_notifications_spec.lua
index 48e7e05e4c..5c67431221 100644
--- a/test/functional/ex_cmds/dict_notifications_spec.lua
+++ b/test/functional/ex_cmds/dict_notifications_spec.lua
@@ -357,4 +357,18 @@ describe('VimL dictionary notifications', function()
eq(2, eval('1+1')) -- Still alive?
end)
+ it('does not cause use-after-free when unletting from callback', function()
+ source([[
+ let g:called = 0
+ function W(...) abort
+ unlet g:d
+ let g:called = 1
+ endfunction
+ let g:d = {}
+ call dictwatcheradd(g:d, '*', function('W'))
+ let g:d.foo = 123
+ ]])
+ eq(1, eval('g:called'))
+ end)
+
end)
diff --git a/test/functional/ex_cmds/drop_spec.lua b/test/functional/ex_cmds/drop_spec.lua
index d6da0d8e88..ef53fe75e3 100644
--- a/test/functional/ex_cmds/drop_spec.lua
+++ b/test/functional/ex_cmds/drop_spec.lua
@@ -19,10 +19,6 @@ describe(":drop", function()
command("set laststatus=2 shortmess-=F")
end)
- after_each(function()
- screen:detach()
- end)
-
it("works like :e when called with only one window open", function()
feed_command("drop tmp1.vim")
screen:expect([[
@@ -35,7 +31,7 @@ describe(":drop", function()
{0:~ }|
{0:~ }|
{1:tmp1.vim }|
- "tmp1.vim" [New File] |
+ "tmp1.vim" [New] |
]])
end)
@@ -74,7 +70,7 @@ describe(":drop", function()
{0:~ }{2:│}{0:~ }|
{0:~ }{2:│}{0:~ }|
{2:tmp2 [+] tmp1 }|
- "tmp3" [New File] |
+ "tmp3" [New] |
]])
end)
diff --git a/test/functional/ex_cmds/echo_spec.lua b/test/functional/ex_cmds/echo_spec.lua
index 10c7230896..404dc39ad2 100644
--- a/test/functional/ex_cmds/echo_spec.lua
+++ b/test/functional/ex_cmds/echo_spec.lua
@@ -11,31 +11,57 @@ local dedent = helpers.dedent
local command = helpers.command
local exc_exec = helpers.exc_exec
local redir_exec = helpers.redir_exec
+local matches = helpers.matches
+
+describe(':echo :echon :echomsg :echoerr', function()
+ local fn_tbl = {'String', 'StringN', 'StringMsg', 'StringErr'}
+ local function assert_same_echo_dump(expected, input, use_eval)
+ for _,v in pairs(fn_tbl) do
+ eq(expected, use_eval and eval(v..'('..input..')') or funcs[v](input))
+ end
+ end
+ local function assert_matches_echo_dump(expected, input, use_eval)
+ for _,v in pairs(fn_tbl) do
+ matches(expected, use_eval and eval(v..'('..input..')') or funcs[v](input))
+ end
+ end
-describe(':echo', function()
before_each(function()
clear()
source([[
function String(s)
return execute('echo a:s')[1:]
endfunction
+ function StringMsg(s)
+ return execute('echomsg a:s')[1:]
+ endfunction
+ function StringN(s)
+ return execute('echon a:s')
+ endfunction
+ function StringErr(s)
+ try
+ execute 'echoerr a:s'
+ catch
+ return substitute(v:exception, '^Vim(echoerr):', '', '')
+ endtry
+ endfunction
]])
end)
describe('used to represent floating-point values', function()
it('dumps NaN values', function()
- eq('str2float(\'nan\')', eval('String(str2float(\'nan\'))'))
+ assert_same_echo_dump("str2float('nan')", "str2float('nan')", true)
end)
it('dumps infinite values', function()
- eq('str2float(\'inf\')', eval('String(str2float(\'inf\'))'))
- eq('-str2float(\'inf\')', eval('String(str2float(\'-inf\'))'))
+ assert_same_echo_dump("str2float('inf')", "str2float('inf')", true)
+ assert_same_echo_dump("-str2float('inf')", "str2float('-inf')", true)
end)
it('dumps regular values', function()
- eq('1.5', funcs.String(1.5))
- eq('1.56e-20', funcs.String(1.56000e-020))
- eq('0.0', eval('String(0.0)'))
+ assert_same_echo_dump('1.5', 1.5)
+ assert_same_echo_dump('1.56e-20', 1.56000e-020)
+ assert_same_echo_dump('0.0', '0.0', true)
end)
it('dumps special v: values', function()
@@ -45,69 +71,81 @@ describe(':echo', function()
eq('v:true', funcs.String(true))
eq('v:false', funcs.String(false))
eq('v:null', funcs.String(NIL))
+ eq('v:true', eval('StringMsg(v:true)'))
+ eq('v:false', eval('StringMsg(v:false)'))
+ eq('v:null', eval('StringMsg(v:null)'))
+ eq('v:true', funcs.StringMsg(true))
+ eq('v:false', funcs.StringMsg(false))
+ eq('v:null', funcs.StringMsg(NIL))
+ eq('v:true', eval('StringErr(v:true)'))
+ eq('v:false', eval('StringErr(v:false)'))
+ eq('v:null', eval('StringErr(v:null)'))
+ eq('v:true', funcs.StringErr(true))
+ eq('v:false', funcs.StringErr(false))
+ eq('v:null', funcs.StringErr(NIL))
end)
it('dumps values with at most six digits after the decimal point',
function()
- eq('1.234568e-20', funcs.String(1.23456789123456789123456789e-020))
- eq('1.234568', funcs.String(1.23456789123456789123456789))
+ assert_same_echo_dump('1.234568e-20', 1.23456789123456789123456789e-020)
+ assert_same_echo_dump('1.234568', 1.23456789123456789123456789)
end)
it('dumps values with at most seven digits before the decimal point',
function()
- eq('1234567.891235', funcs.String(1234567.89123456789123456789))
- eq('1.234568e7', funcs.String(12345678.9123456789123456789))
+ assert_same_echo_dump('1234567.891235', 1234567.89123456789123456789)
+ assert_same_echo_dump('1.234568e7', 12345678.9123456789123456789)
end)
it('dumps negative values', function()
- eq('-1.5', funcs.String(-1.5))
- eq('-1.56e-20', funcs.String(-1.56000e-020))
- eq('-1.234568e-20', funcs.String(-1.23456789123456789123456789e-020))
- eq('-1.234568', funcs.String(-1.23456789123456789123456789))
- eq('-1234567.891235', funcs.String(-1234567.89123456789123456789))
- eq('-1.234568e7', funcs.String(-12345678.9123456789123456789))
+ assert_same_echo_dump('-1.5', -1.5)
+ assert_same_echo_dump('-1.56e-20', -1.56000e-020)
+ assert_same_echo_dump('-1.234568e-20', -1.23456789123456789123456789e-020)
+ assert_same_echo_dump('-1.234568', -1.23456789123456789123456789)
+ assert_same_echo_dump('-1234567.891235', -1234567.89123456789123456789)
+ assert_same_echo_dump('-1.234568e7', -12345678.9123456789123456789)
end)
end)
describe('used to represent numbers', function()
it('dumps regular values', function()
- eq('0', funcs.String(0))
- eq('-1', funcs.String(-1))
- eq('1', funcs.String(1))
+ assert_same_echo_dump('0', 0)
+ assert_same_echo_dump('-1', -1)
+ assert_same_echo_dump('1', 1)
end)
it('dumps large values', function()
- eq('2147483647', funcs.String(2^31-1))
- eq('-2147483648', funcs.String(-2^31))
+ assert_same_echo_dump('2147483647', 2^31-1)
+ assert_same_echo_dump('-2147483648', -2^31)
end)
end)
describe('used to represent strings', function()
it('dumps regular strings', function()
- eq('test', funcs.String('test'))
+ assert_same_echo_dump('test', 'test')
end)
it('dumps empty strings', function()
- eq('', funcs.String(''))
+ assert_same_echo_dump('', '')
end)
- it('dumps strings with \' inside', function()
- eq('\'\'\'', funcs.String('\'\'\''))
- eq('a\'b\'\'', funcs.String('a\'b\'\''))
- eq('\'b\'\'d', funcs.String('\'b\'\'d'))
- eq('a\'b\'c\'d', funcs.String('a\'b\'c\'d'))
+ it("dumps strings with ' inside", function()
+ assert_same_echo_dump("'''", "'''")
+ assert_same_echo_dump("a'b''", "a'b''")
+ assert_same_echo_dump("'b''d", "'b''d")
+ assert_same_echo_dump("a'b'c'd", "a'b'c'd")
end)
it('dumps NULL strings', function()
- eq('', eval('String($XXX_UNEXISTENT_VAR_XXX)'))
+ assert_same_echo_dump('', '$XXX_UNEXISTENT_VAR_XXX', true)
end)
it('dumps NULL lists', function()
- eq('[]', eval('String(v:_null_list)'))
+ assert_same_echo_dump('[]', 'v:_null_list', true)
end)
it('dumps NULL dictionaries', function()
- eq('{}', eval('String(v:_null_dict)'))
+ assert_same_echo_dump('{}', 'v:_null_dict', true)
end)
end)
@@ -129,15 +167,27 @@ describe(':echo', function()
it('dumps references to built-in functions', function()
eq('function', eval('String(function("function"))'))
+ eq("function('function')", eval('StringMsg(function("function"))'))
+ eq("function('function')", eval('StringErr(function("function"))'))
end)
it('dumps references to user functions', function()
eq('Test1', eval('String(function("Test1"))'))
eq('g:Test3', eval('String(function("g:Test3"))'))
+ eq("function('Test1')", eval("StringMsg(function('Test1'))"))
+ eq("function('g:Test3')", eval("StringMsg(function('g:Test3'))"))
+ eq("function('Test1')", eval("StringErr(function('Test1'))"))
+ eq("function('g:Test3')", eval("StringErr(function('g:Test3'))"))
end)
it('dumps references to script functions', function()
eq('<SNR>2_Test2', eval('String(Test2_f)'))
+ eq("function('<SNR>2_Test2')", eval('StringMsg(Test2_f)'))
+ eq("function('<SNR>2_Test2')", eval('StringErr(Test2_f)'))
+ end)
+
+ it('dump references to lambdas', function()
+ assert_matches_echo_dump("function%('<lambda>%d+'%)", '{-> 1234}', true)
end)
it('dumps partials with self referencing a partial', function()
@@ -156,19 +206,23 @@ describe(':echo', function()
end)
it('dumps automatically created partials', function()
- eq('function(\'<SNR>2_Test2\', {\'f\': function(\'<SNR>2_Test2\')})',
- eval('String({"f": Test2_f}.f)'))
- eq('function(\'<SNR>2_Test2\', [1], {\'f\': function(\'<SNR>2_Test2\', [1])})',
- eval('String({"f": function(Test2_f, [1])}.f)'))
+ assert_same_echo_dump(
+ "function('<SNR>2_Test2', {'f': function('<SNR>2_Test2')})",
+ '{"f": Test2_f}.f',
+ true)
+ assert_same_echo_dump(
+ "function('<SNR>2_Test2', [1], {'f': function('<SNR>2_Test2', [1])})",
+ '{"f": function(Test2_f, [1])}.f',
+ true)
end)
it('dumps manually created partials', function()
- eq('function(\'Test3\', [1, 2], {})',
- eval('String(function("Test3", [1, 2], {}))'))
- eq('function(\'Test3\', {})',
- eval('String(function("Test3", {}))'))
- eq('function(\'Test3\', [1, 2])',
- eval('String(function("Test3", [1, 2]))'))
+ assert_same_echo_dump("function('Test3', [1, 2], {})",
+ "function('Test3', [1, 2], {})", true)
+ assert_same_echo_dump("function('Test3', [1, 2])",
+ "function('Test3', [1, 2])", true)
+ assert_same_echo_dump("function('Test3', {})",
+ "function('Test3', {})", true)
end)
it('does not crash or halt when dumping partials with reference cycles in self',
@@ -225,15 +279,19 @@ describe(':echo', function()
describe('used to represent lists', function()
it('dumps empty list', function()
- eq('[]', funcs.String({}))
+ assert_same_echo_dump('[]', {})
+ end)
+
+ it('dumps non-empty list', function()
+ assert_same_echo_dump('[1, 2]', {1,2})
end)
it('dumps nested lists', function()
- eq('[[[[[]]]]]', funcs.String({{{{{}}}}}))
+ assert_same_echo_dump('[[[[[]]]]]', {{{{{}}}}})
end)
it('dumps nested non-empty lists', function()
- eq('[1, [[3, [[5], 4]], 2]]', funcs.String({1, {{3, {{5}, 4}}, 2}}))
+ assert_same_echo_dump('[1, [[3, [[5], 4]], 2]]', {1, {{3, {{5}, 4}}, 2}})
end)
it('does not error when dumping recursive lists', function()
@@ -252,18 +310,18 @@ describe(':echo', function()
describe('used to represent dictionaries', function()
it('dumps empty dictionary', function()
- eq('{}', eval('String({})'))
+ assert_same_echo_dump('{}', '{}', true)
end)
it('dumps list with two same empty dictionaries, also in partials', function()
command('let d = {}')
- eq('[{}, {}]', eval('String([d, d])'))
+ assert_same_echo_dump('[{}, {}]', '[d, d]', true)
eq('[function(\'tr\', {}), {}]', eval('String([function("tr", d), d])'))
eq('[{}, function(\'tr\', {})]', eval('String([d, function("tr", d)])'))
end)
it('dumps non-empty dictionary', function()
- eq('{\'t\'\'est\': 1}', funcs.String({['t\'est']=1}))
+ assert_same_echo_dump("{'t''est': 1}", {["t'est"]=1})
end)
it('does not error when dumping recursive dictionaries', function()
@@ -297,11 +355,20 @@ describe(':echo', function()
eq('<8e>', funcs.String(chr(0x8e)))
eq('<c2>', funcs.String(('«'):sub(1, 1)))
eq('«', funcs.String(('«'):sub(1, 2)))
+
+ eq('<80>', funcs.StringMsg(chr(0x80)))
+ eq('<81>', funcs.StringMsg(chr(0x81)))
+ eq('<8e>', funcs.StringMsg(chr(0x8e)))
+ eq('<c2>', funcs.StringMsg(('«'):sub(1, 1)))
+ eq('«', funcs.StringMsg(('«'):sub(1, 2)))
end)
it('displays ASCII control characters using ^X notation', function()
eq('^C', funcs.String(ctrl('c')))
eq('^A', funcs.String(ctrl('a')))
eq('^F', funcs.String(ctrl('f')))
+ eq('^C', funcs.StringMsg(ctrl('c')))
+ eq('^A', funcs.StringMsg(ctrl('a')))
+ eq('^F', funcs.StringMsg(ctrl('f')))
end)
it('prints CR, NL and tab as-is', function()
eq('\n', funcs.String('\n'))
@@ -311,11 +378,15 @@ describe(':echo', function()
it('prints non-printable UTF-8 in <> notation', function()
-- SINGLE SHIFT TWO, unicode control
eq('<8e>', funcs.String(funcs.nr2char(0x8E)))
+ eq('<8e>', funcs.StringMsg(funcs.nr2char(0x8E)))
-- Surrogate pair: U+1F0A0 PLAYING CARD BACK is represented in UTF-16 as
-- 0xD83C 0xDCA0. This is not valid in UTF-8.
eq('<d83c>', funcs.String(funcs.nr2char(0xD83C)))
eq('<dca0>', funcs.String(funcs.nr2char(0xDCA0)))
eq('<d83c><dca0>', funcs.String(funcs.nr2char(0xD83C) .. funcs.nr2char(0xDCA0)))
+ eq('<d83c>', funcs.StringMsg(funcs.nr2char(0xD83C)))
+ eq('<dca0>', funcs.StringMsg(funcs.nr2char(0xDCA0)))
+ eq('<d83c><dca0>', funcs.StringMsg(funcs.nr2char(0xD83C) .. funcs.nr2char(0xDCA0)))
end)
end)
end)
diff --git a/test/functional/ex_cmds/highlight_spec.lua b/test/functional/ex_cmds/highlight_spec.lua
index 25968b8204..1cd6759a53 100644
--- a/test/functional/ex_cmds/highlight_spec.lua
+++ b/test/functional/ex_cmds/highlight_spec.lua
@@ -13,10 +13,6 @@ describe(':highlight', function()
screen:attach()
end)
- after_each(function()
- screen:detach()
- end)
-
it('invalid color name', function()
eq('Vim(highlight):E421: Color name or number not recognized: ctermfg=#181818',
exc_exec("highlight normal ctermfg=#181818"))
diff --git a/test/functional/ex_cmds/ls_spec.lua b/test/functional/ex_cmds/ls_spec.lua
index f7bacd7386..9853084c47 100644
--- a/test/functional/ex_cmds/ls_spec.lua
+++ b/test/functional/ex_cmds/ls_spec.lua
@@ -31,6 +31,18 @@ describe(':ls', function()
-- Terminal buffer [F]inished.
eq('\n 3 %aF', string.match(ls_output, '\n *3....'))
end)
+
+ retry(nil, 5000, function()
+ local ls_output = eval('execute("ls R")')
+ -- Just the [R]unning terminal buffer.
+ eq('\n 2 #aR ', string.match(ls_output, '^\n *2 ... '))
+ end)
+
+ retry(nil, 5000, function()
+ local ls_output = eval('execute("ls F")')
+ -- Just the [F]inished terminal buffer.
+ eq('\n 3 %aF ', string.match(ls_output, '^\n *3 ... '))
+ end)
end)
end)
diff --git a/test/functional/ex_cmds/mksession_spec.lua b/test/functional/ex_cmds/mksession_spec.lua
index 0f7860740e..949724bb53 100644
--- a/test/functional/ex_cmds/mksession_spec.lua
+++ b/test/functional/ex_cmds/mksession_spec.lua
@@ -6,6 +6,8 @@ local command = helpers.command
local get_pathsep = helpers.get_pathsep
local eq = helpers.eq
local funcs = helpers.funcs
+local matches = helpers.matches
+local pesc = helpers.pesc
local rmdir = helpers.rmdir
local file_prefix = 'Xtest-functional-ex_cmds-mksession_spec'
@@ -24,6 +26,27 @@ describe(':mksession', function()
rmdir(tab_dir)
end)
+ it('restores same :terminal buf in splits', function()
+ -- If the same :terminal is displayed in multiple windows, :mksession
+ -- should restore it as such.
+
+ -- Create two windows showing the same :terminal buffer.
+ command('terminal')
+ command('split')
+ command('terminal')
+ command('split')
+ command('mksession '..session_file)
+
+ -- Create a new test instance of Nvim.
+ command('qall!')
+ clear()
+ -- Restore session.
+ command('source '..session_file)
+
+ eq({3,3,2},
+ {funcs.winbufnr(1), funcs.winbufnr(2), funcs.winbufnr(3)})
+ end)
+
it('restores tab-local working directories', function()
local tmpfile_base = file_prefix .. '-tmpfile'
local cwd_dir = funcs.getcwd()
@@ -48,7 +71,7 @@ describe(':mksession', function()
eq(cwd_dir .. get_pathsep() .. tab_dir, funcs.getcwd())
end)
- it('restores buffers when using tab-local working directories', function()
+ it('restores buffers with tab-local CWD', function()
local tmpfile_base = file_prefix .. '-tmpfile'
local cwd_dir = funcs.getcwd()
local session_path = cwd_dir .. get_pathsep() .. session_file
@@ -70,4 +93,24 @@ describe(':mksession', function()
command('tabnext 2')
eq(cwd_dir .. get_pathsep() .. tmpfile_base .. '2', funcs.expand('%:p'))
end)
+
+ it('restores CWD for :terminal buffers #11288', function()
+ local cwd_dir = funcs.fnamemodify('.', ':p:~'):gsub([[[\/]*$]], '')
+ cwd_dir = cwd_dir:gsub([[\]], '/') -- :mksession always uses unix slashes.
+ local session_path = cwd_dir..'/'..session_file
+
+ command('cd '..tab_dir)
+ command('terminal echo $PWD')
+ command('cd '..cwd_dir)
+ command('mksession '..session_path)
+ command('qall!')
+
+ -- Create a new test instance of Nvim.
+ clear()
+ command('silent source '..session_path)
+
+ local expected_cwd = cwd_dir..'/'..tab_dir
+ matches('^term://'..pesc(expected_cwd)..'//%d+:', funcs.expand('%'))
+ command('qall!')
+ end)
end)
diff --git a/test/functional/ex_cmds/profile_spec.lua b/test/functional/ex_cmds/profile_spec.lua
index f185db192a..2b92f8d0de 100644
--- a/test/functional/ex_cmds/profile_spec.lua
+++ b/test/functional/ex_cmds/profile_spec.lua
@@ -6,6 +6,9 @@ local eval = helpers.eval
local command = helpers.command
local eq, neq = helpers.eq, helpers.neq
local tempfile = helpers.tmpname()
+local source = helpers.source
+local matches = helpers.matches
+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.
@@ -32,20 +35,67 @@ describe(':profile', function()
end
end)
- it('dump', function()
- eq(0, eval('v:profiling'))
- command('profile start ' .. tempfile)
- eq(1, eval('v:profiling'))
- assert_file_exists_not(tempfile)
- command('profile dump')
- assert_file_exists(tempfile)
+ describe('dump', function()
+ it('works', function()
+ eq(0, eval('v:profiling'))
+ command('profile start ' .. tempfile)
+ eq(1, eval('v:profiling'))
+ assert_file_exists_not(tempfile)
+ command('profile dump')
+ assert_file_exists(tempfile)
+ end)
+
+ it('not resetting the profile', function()
+ source([[
+ function! Test()
+ endfunction
+ ]])
+ command('profile start ' .. tempfile)
+ assert_file_exists_not(tempfile)
+ command('profile func Test')
+ command('call Test()')
+ command('profile dump')
+ assert_file_exists(tempfile)
+ local profile = read_file(tempfile)
+ matches('Called 1 time', profile)
+ command('call Test()')
+ command('profile dump')
+ assert_file_exists(tempfile)
+ profile = read_file(tempfile)
+ matches('Called 2 time', profile)
+ command('profile stop')
+ end)
end)
- it('stop', function()
- command('profile start ' .. tempfile)
- assert_file_exists_not(tempfile)
- command('profile stop')
- assert_file_exists(tempfile)
- eq(0, eval('v:profiling'))
+ describe('stop', function()
+ it('works', function()
+ command('profile start ' .. tempfile)
+ assert_file_exists_not(tempfile)
+ command('profile stop')
+ assert_file_exists(tempfile)
+ eq(0, eval('v:profiling'))
+ end)
+
+ it('resetting the profile', function()
+ source([[
+ function! Test()
+ endfunction
+ ]])
+ command('profile start ' .. tempfile)
+ assert_file_exists_not(tempfile)
+ command('profile func Test')
+ command('call Test()')
+ command('profile stop')
+ assert_file_exists(tempfile)
+ local profile = read_file(tempfile)
+ matches('Called 1 time', profile)
+ command('profile start ' .. tempfile)
+ command('profile func Test')
+ command('call Test()')
+ command('profile stop')
+ assert_file_exists(tempfile)
+ profile = read_file(tempfile)
+ matches('Called 1 time', profile)
+ end)
end)
end)
diff --git a/test/functional/ex_cmds/script_spec.lua b/test/functional/ex_cmds/script_spec.lua
index 4e57d2755d..0a772c559b 100644
--- a/test/functional/ex_cmds/script_spec.lua
+++ b/test/functional/ex_cmds/script_spec.lua
@@ -22,7 +22,7 @@ describe('script_get-based command', function()
%s %s
endif
]])):format(cmd, garbage)))
- eq('', meths.command_output('messages'))
+ eq('', meths.exec('messages', true))
if check_neq then
neq(0, exc_exec(dedent([[
%s %s
@@ -37,7 +37,7 @@ describe('script_get-based command', function()
EOF
endif
]])):format(cmd, garbage)))
- eq('', meths.command_output('messages'))
+ eq('', meths.exec('messages', true))
if check_neq then
eq(true, pcall(source, (dedent([[
let g:exc = 0
diff --git a/test/functional/ex_cmds/sign_spec.lua b/test/functional/ex_cmds/sign_spec.lua
index 9d08a66625..891cfe1670 100644
--- a/test/functional/ex_cmds/sign_spec.lua
+++ b/test/functional/ex_cmds/sign_spec.lua
@@ -16,8 +16,8 @@ describe('sign', function()
nvim('command', 'sign place 34 line=3 name=Foo buffer='..buf2)
-- now unplace without specifying a buffer
nvim('command', 'sign unplace 34')
- eq("--- Signs ---\n", nvim('command_output', 'sign place buffer='..buf1))
- eq("--- Signs ---\n", nvim('command_output', 'sign place buffer='..buf2))
+ eq("--- Signs ---\n", nvim('exec', 'sign place buffer='..buf1, true))
+ eq("--- Signs ---\n", nvim('exec', 'sign place buffer='..buf2, true))
end)
end)
end)
diff --git a/test/functional/ex_cmds/write_spec.lua b/test/functional/ex_cmds/write_spec.lua
index 3f54ff6f41..4f526ddfca 100644
--- a/test/functional/ex_cmds/write_spec.lua
+++ b/test/functional/ex_cmds/write_spec.lua
@@ -41,7 +41,7 @@ describe(':write', function()
command("silent !ln -s test_bkc_file.txt test_bkc_link.txt")
end
if eval('v:shell_error') ~= 0 then
- pending('Cannot create symlink', function()end)
+ pending('Cannot create symlink')
end
source([[
edit test_bkc_link.txt
@@ -61,7 +61,7 @@ describe(':write', function()
command("silent !ln -s test_bkc_file.txt test_bkc_link.txt")
end
if eval('v:shell_error') ~= 0 then
- pending('Cannot create symlink', function()end)
+ pending('Cannot create symlink')
end
source([[
edit test_bkc_link.txt
@@ -75,8 +75,7 @@ describe(':write', function()
it("appends FIFO file", function()
-- mkfifo creates read-only .lnk files on Windows
if iswin() or eval("executable('mkfifo')") == 0 then
- pending('missing "mkfifo" command', function()end)
- return
+ pending('missing "mkfifo" command')
end
local text = "some fifo text from write_spec"
diff --git a/test/functional/example_spec.lua b/test/functional/example_spec.lua
index 883fe4ba63..f07f88b2b6 100644
--- a/test/functional/example_spec.lua
+++ b/test/functional/example_spec.lua
@@ -3,7 +3,10 @@
local helpers = require('test.functional.helpers')(after_each)
local Screen = require('test.functional.ui.screen')
-local clear, feed = helpers.clear, helpers.feed
+local clear = helpers.clear
+local command = helpers.command
+local eq = helpers.eq
+local feed = helpers.feed
describe('example', function()
local screen
@@ -33,4 +36,37 @@ describe('example', function()
|
]])
end)
+
+ it('override UI event-handler', function()
+ -- Example: override the "tabline_update" UI event handler.
+ --
+ -- screen.lua defines default handlers for UI events, but tests
+ -- may sometimes want to override a handler.
+
+ -- The UI must declare that it wants to handle the UI events.
+ -- For this example, we enable `ext_tabline`:
+ screen:detach()
+ screen = Screen.new(25, 5)
+ screen:attach({rgb=true, ext_tabline=true})
+
+ -- From ":help ui" we find that `tabline_update` receives `curtab` and
+ -- `tabs` objects. So we declare the UI handler like this:
+ local event_tabs, event_curtab
+ function screen:_handle_tabline_update(curtab, tabs)
+ event_curtab, event_tabs = curtab, tabs
+ end
+
+ -- Create a tabpage...
+ command('tabedit foo')
+
+ -- Use screen:expect{condition=…} to check the result.
+ screen:expect{condition=function()
+ eq({ id = 2 }, event_curtab)
+ eq({
+ {tab = { id = 1 }, name = '[No Name]'},
+ {tab = { id = 2 }, name = 'foo'},
+ },
+ event_tabs)
+ end}
+ end)
end)
diff --git a/test/functional/fixtures/CMakeLists.txt b/test/functional/fixtures/CMakeLists.txt
index dbcb157956..270540de2e 100644
--- a/test/functional/fixtures/CMakeLists.txt
+++ b/test/functional/fixtures/CMakeLists.txt
@@ -1,12 +1,12 @@
-add_executable(tty-test tty-test.c)
+add_executable(tty-test EXCLUDE_FROM_ALL tty-test.c)
target_link_libraries(tty-test ${LIBUV_LIBRARIES})
-add_executable(shell-test shell-test.c)
-add_executable(printargs-test printargs-test.c)
-add_executable(printenv-test printenv-test.c)
+add_executable(shell-test EXCLUDE_FROM_ALL shell-test.c)
+add_executable(printargs-test EXCLUDE_FROM_ALL printargs-test.c)
+add_executable(printenv-test EXCLUDE_FROM_ALL printenv-test.c)
if(WIN32)
set_target_properties(printenv-test PROPERTIES LINK_FLAGS -municode)
endif()
-add_executable(streams-test streams-test.c)
+add_executable(streams-test EXCLUDE_FROM_ALL streams-test.c)
target_link_libraries(streams-test ${LIBUV_LIBRARIES})
diff --git a/test/functional/fixtures/api_level_6.mpack b/test/functional/fixtures/api_level_6.mpack
index b348f86beb..1c939d5931 100644
--- a/test/functional/fixtures/api_level_6.mpack
+++ b/test/functional/fixtures/api_level_6.mpack
Binary files differ
diff --git a/test/functional/fixtures/fake-lsp-server.lua b/test/functional/fixtures/fake-lsp-server.lua
new file mode 100644
index 0000000000..dca7f35923
--- /dev/null
+++ b/test/functional/fixtures/fake-lsp-server.lua
@@ -0,0 +1,450 @@
+local protocol = require 'vim.lsp.protocol'
+
+
+-- Logs to $NVIM_LOG_FILE.
+--
+-- TODO(justinmk): remove after https://github.com/neovim/neovim/pull/7062
+local function log(loglevel, area, msg)
+ vim.fn.writefile(
+ {string.format('%s %s: %s', loglevel, area, msg)},
+ vim.env.NVIM_LOG_FILE,
+ 'a')
+end
+
+local function message_parts(sep, ...)
+ local parts = {}
+ for i = 1, select("#", ...) do
+ local arg = select(i, ...)
+ if arg ~= nil then
+ table.insert(parts, arg)
+ end
+ end
+ return table.concat(parts, sep)
+end
+
+-- Assert utility methods
+
+local function assert_eq(a, b, ...)
+ if not vim.deep_equal(a, b) then
+ error(message_parts(": ",
+ ..., "assert_eq failed",
+ string.format("left == %q, right == %q", vim.inspect(a), vim.inspect(b))
+ ))
+ end
+end
+
+local function format_message_with_content_length(encoded_message)
+ return table.concat {
+ 'Content-Length: '; tostring(#encoded_message); '\r\n\r\n';
+ encoded_message;
+ }
+end
+
+local function read_message()
+ local line = io.read("*l")
+ local length = line:lower():match("content%-length:%s*(%d+)")
+ return vim.fn.json_decode(io.read(2 + length):sub(2))
+end
+
+local function send(payload)
+ io.stdout:write(format_message_with_content_length(vim.fn.json_encode(payload)))
+end
+
+local function respond(id, err, result)
+ assert(type(id) == 'number', "id must be a number")
+ send { jsonrpc = "2.0"; id = id, error = err, result = result }
+end
+
+local function notify(method, params)
+ assert(type(method) == 'string', "method must be a string")
+ send { method = method, params = params or {} }
+end
+
+local function expect_notification(method, params, ...)
+ local message = read_message()
+ assert_eq(method, message.method,
+ ..., "expect_notification", "method")
+ assert_eq(params, message.params,
+ ..., "expect_notification", method, "params")
+ assert_eq({jsonrpc = "2.0"; method=method, params=params}, message,
+ ..., "expect_notification", "message")
+end
+
+local function expect_request(method, callback, ...)
+ local req = read_message()
+ assert_eq(method, req.method,
+ ..., "expect_request", "method")
+ local err, result = callback(req.params)
+ respond(req.id, err, result)
+end
+
+io.stderr:setvbuf("no")
+
+local function skeleton(config)
+ local on_init = assert(config.on_init)
+ local body = assert(config.body)
+ expect_request("initialize", function(params)
+ return nil, on_init(params)
+ end)
+ expect_notification("initialized", {})
+ body()
+ expect_request("shutdown", function()
+ return nil, {}
+ end)
+ expect_notification("exit", nil)
+end
+
+-- The actual tests.
+
+local tests = {}
+
+function tests.basic_init()
+ skeleton {
+ on_init = function(_params)
+ return { capabilities = {} }
+ end;
+ body = function()
+ notify('test')
+ end;
+ }
+end
+
+function tests.basic_check_capabilities()
+ skeleton {
+ on_init = function(params)
+ local expected_capabilities = protocol.make_client_capabilities()
+ assert_eq(params.capabilities, expected_capabilities)
+ return {
+ capabilities = {
+ textDocumentSync = protocol.TextDocumentSyncKind.Full;
+ }
+ }
+ end;
+ body = function()
+ end;
+ }
+end
+
+function tests.basic_finish()
+ skeleton {
+ on_init = function(params)
+ local expected_capabilities = protocol.make_client_capabilities()
+ assert_eq(params.capabilities, expected_capabilities)
+ return {
+ capabilities = {
+ textDocumentSync = protocol.TextDocumentSyncKind.Full;
+ }
+ }
+ end;
+ body = function()
+ expect_notification("finish")
+ notify('finish')
+ end;
+ }
+end
+
+function tests.basic_check_buffer_open()
+ skeleton {
+ on_init = function(params)
+ local expected_capabilities = protocol.make_client_capabilities()
+ assert_eq(params.capabilities, expected_capabilities)
+ return {
+ capabilities = {
+ textDocumentSync = protocol.TextDocumentSyncKind.Full;
+ }
+ }
+ end;
+ body = function()
+ notify('start')
+ expect_notification('textDocument/didOpen', {
+ textDocument = {
+ languageId = "";
+ text = table.concat({"testing"; "123"}, "\n") .. '\n';
+ uri = "file://";
+ version = 0;
+ };
+ })
+ expect_notification("finish")
+ notify('finish')
+ end;
+ }
+end
+
+function tests.basic_check_buffer_open_and_change()
+ skeleton {
+ on_init = function(params)
+ local expected_capabilities = protocol.make_client_capabilities()
+ assert_eq(params.capabilities, expected_capabilities)
+ return {
+ capabilities = {
+ textDocumentSync = protocol.TextDocumentSyncKind.Full;
+ }
+ }
+ end;
+ body = function()
+ notify('start')
+ expect_notification('textDocument/didOpen', {
+ textDocument = {
+ languageId = "";
+ text = table.concat({"testing"; "123"}, "\n") .. '\n';
+ uri = "file://";
+ version = 0;
+ };
+ })
+ expect_notification('textDocument/didChange', {
+ textDocument = {
+ uri = "file://";
+ version = 3;
+ };
+ contentChanges = {
+ { text = table.concat({"testing"; "boop"}, "\n") .. '\n'; };
+ }
+ })
+ expect_notification("finish")
+ notify('finish')
+ end;
+ }
+end
+
+function tests.basic_check_buffer_open_and_change_noeol()
+ skeleton {
+ on_init = function(params)
+ local expected_capabilities = protocol.make_client_capabilities()
+ assert_eq(params.capabilities, expected_capabilities)
+ return {
+ capabilities = {
+ textDocumentSync = protocol.TextDocumentSyncKind.Full;
+ }
+ }
+ end;
+ body = function()
+ notify('start')
+ expect_notification('textDocument/didOpen', {
+ textDocument = {
+ languageId = "";
+ text = table.concat({"testing"; "123"}, "\n");
+ uri = "file://";
+ version = 0;
+ };
+ })
+ expect_notification('textDocument/didChange', {
+ textDocument = {
+ uri = "file://";
+ version = 3;
+ };
+ contentChanges = {
+ { text = table.concat({"testing"; "boop"}, "\n"); };
+ }
+ })
+ expect_notification("finish")
+ notify('finish')
+ end;
+ }
+end
+function tests.basic_check_buffer_open_and_change_multi()
+ skeleton {
+ on_init = function(params)
+ local expected_capabilities = protocol.make_client_capabilities()
+ assert_eq(params.capabilities, expected_capabilities)
+ return {
+ capabilities = {
+ textDocumentSync = protocol.TextDocumentSyncKind.Full;
+ }
+ }
+ end;
+ body = function()
+ notify('start')
+ expect_notification('textDocument/didOpen', {
+ textDocument = {
+ languageId = "";
+ text = table.concat({"testing"; "123"}, "\n") .. '\n';
+ uri = "file://";
+ version = 0;
+ };
+ })
+ expect_notification('textDocument/didChange', {
+ textDocument = {
+ uri = "file://";
+ version = 3;
+ };
+ contentChanges = {
+ { text = table.concat({"testing"; "321"}, "\n") .. '\n'; };
+ }
+ })
+ expect_notification('textDocument/didChange', {
+ textDocument = {
+ uri = "file://";
+ version = 4;
+ };
+ contentChanges = {
+ { text = table.concat({"testing"; "boop"}, "\n") .. '\n'; };
+ }
+ })
+ expect_notification("finish")
+ notify('finish')
+ end;
+ }
+end
+
+function tests.basic_check_buffer_open_and_change_multi_and_close()
+ skeleton {
+ on_init = function(params)
+ local expected_capabilities = protocol.make_client_capabilities()
+ assert_eq(params.capabilities, expected_capabilities)
+ return {
+ capabilities = {
+ textDocumentSync = protocol.TextDocumentSyncKind.Full;
+ }
+ }
+ end;
+ body = function()
+ notify('start')
+ expect_notification('textDocument/didOpen', {
+ textDocument = {
+ languageId = "";
+ text = table.concat({"testing"; "123"}, "\n") .. '\n';
+ uri = "file://";
+ version = 0;
+ };
+ })
+ expect_notification('textDocument/didChange', {
+ textDocument = {
+ uri = "file://";
+ version = 3;
+ };
+ contentChanges = {
+ { text = table.concat({"testing"; "321"}, "\n") .. '\n'; };
+ }
+ })
+ expect_notification('textDocument/didChange', {
+ textDocument = {
+ uri = "file://";
+ version = 4;
+ };
+ contentChanges = {
+ { text = table.concat({"testing"; "boop"}, "\n") .. '\n'; };
+ }
+ })
+ expect_notification('textDocument/didClose', {
+ textDocument = {
+ uri = "file://";
+ };
+ })
+ expect_notification("finish")
+ notify('finish')
+ end;
+ }
+end
+
+function tests.basic_check_buffer_open_and_change_incremental()
+ skeleton {
+ on_init = function(params)
+ local expected_capabilities = protocol.make_client_capabilities()
+ assert_eq(params.capabilities, expected_capabilities)
+ return {
+ capabilities = {
+ textDocumentSync = protocol.TextDocumentSyncKind.Incremental;
+ }
+ }
+ end;
+ body = function()
+ notify('start')
+ expect_notification('textDocument/didOpen', {
+ textDocument = {
+ languageId = "";
+ text = table.concat({"testing"; "123"}, "\n") .. '\n';
+ uri = "file://";
+ version = 0;
+ };
+ })
+ expect_notification('textDocument/didChange', {
+ textDocument = {
+ uri = "file://";
+ version = 3;
+ };
+ contentChanges = {
+ {
+ range = {
+ start = { line = 1; character = 0; };
+ ["end"] = { line = 2; character = 0; };
+ };
+ rangeLength = 4;
+ text = "boop\n";
+ };
+ }
+ })
+ expect_notification("finish")
+ notify('finish')
+ end;
+ }
+end
+
+function tests.basic_check_buffer_open_and_change_incremental_editing()
+ skeleton {
+ on_init = function(params)
+ local expected_capabilities = protocol.make_client_capabilities()
+ assert_eq(params.capabilities, expected_capabilities)
+ return {
+ capabilities = {
+ textDocumentSync = protocol.TextDocumentSyncKind.Incremental;
+ }
+ }
+ end;
+ body = function()
+ notify('start')
+ expect_notification('textDocument/didOpen', {
+ textDocument = {
+ languageId = "";
+ text = table.concat({"testing"; "123"}, "\n");
+ uri = "file://";
+ version = 0;
+ };
+ })
+ expect_notification('textDocument/didChange', {
+ textDocument = {
+ uri = "file://";
+ version = 3;
+ };
+ contentChanges = {
+ {
+ range = {
+ start = { line = 0; character = 0; };
+ ["end"] = { line = 1; character = 0; };
+ };
+ rangeLength = 4;
+ text = "testing\n\n";
+ };
+ }
+ })
+ expect_notification("finish")
+ notify('finish')
+ end;
+ }
+end
+
+function tests.invalid_header()
+ io.stdout:write("Content-length: \r\n")
+end
+
+-- Tests will be indexed by TEST_NAME
+
+local kill_timer = vim.loop.new_timer()
+kill_timer:start(_G.TIMEOUT or 1e3, 0, function()
+ kill_timer:stop()
+ kill_timer:close()
+ log('ERROR', 'LSP', 'TIMEOUT')
+ io.stderr:write("TIMEOUT")
+ os.exit(100)
+end)
+
+local test_name = _G.TEST_NAME -- lualint workaround
+assert(type(test_name) == 'string', 'TEST_NAME must be specified.')
+local status, err = pcall(assert(tests[test_name], "Test not found"))
+kill_timer:stop()
+kill_timer:close()
+if not status then
+ log('ERROR', 'LSP', tostring(err))
+ io.stderr:write(err)
+ os.exit(101)
+end
+os.exit(0)
diff --git a/test/functional/fixtures/printenv-test.c b/test/functional/fixtures/printenv-test.c
index 5ac076f653..0e68129543 100644
--- a/test/functional/fixtures/printenv-test.c
+++ b/test/functional/fixtures/printenv-test.c
@@ -44,7 +44,7 @@ int main(int argc, char **argv)
utf8_len,
NULL,
NULL);
- fprintf(stderr, "%s", utf8_value);
+ fprintf(stdout, "%s", utf8_value);
free(utf8_value);
#else
char *value = getenv(argv[1]);
@@ -52,8 +52,8 @@ int main(int argc, char **argv)
fprintf(stderr, "env var not found: %s", argv[1]);
return 1;
}
- // Print to stderr to avoid buffering.
- fprintf(stderr, "%s", value);
+ fprintf(stdout, "%s", value);
#endif
+ fflush(stdout);
return 0;
}
diff --git a/test/functional/helpers.lua b/test/functional/helpers.lua
index b29161e34c..e8435cd3b7 100644
--- a/test/functional/helpers.lua
+++ b/test/functional/helpers.lua
@@ -1,4 +1,5 @@
require('coxpcall')
+local busted = require('busted')
local luv = require('luv')
local lfs = require('lfs')
local mpack = require('mpack')
@@ -14,9 +15,9 @@ local check_cores = global_helpers.check_cores
local check_logs = global_helpers.check_logs
local dedent = global_helpers.dedent
local eq = global_helpers.eq
-local filter = global_helpers.filter
+local filter = global_helpers.tbl_filter
local is_os = global_helpers.is_os
-local map = global_helpers.map
+local map = global_helpers.tbl_map
local ok = global_helpers.ok
local sleep = global_helpers.sleep
local tbl_contains = global_helpers.tbl_contains
@@ -28,17 +29,15 @@ local module = {
}
local start_dir = lfs.currentdir()
--- XXX: NVIM_PROG takes precedence, QuickBuild sets it.
module.nvim_prog = (
- os.getenv('NVIM_PROG')
- or os.getenv('NVIM_PRG')
+ os.getenv('NVIM_PRG')
or global_helpers.test_build_dir .. '/bin/nvim'
)
-- Default settings for the test session.
module.nvim_set = (
- 'set shortmess+=IS background=light noswapfile noautoindent'
+ 'set shortmess+=IS background=light noswapfile noautoindent startofline'
..' laststatus=1 undodir=. directory=. viewdir=. backupdir=.'
- ..' belloff= wildoptions-=pum noshowcmd noruler nomore')
+ ..' belloff= wildoptions-=pum noshowcmd noruler nomore redrawdebug=invalid')
module.nvim_argv = {
module.nvim_prog, '-u', 'NONE', '-i', 'NONE',
'--cmd', module.nvim_set, '--embed'}
@@ -187,7 +186,12 @@ function module.expect_msg_seq(...)
if status then
return result
end
- final_error = cat_err(final_error, result)
+ local message = result
+ if type(result) == "table" then
+ -- 'eq' returns several things
+ message = result.message
+ end
+ final_error = cat_err(final_error, message)
end
error(final_error)
end
@@ -385,7 +389,7 @@ function module.retry(max, max_ms, fn)
end
luv.update_time() -- Update cached value of luv.now() (libuv: uv_now()).
if (max and tries >= max) or (luv.now() - start_time > timeout) then
- error("\nretry() attempts: "..tostring(tries).."\n"..tostring(result))
+ busted.fail(string.format("retry() attempts: %d\n%s", tries, tostring(result)), 2)
end
tries = tries + 1
luv.sleep(20) -- Avoid hot loop...
@@ -437,6 +441,7 @@ function module.new_argv(...)
'NVIM_LOG_FILE',
'NVIM_RPLUGIN_MANIFEST',
'GCOV_ERROR_FILE',
+ 'TMPDIR',
}) do
if not env_tbl[k] then
env_tbl[k] = os.getenv(k)
@@ -499,10 +504,21 @@ function module.source(code)
return fname
end
+function module.has_powershell()
+ return module.eval('executable("'..(iswin() and 'powershell' or 'pwsh')..'")') == 1
+end
+
function module.set_shell_powershell()
+ local shell = iswin() and 'powershell' or 'pwsh'
+ assert(module.has_powershell())
+ local cmd = 'Remove-Item -Force '..table.concat(iswin()
+ and {'alias:cat', 'alias:echo', 'alias:sleep'}
+ or {'alias:echo'}, ',')..';'
module.source([[
- set shell=powershell shellquote=( shellpipe=\| shellredir=> shellxquote=
- let &shellcmdflag = '-NoLogo -NoProfile -ExecutionPolicy RemoteSigned -Command Remove-Item -Force alias:sleep; Remove-Item -Force alias:cat;'
+ let &shell = ']]..shell..[['
+ set shellquote= shellpipe=\| shellxquote=
+ let &shellredir = '| Out-File -Encoding UTF8'
+ let &shellcmdflag = '-NoLogo -NoProfile -ExecutionPolicy RemoteSigned -Command ]]..cmd..[['
]])
end
@@ -543,6 +559,11 @@ function module.wait()
session:request('nvim_eval', '1')
end
+function module.buf_lines(bufnr)
+ return module.exec_lua("return vim.api.nvim_buf_get_lines((...), 0, -1, false)", bufnr)
+end
+
+--@see buf_lines()
function module.curbuf_contents()
module.wait() -- Before inspecting the buffer, process all input.
return table.concat(module.curbuf('get_lines', 0, -1, true), '\n')
@@ -576,6 +597,19 @@ function module.assert_alive()
assert(2 == module.eval('1+1'), 'crash? request failed')
end
+-- Asserts that buffer is loaded and visible in the current tabpage.
+function module.assert_visible(bufnr, visible)
+ assert(type(visible) == 'boolean')
+ eq(visible, module.bufmeths.is_loaded(bufnr))
+ if visible then
+ assert(-1 ~= module.funcs.bufwinnr(bufnr),
+ 'expected buffer to be visible in current tabpage: '..tostring(bufnr))
+ else
+ assert(-1 == module.funcs.bufwinnr(bufnr),
+ 'expected buffer NOT visible in current tabpage: '..tostring(bufnr))
+ end
+end
+
local function do_rmdir(path)
local mode, errmsg, errcode = lfs.attributes(path, 'mode')
if mode == nil then
@@ -699,7 +733,7 @@ module.curwinmeths = module.create_callindex(module.curwin)
module.curtabmeths = module.create_callindex(module.curtab)
function module.exec_lua(code, ...)
- return module.meths.execute_lua(code, {...})
+ return module.meths.exec_lua(code, {...})
end
function module.redir_exec(cmd)
@@ -734,7 +768,7 @@ function module.new_pipename()
end
function module.missing_provider(provider)
- if provider == 'ruby' or provider == 'node' then
+ if provider == 'ruby' or provider == 'node' or provider == 'perl' then
local prog = module.funcs['provider#' .. provider .. '#Detect']()
return prog == '' and (provider .. ' not detected') or false
elseif provider == 'python' or provider == 'python3' then
@@ -760,7 +794,7 @@ function module.alter_slashes(obj)
end
return ret
else
- assert(false, 'Could only alter slashes for tables of strings and strings')
+ assert(false, 'expected string or table of strings, got '..type(obj))
end
end
diff --git a/test/functional/insert/insert_spec.lua b/test/functional/insert/insert_spec.lua
index 427954f5a6..330cfbd830 100644
--- a/test/functional/insert/insert_spec.lua
+++ b/test/functional/insert/insert_spec.lua
@@ -37,5 +37,11 @@ describe('insert-mode', function()
command('iunmap <M-l>')
feed('0i<M-l>')
eq({ 0, 1, 2, 0, }, funcs.getpos('.'))
+ -- Unmapped ALT-chord has same `undo` characteristics as ESC+<key>
+ command('0,$d')
+ feed('ahello<M-.>')
+ expect('hellohello')
+ feed('u')
+ expect('hello')
end)
end)
diff --git a/test/functional/legacy/021_control_wi_spec.lua b/test/functional/legacy/021_control_wi_spec.lua
index 87d9deed7a..94871433cd 100644
--- a/test/functional/legacy/021_control_wi_spec.lua
+++ b/test/functional/legacy/021_control_wi_spec.lua
@@ -18,7 +18,7 @@ describe('CTRL-W CTRL-I', function()
start found wrong line
test text]])
- -- Search for the second occurence of start and append to register
+ -- Search for the second occurrence of start and append to register
feed_command('/start')
feed('2[<C-i>')
feed_command('yank A')
diff --git a/test/functional/legacy/022_line_ending_spec.lua b/test/functional/legacy/022_line_ending_spec.lua
deleted file mode 100644
index fb4b782011..0000000000
--- a/test/functional/legacy/022_line_ending_spec.lua
+++ /dev/null
@@ -1,25 +0,0 @@
--- Tests for file with some lines ending in CTRL-M, some not
-
-local helpers = require('test.functional.helpers')(after_each)
-local clear, feed = helpers.clear, helpers.feed
-local feed_command, expect = helpers.feed_command, helpers.expect
-
-describe('line ending', function()
- setup(clear)
-
- it('is working', function()
- feed('i', [[
- this lines ends in a<C-V><C-M>
- this one doesn't
- this one does<C-V><C-M>
- and the last one doesn't]], '<ESC>')
-
- feed_command('set ta tx')
- feed_command('e!')
-
- expect("this lines ends in a\r\n"..
- "this one doesn't\n"..
- "this one does\r\n"..
- "and the last one doesn't")
- end)
-end)
diff --git a/test/functional/legacy/041_writing_and_reading_hundred_kbyte_spec.lua b/test/functional/legacy/041_writing_and_reading_hundred_kbyte_spec.lua
deleted file mode 100644
index b526d82519..0000000000
--- a/test/functional/legacy/041_writing_and_reading_hundred_kbyte_spec.lua
+++ /dev/null
@@ -1,43 +0,0 @@
--- Test for writing and reading a file of over 100 Kbyte
-
-local helpers = require('test.functional.helpers')(after_each)
-
-local clear, feed, insert = helpers.clear, helpers.feed, helpers.insert
-local command, expect = helpers.command, helpers.expect
-local wait = helpers.wait
-
-describe('writing and reading a file of over 100 Kbyte', function()
- setup(clear)
-
- it('is working', function()
- insert([[
- This is the start
- This is the leader
- This is the middle
- This is the trailer
- This is the end]])
-
- feed('kY3000p2GY3000p')
- wait()
-
- command('w! test.out')
- command('%d')
- command('e! test.out')
- command('yank A')
- command('3003yank A')
- command('6005yank A')
- command('%d')
- command('0put a')
- command('$d')
- command('w!')
-
- expect([[
- This is the start
- This is the middle
- This is the end]])
- end)
-
- teardown(function()
- os.remove('test.out')
- end)
-end)
diff --git a/test/functional/legacy/045_folding_spec.lua b/test/functional/legacy/045_folding_spec.lua
index 6ca1176aea..1e5239ceac 100644
--- a/test/functional/legacy/045_folding_spec.lua
+++ b/test/functional/legacy/045_folding_spec.lua
@@ -14,9 +14,6 @@ describe('folding', function()
screen = Screen.new(20, 8)
screen:attach()
end)
- after_each(function()
- screen:detach()
- end)
it('creation, opening, moving (to the end) and closing', function()
insert([[
diff --git a/test/functional/legacy/063_match_and_matchadd_spec.lua b/test/functional/legacy/063_match_and_matchadd_spec.lua
index 518d79861b..a4f5d6ac5b 100644
--- a/test/functional/legacy/063_match_and_matchadd_spec.lua
+++ b/test/functional/legacy/063_match_and_matchadd_spec.lua
@@ -14,6 +14,10 @@ describe('063: Test for ":match", "matchadd()" and related functions', function(
it('is working', function()
local screen = Screen.new(40, 5)
screen:attach()
+ screen:set_default_attr_ids({
+ [0] = {bold = true, foreground = Screen.colors.Blue},
+ [1] = {background = Screen.colors.Red},
+ })
-- Check that "matcharg()" returns the correct group and pattern if a match
-- is defined.
@@ -126,22 +130,22 @@ describe('063: Test for ":match", "matchadd()" and related functions', function(
command("call matchaddpos('MyGroup1', [[1, 5], [1, 8, 3]], 10, 3)")
screen:expect([[
abcd{1:e}fg{1:hij}klmnop^q |
- ~ |
- ~ |
- ~ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
|
- ]], {[1] = {background = Screen.colors.Red}}, {{bold = true, foreground = Screen.colors.Blue}})
+ ]])
command("call clearmatches()")
command("call setline(1, 'abcdΣabcdef')")
command("call matchaddpos('MyGroup1', [[1, 4, 2], [1, 9, 2]])")
screen:expect([[
abc{1:dΣ}ab{1:cd}e^f |
- ~ |
- ~ |
- ~ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
|
- ]],{[1] = {background = Screen.colors.Red}}, {{bold = true, foreground = Screen.colors.Blue}})
+ ]])
end)
end)
diff --git a/test/functional/legacy/075_maparg_spec.lua b/test/functional/legacy/075_maparg_spec.lua
index 0164f5077a..ee2b041b51 100644
--- a/test/functional/legacy/075_maparg_spec.lua
+++ b/test/functional/legacy/075_maparg_spec.lua
@@ -49,9 +49,9 @@ describe('maparg()', function()
-- Assert buffer contents.
expect([[
is<F4>foo
- {'lnum': 0, 'silent': 0, 'noremap': 0, 'lhs': 'foo<C-V>', 'mode': ' ', 'nowait': 0, 'expr': 0, 'sid': 0, 'rhs': 'is<F4>foo', 'buffer': 0}
- {'lnum': 0, 'silent': 1, 'noremap': 1, 'lhs': 'bar', 'mode': 'v', 'nowait': 0, 'expr': 1, 'sid': 0, 'rhs': 'isbar', 'buffer': 1}
- {'lnum': 0, 'silent': 0, 'noremap': 0, 'lhs': 'foo', 'mode': ' ', 'nowait': 1, 'expr': 0, 'sid': 0, 'rhs': 'bar', 'buffer': 1}
+ {'lnum': 0, 'script': 0, 'silent': 0, 'noremap': 0, 'lhs': 'foo<C-V>', 'mode': ' ', 'nowait': 0, 'expr': 0, 'sid': 0, 'rhs': 'is<F4>foo', 'buffer': 0}
+ {'lnum': 0, 'script': 1, 'silent': 1, 'noremap': 1, 'lhs': 'bar', 'mode': 'v', 'nowait': 0, 'expr': 1, 'sid': 0, 'rhs': 'isbar', 'buffer': 1}
+ {'lnum': 0, 'script': 0, 'silent': 0, 'noremap': 0, 'lhs': 'foo', 'mode': ' ', 'nowait': 1, 'expr': 0, 'sid': 0, 'rhs': 'bar', 'buffer': 1}
xrx
yRy
abcd]])
diff --git a/test/functional/legacy/077_mf_hash_grow_spec.lua b/test/functional/legacy/077_mf_hash_grow_spec.lua
deleted file mode 100644
index 4719a3ecbf..0000000000
--- a/test/functional/legacy/077_mf_hash_grow_spec.lua
+++ /dev/null
@@ -1,52 +0,0 @@
--- Inserts 2 million lines with consecutive integers starting from 1
--- (essentially, the output of GNU's seq 1 2000000), writes them to Xtest
--- and calculates its cksum.
--- We need 2 million lines to trigger a call to mf_hash_grow(). If it would mess
--- up the lines the checksum would differ.
--- cksum is part of POSIX and so should be available on most Unixes.
--- If it isn't available then the test will be skipped.
-
-local helpers = require('test.functional.helpers')(after_each)
-
-local feed = helpers.feed
-local wait = helpers.wait
-local clear = helpers.clear
-local expect = helpers.expect
-local command = helpers.command
-
-describe('mf_hash_grow()', function()
- setup(clear)
-
- -- Check to see if cksum exists, otherwise skip the test
- local null = helpers.iswin() and 'nul' or '/dev/null'
- if os.execute('cksum --help >' .. null .. ' 2>&1') ~= 0 then
- pending('was not tested because cksum was not found', function() end)
- else
- it('is working', function()
- command('set fileformat=unix undolevels=-1')
-
- -- Fill the buffer with numbers 1 - 2000000
- command('let i = 1')
- command('while i <= 2000000 | call append(i, range(i, i + 99)) | let i += 100 | endwhile')
-
- -- Delete empty first line, save to Xtest, and clear buffer
- feed('ggdd<cr>')
- wait()
- command('w! Xtest')
- feed('ggdG<cr>')
- wait()
-
- -- Calculate the cksum of Xtest and delete first line
- command('r !cksum Xtest')
- feed('ggdd<cr>')
-
- -- Assert correct output of cksum.
- expect([[
- 3678979763 14888896 Xtest]])
- end)
- end
-
- teardown(function()
- os.remove('Xtest')
- end)
-end)
diff --git a/test/functional/legacy/084_curswant_spec.lua b/test/functional/legacy/084_curswant_spec.lua
deleted file mode 100644
index 42cb2fc56d..0000000000
--- a/test/functional/legacy/084_curswant_spec.lua
+++ /dev/null
@@ -1,49 +0,0 @@
--- Tests for curswant not changing when setting an option.
-
-local helpers = require('test.functional.helpers')(after_each)
-local insert, source = helpers.insert, helpers.source
-local clear, expect = helpers.clear, helpers.expect
-
-describe('curswant', function()
- setup(clear)
-
- -- luacheck: ignore 621 (Indentation)
- it('is working', function()
- insert([[
- start target options
- tabstop
- timeoutlen
- ttimeoutlen
- end target options]])
-
- source([[
- /^start target options$/+1,/^end target options$/-1 yank
- let target_option_names = split(@0)
- function TestCurswant(option_name)
- normal! ggf8j
- let curswant_before = winsaveview().curswant
- execute 'let' '&'.a:option_name '=' '&'.a:option_name
- let curswant_after = winsaveview().curswant
- return [a:option_name, curswant_before, curswant_after]
- endfunction
-
- new
- put =['1234567890', '12345']
- 1 delete _
- let result = []
- for option_name in target_option_names
- call add(result, TestCurswant(option_name))
- endfor
-
- new
- put =map(copy(result), 'join(v:val, '' '')')
- 1 delete _
- ]])
-
- -- Assert buffer contents.
- expect([[
- tabstop 7 4
- timeoutlen 7 7
- ttimeoutlen 7 7]])
- end)
-end)
diff --git a/test/functional/legacy/095_regexp_multibyte_spec.lua b/test/functional/legacy/095_regexp_multibyte_spec.lua
index 845ebaaad7..fad0dc8023 100644
--- a/test/functional/legacy/095_regexp_multibyte_spec.lua
+++ b/test/functional/legacy/095_regexp_multibyte_spec.lua
@@ -1,5 +1,5 @@
-- Test for regexp patterns with multi-byte support, using utf-8.
--- See test64 for the non-multi-byte tests.
+-- See test_regexp_latin.vim for the non-multi-byte tests.
-- A pattern that gives the expected result produces OK, so that we know it was
-- actually tried.
diff --git a/test/functional/legacy/097_glob_path_spec.lua b/test/functional/legacy/097_glob_path_spec.lua
index ccd93fed60..dd5a26ad3b 100644
--- a/test/functional/legacy/097_glob_path_spec.lua
+++ b/test/functional/legacy/097_glob_path_spec.lua
@@ -52,7 +52,7 @@ describe('glob() and globpath()', function()
command([[$put =glob('Xxx\{')]])
command([[$put =glob('Xxx\$')]])
- command('silent w! Xxx{')
+ command('silent w! Xxx\\{')
command([[w! Xxx\$]])
command([[$put =glob('Xxx\{')]])
command([[$put =glob('Xxx\$')]])
diff --git a/test/functional/legacy/098_scrollbind_spec.lua b/test/functional/legacy/098_scrollbind_spec.lua
deleted file mode 100644
index d22aefdcbc..0000000000
--- a/test/functional/legacy/098_scrollbind_spec.lua
+++ /dev/null
@@ -1,48 +0,0 @@
--- Test for 'scrollbind' causing an unexpected scroll of one of the windows.
-
-local helpers = require('test.functional.helpers')(after_each)
-local source = helpers.source
-local clear, expect = helpers.clear, helpers.expect
-
-describe('scrollbind', function()
- setup(clear)
-
- it('is working', function()
- source([[
- set laststatus=0
- let g:totalLines = &lines * 20
- let middle = g:totalLines / 2
- wincmd n
- wincmd o
- for i in range(1, g:totalLines)
- call setline(i, 'LINE ' . i)
- endfor
- exe string(middle)
- normal zt
- normal M
- aboveleft vert new
- for i in range(1, g:totalLines)
- call setline(i, 'line ' . i)
- endfor
- exe string(middle)
- normal zt
- normal M
- setl scb | wincmd p
- setl scb
- wincmd w
- let topLineLeft = line('w0')
- wincmd p
- let topLineRight = line('w0')
- setl noscrollbind
- wincmd p
- setl noscrollbind
- q!
- %del _
- call setline(1, 'Difference between the top lines (left - right): ' . string(topLineLeft - topLineRight))
- brewind
- ]])
-
- -- Assert buffer contents.
- expect("Difference between the top lines (left - right): 0")
- end)
-end)
diff --git a/test/functional/legacy/104_let_assignment_spec.lua b/test/functional/legacy/104_let_assignment_spec.lua
deleted file mode 100644
index a03bb026f6..0000000000
--- a/test/functional/legacy/104_let_assignment_spec.lua
+++ /dev/null
@@ -1,54 +0,0 @@
--- Tests for :let.
-
-local helpers = require('test.functional.helpers')(after_each)
-local clear, source = helpers.clear, helpers.source
-local command, expect = helpers.command, helpers.expect
-
-describe(':let', function()
- setup(clear)
-
- it('is working', function()
- command('set runtimepath+=test/functional/fixtures')
-
- -- Test to not autoload when assigning. It causes internal error.
- source([[
- try
- let Test104#numvar = function('tr')
- $put ='OK: ' . string(Test104#numvar)
- catch
- $put ='FAIL: ' . v:exception
- endtry
- let a = 1
- let b = 2
- for letargs in ['a b', '{0 == 1 ? "a" : "b"}', '{0 == 1 ? "a" : "b"} a', 'a {0 == 1 ? "a" : "b"}']
- try
- redir => messages
- execute 'let' letargs
- redir END
- $put ='OK:'
- $put =split(substitute(messages, '\n', '\0 ', 'g'), '\n')
- catch
- $put ='FAIL: ' . v:exception
- redir END
- endtry
- endfor]])
-
- -- Remove empty line
- command('1d')
-
- -- Assert buffer contents.
- expect([[
- OK: function('tr')
- OK:
- a #1
- b #2
- OK:
- b #2
- OK:
- b #2
- a #1
- OK:
- a #1
- b #2]])
- end)
-end)
diff --git a/test/functional/legacy/assert_spec.lua b/test/functional/legacy/assert_spec.lua
index 8df2d89b70..d48b8882af 100644
--- a/test/functional/legacy/assert_spec.lua
+++ b/test/functional/legacy/assert_spec.lua
@@ -3,6 +3,7 @@ local nvim, call = helpers.meths, helpers.call
local clear, eq = helpers.clear, helpers.eq
local source, command = helpers.source, helpers.command
local exc_exec = helpers.exc_exec
+local eval = helpers.eval
local function expected_errors(errors)
eq(errors, nvim.get_vvar('errors'))
@@ -37,6 +38,9 @@ describe('assert function:', function()
call assert_equal(4, n)
let l = [1, 2, 3]
call assert_equal([1, 2, 3], l)
+ call assert_equal(v:_null_list, v:_null_list)
+ call assert_equal(v:_null_list, [])
+ call assert_equal([], v:_null_list)
fu Func()
endfu
let F1 = function('Func')
@@ -47,18 +51,18 @@ describe('assert function:', function()
end)
it('should not change v:errors when expected is equal to actual', function()
- call('assert_equal', '', '')
- call('assert_equal', 'string', 'string')
+ eq(0, call('assert_equal', '', ''))
+ eq(0, call('assert_equal', 'string', 'string'))
expected_empty()
end)
it('should change v:errors when expected is not equal to actual', function()
- call('assert_equal', 0, {0})
+ eq(1, call('assert_equal', 0, {0}))
expected_errors({'Expected 0 but got [0]'})
end)
it('should change v:errors when expected is not equal to actual', function()
- call('assert_equal', 0, "0")
+ eq(1, call('assert_equal', 0, "0"))
expected_errors({"Expected 0 but got '0'"})
end)
@@ -91,18 +95,23 @@ describe('assert function:', function()
call('assert_equal', 'foo', 'bar', 'testing')
expected_errors({"testing: Expected 'foo' but got 'bar'"})
end)
+
+ it('should shorten a long message', function()
+ call ('assert_equal', 'XxxxxxxxxxxxxxxxxxxxxxX', 'XyyyyyyyyyyyyyyyyyyyyyyyyyX')
+ expected_errors({"Expected 'X\\[x occurs 21 times]X' but got 'X\\[y occurs 25 times]X'"})
+ end)
end)
-- assert_notequal({expected}, {actual}[, {msg}])
describe('assert_notequal', function()
it('should not change v:errors when expected differs from actual', function()
- call('assert_notequal', 'foo', 4)
- call('assert_notequal', {1, 2, 3}, 'foo')
+ eq(0, call('assert_notequal', 'foo', 4))
+ eq(0, call('assert_notequal', {1, 2, 3}, 'foo'))
expected_empty()
end)
it('should change v:errors when expected is equal to actual', function()
- call('assert_notequal', 'foo', 'foo')
+ eq(1, call('assert_notequal', 'foo', 'foo'))
expected_errors({"Expected not equal to 'foo'"})
end)
end)
@@ -110,13 +119,13 @@ describe('assert function:', function()
-- assert_false({actual}, [, {msg}])
describe('assert_false', function()
it('should not change v:errors when actual is false', function()
- call('assert_false', 0)
- call('assert_false', false)
+ eq(0, call('assert_false', 0))
+ eq(0, call('assert_false', false))
expected_empty()
end)
it('should change v:errors when actual is not false', function()
- call('assert_false', 1)
+ eq(1, call('assert_false', 1))
expected_errors({'Expected False but got 1'})
end)
@@ -129,14 +138,14 @@ describe('assert function:', function()
-- assert_true({actual}, [, {msg}])
describe('assert_true', function()
it('should not change v:errors when actual is true', function()
- call('assert_true', 1)
- call('assert_true', -1) -- In Vim script, non-zero Numbers are TRUE.
- call('assert_true', true)
+ eq(0, call('assert_true', 1))
+ eq(0, call('assert_true', -1)) -- In Vim script, non-zero Numbers are TRUE.
+ eq(0, call('assert_true', true))
expected_empty()
end)
it('should change v:errors when actual is not true', function()
- call('assert_true', 1.5)
+ eq(1, call('assert_true', 1.5))
expected_errors({'Expected True but got 1.5'})
end)
end)
@@ -233,20 +242,31 @@ describe('assert function:', function()
-- assert_fails({cmd}, [, {error}])
describe('assert_fails', function()
it('should change v:errors when error does not match v:errmsg', function()
- command([[call assert_fails('xxx', {})]])
+ eq(1, eval([[assert_fails('xxx', {})]]))
command([[call assert_match("Expected {} but got 'E731:", v:errors[0])]])
expected_errors({"Expected {} but got 'E731: using Dictionary as a String'"})
end)
it('should not change v:errors when cmd errors', function()
- call('assert_fails', 'NonexistentCmd')
+ eq(0, eval([[assert_fails('NonexistentCmd')]]))
expected_empty()
end)
it('should change v:errors when cmd succeeds', function()
- call('assert_fails', 'call empty("")')
+ eq(1, eval([[assert_fails('call empty("")', '')]]))
expected_errors({'command did not fail: call empty("")'})
end)
+
+ it('can specify and get a message about what failed', function()
+ eq(1, eval([[assert_fails('xxx', {}, 'stupid')]]))
+ command([[call assert_match("stupid: Expected {} but got 'E731:", v:errors[0])]])
+ expected_errors({"stupid: Expected {} but got 'E731: using Dictionary as a String'"})
+ end)
+
+ it('can specify and get a message even when cmd succeeds', function()
+ eq(1, eval([[assert_fails('echo', '', 'echo command')]]))
+ expected_errors({'command did not fail: echo command'})
+ end)
end)
-- assert_inrange({lower}, {upper}, {actual}[, {msg}])
@@ -277,7 +297,7 @@ describe('assert function:', function()
-- assert_report({msg})
describe('assert_report()', function()
it('should add a message to v:errors', function()
- command("call assert_report('something is wrong')")
+ eq(1, call('assert_report', 'something is wrong'))
command("call assert_match('something is wrong', v:errors[0])")
command('call remove(v:errors, 0)')
expected_empty()
@@ -291,7 +311,7 @@ describe('assert function:', function()
try
nocommand
catch
- call assert_exception('E492')
+ call assert_equal(0, assert_exception('E492'))
endtry
]])
expected_empty()
@@ -304,9 +324,9 @@ describe('assert function:', function()
catch
try
" illegal argument, get NULL for error
- call assert_exception([])
+ call assert_equal(1, assert_exception([]))
catch
- call assert_exception('E730')
+ call assert_equal(0, assert_exception('E730'))
endtry
endtry
]])
diff --git a/test/functional/legacy/breakindent_spec.lua b/test/functional/legacy/breakindent_spec.lua
deleted file mode 100644
index fd25e809e0..0000000000
--- a/test/functional/legacy/breakindent_spec.lua
+++ /dev/null
@@ -1,214 +0,0 @@
--- Test for breakindent
-
-local helpers = require('test.functional.helpers')(after_each)
-local feed, insert = helpers.feed, helpers.insert
-local clear, feed_command, expect = helpers.clear, helpers.feed_command, helpers.expect
-
-describe('breakindent', function()
- setup(clear)
-
- -- luacheck: ignore 621 (Indentation)
- -- luacheck: ignore 613 (Trailing whitespace in a string)
- -- luacheck: ignore 611 (Line contains only whitespaces)
- it('is working', function()
- insert('dummy text')
-
- feed_command('set wildchar=^E')
- feed_command('10new')
- feed_command('vsp')
- feed_command('vert resize 20')
- feed_command([[put =\"\tabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOP\"]])
- feed_command('set ts=4 sw=4 sts=4 breakindent')
- feed_command('fu! ScreenChar(line, width)')
- feed_command(' let c=""')
- feed_command(' for i in range(1,a:width)')
- feed_command(' let c.=nr2char(screenchar(a:line, i))')
- feed_command(' endfor')
- feed_command([[ let c.="\n"]])
- feed_command(' for i in range(1,a:width)')
- feed_command(' let c.=nr2char(screenchar(a:line+1, i))')
- feed_command(' endfor')
- feed_command([[ let c.="\n"]])
- feed_command(' for i in range(1,a:width)')
- feed_command(' let c.=nr2char(screenchar(a:line+2, i))')
- feed_command(' endfor')
- feed_command(' return c')
- feed_command('endfu')
- feed_command('fu DoRecordScreen()')
- feed_command(' wincmd l')
- feed_command([[ $put =printf(\"\n%s\", g:test)]])
- feed_command(' $put =g:line1')
- feed_command(' wincmd p')
- feed_command('endfu')
- feed_command('set briopt=min:0')
- feed_command('let g:test="Test 1: Simple breakindent"')
- feed_command('let line1=ScreenChar(line("."),8)')
- feed_command('call DoRecordScreen()')
- feed_command('let g:test="Test 2: Simple breakindent + sbr=>>"')
- feed_command('set sbr=>>')
- feed_command('let line1=ScreenChar(line("."),8)')
- feed_command('call DoRecordScreen()')
- feed_command('let g:test ="Test 3: Simple breakindent + briopt:sbr"')
- feed_command('set briopt=sbr,min:0 sbr=++')
- feed_command('let line1=ScreenChar(line("."),8)')
- feed_command('call DoRecordScreen()')
- feed_command('let g:test ="Test 4: Simple breakindent + min width: 18"')
- feed_command('set sbr= briopt=min:18')
- feed_command('let line1=ScreenChar(line("."),8)')
- feed_command('call DoRecordScreen()')
- feed_command('let g:test =" Test 5: Simple breakindent + shift by 2"')
- feed_command('set briopt=shift:2,min:0')
- feed_command('let line1=ScreenChar(line("."),8)')
- feed_command('call DoRecordScreen()')
- feed_command('let g:test=" Test 6: Simple breakindent + shift by -1"')
- feed_command('set briopt=shift:-1,min:0')
- feed_command('let line1=ScreenChar(line("."),8)')
- feed_command('call DoRecordScreen()')
- feed_command('let g:test=" Test 7: breakindent + shift by +1 + nu + sbr=? briopt:sbr"')
- feed_command('set briopt=shift:1,sbr,min:0 nu sbr=? nuw=4')
- feed_command('let line1=ScreenChar(line("."),10)')
- feed_command('call DoRecordScreen()')
- feed_command('let g:test=" Test 8: breakindent + shift:1 + nu + sbr=# list briopt:sbr"')
- feed_command('set briopt=shift:1,sbr,min:0 nu sbr=# list lcs&vi')
- feed_command('let line1=ScreenChar(line("."),10)')
- feed_command('call DoRecordScreen()')
- feed_command([[let g:test=" Test 9: breakindent + shift by +1 + 'nu' + sbr=# list"]])
- feed_command('set briopt-=sbr')
- feed_command('let line1=ScreenChar(line("."),10)')
- feed_command('call DoRecordScreen()')
- feed_command([[let g:test=" Test 10: breakindent + shift by +1 + 'nu' + sbr=~ cpo+=n"]])
- feed_command('set cpo+=n sbr=~ nu nuw=4 nolist briopt=sbr,min:0')
- feed_command('let line1=ScreenChar(line("."),10)')
- feed_command('call DoRecordScreen()')
- feed_command('wincmd p')
- feed_command([[let g:test="\n Test 11: strdisplaywidth when breakindent is on"]])
- feed_command('set cpo-=n sbr=>> nu nuw=4 nolist briopt= ts=4')
- -- Skip leading tab when calculating text width.
- feed_command('let text=getline(2)')
- -- Text wraps 3 times.
- feed_command('let width = strlen(text[1:])+indent(2)*4+strlen(&sbr)*3')
- feed_command('$put =g:test')
- feed_command([[$put =printf(\"strdisplaywidth: %d == calculated: %d\", strdisplaywidth(text), width)]])
- feed_command([[let g:str="\t\t\t\t\t{"]])
- feed_command('let g:test=" Test 12: breakindent + long indent"')
- feed_command('wincmd p')
- feed_command('set all& breakindent linebreak briopt=min:10 nu numberwidth=3 ts=4')
- feed_command('$put =g:str')
- feed('zt')
- feed_command('let line1=ScreenChar(1,10)')
- feed_command('wincmd p')
- feed_command('call DoRecordScreen()')
-
- -- Test, that the string " a\tb\tc\td\te" is correctly displayed in a
- -- 20 column wide window (see bug report
- -- https://groups.google.com/d/msg/vim_dev/ZOdg2mc9c9Y/TT8EhFjEy0IJ ).
- feed_command('only')
- feed_command('vert 20new')
- feed_command('set all& breakindent briopt=min:10')
- feed_command([[call setline(1, [" a\tb\tc\td\te", " z y x w v"])]])
- feed_command([[/^\s*a]])
- feed('fbgjyl')
- feed_command('let line1 = @0')
- feed_command([[?^\s*z]])
- feed('fygjyl')
- feed_command('let line2 = @0')
- feed_command('quit!')
- feed_command([[$put ='Test 13: breakindent with wrapping Tab']])
- feed_command('$put =line1')
- feed_command('$put =line2')
-
- feed_command('let g:test="Test 14: breakindent + visual blockwise delete #1"')
- feed_command('set all& breakindent shada+=nX-test-breakindent.shada')
- feed_command('30vnew')
- feed_command('normal! 3a1234567890')
- feed_command('normal! a abcde')
- feed_command([[exec "normal! 0\<C-V>tex"]])
- feed_command('let line1=ScreenChar(line("."),8)')
- feed_command('call DoRecordScreen()')
-
- feed_command('let g:test="Test 15: breakindent + visual blockwise delete #2"')
- feed_command('%d')
- feed_command('normal! 4a1234567890')
- feed_command([[exec "normal! >>\<C-V>3f0x"]])
- feed_command('let line1=ScreenChar(line("."),20)')
- feed_command('call DoRecordScreen()')
- feed_command('quit!')
-
- -- Assert buffer contents.
- expect([[
-
- abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOP
-
- Test 1: Simple breakindent
- abcd
- qrst
- GHIJ
-
- Test 2: Simple breakindent + sbr=>>
- abcd
- >>qr
- >>EF
-
- Test 3: Simple breakindent + briopt:sbr
- abcd
- ++ qrst
- ++ GHIJ
-
- Test 4: Simple breakindent + min width: 18
- abcd
- qrstuv
- IJKLMN
-
- Test 5: Simple breakindent + shift by 2
- abcd
- qr
- EF
-
- Test 6: Simple breakindent + shift by -1
- abcd
- qrstu
- HIJKL
-
- Test 7: breakindent + shift by +1 + nu + sbr=? briopt:sbr
- 2 ab
- ? m
- ? x
-
- Test 8: breakindent + shift:1 + nu + sbr=# list briopt:sbr
- 2 ^Iabcd
- # opq
- # BCD
-
- Test 9: breakindent + shift by +1 + 'nu' + sbr=# list
- 2 ^Iabcd
- #op
- #AB
-
- Test 10: breakindent + shift by +1 + 'nu' + sbr=~ cpo+=n
- 2 ab
- ~ mn
- ~ yz
-
- Test 11: strdisplaywidth when breakindent is on
- strdisplaywidth: 46 == calculated: 64
- {
-
- Test 12: breakindent + long indent
- 56
-
- ~
- Test 13: breakindent with wrapping Tab
- d
- w
-
- Test 14: breakindent + visual blockwise delete #1
- e
- ~
- ~
-
- Test 15: breakindent + visual blockwise delete #2
- 1234567890
- ~
- ~ ]])
- end)
-end)
diff --git a/test/functional/legacy/delete_spec.lua b/test/functional/legacy/delete_spec.lua
index 9ea3269828..f2ced8942d 100644
--- a/test/functional/legacy/delete_spec.lua
+++ b/test/functional/legacy/delete_spec.lua
@@ -56,7 +56,7 @@ describe('Test for delete()', function()
endif
]])
if eval('v:shell_error') ~= 0 then
- pending('Cannot create symlink', function()end)
+ pending('Cannot create symlink')
end
-- Delete the link, not the file
eq(0, eval("delete('Xlink')"))
diff --git a/test/functional/legacy/eval_spec.lua b/test/functional/legacy/eval_spec.lua
index c5d38d6d05..4198ea8bfe 100644
--- a/test/functional/legacy/eval_spec.lua
+++ b/test/functional/legacy/eval_spec.lua
@@ -360,7 +360,7 @@ describe('eval', function()
abcD3b]])
-- From now on we delete the buffer contents after each expect() to make
- -- the next expect() easier to write. This is neccessary because null
+ -- the next expect() easier to write. This is necessary because null
-- bytes on a line by itself don't play well together with the dedent
-- function used in expect().
command('%delete')
@@ -416,7 +416,7 @@ describe('eval', function()
' abcD3b50')
end)
- -- The tests for setting lists with NLs are split into seperate it() blocks
+ -- The tests for setting lists with NLs are split into separate it() blocks
-- to make the expect() calls easier to write. Otherwise the null byte can
-- make trouble on a line on its own.
it('setting lists with NLs with setreg(), part 1', function()
diff --git a/test/functional/legacy/expand_spec.lua b/test/functional/legacy/expand_spec.lua
index 1b735080f4..f238128b31 100644
--- a/test/functional/legacy/expand_spec.lua
+++ b/test/functional/legacy/expand_spec.lua
@@ -70,6 +70,40 @@ describe('expand file name', function()
call assert_match('\~', expand('%:p'))
bwipe!
endfunc
+
+ func Test_expandcmd()
+ let $FOO = 'Test'
+ call assert_equal('e x/Test/y', expandcmd('e x/$FOO/y'))
+ unlet $FOO
+
+ new
+ edit Xfile1
+ call assert_equal('e Xfile1', expandcmd('e %'))
+ edit Xfile2
+ edit Xfile1
+ call assert_equal('e Xfile2', expandcmd('e #'))
+ edit Xfile2
+ edit Xfile3
+ edit Xfile4
+ let bnum = bufnr('Xfile2')
+ call assert_equal('e Xfile2', expandcmd('e #' . bnum))
+ call setline('.', 'Vim!@#')
+ call assert_equal('e Vim', expandcmd('e <cword>'))
+ call assert_equal('e Vim!@#', expandcmd('e <cWORD>'))
+ enew!
+ edit Xfile.java
+ call assert_equal('e Xfile.py', expandcmd('e %:r.py'))
+ call assert_equal('make abc.java', expandcmd('make abc.%:e'))
+ call assert_equal('make Xabc.java', expandcmd('make %:s?file?abc?'))
+ edit a1a2a3.rb
+ call assert_equal('make b1b2b3.rb a1a2a3 Xfile.o', expandcmd('make %:gs?a?b? %< #<.o'))
+
+ call assert_fails('call expandcmd("make <afile>")', 'E495:')
+ call assert_fails('call expandcmd("make <afile>")', 'E495:')
+ enew
+ call assert_fails('call expandcmd("make %")', 'E499:')
+ close
+ endfunc
]])
end)
@@ -87,4 +121,9 @@ describe('expand file name', function()
call('Test_expand_tilde_filename')
expected_empty()
end)
+
+ it('works with expandcmd()', function()
+ call('Test_expandcmd')
+ expected_empty()
+ end)
end)
diff --git a/test/functional/legacy/memory_usage_spec.lua b/test/functional/legacy/memory_usage_spec.lua
new file mode 100644
index 0000000000..251e6a5ea4
--- /dev/null
+++ b/test/functional/legacy/memory_usage_spec.lua
@@ -0,0 +1,169 @@
+local helpers = require('test.functional.helpers')(after_each)
+local clear = helpers.clear
+local eval = helpers.eval
+local eq = helpers.eq
+local feed_command = helpers.feed_command
+local iswin = helpers.iswin
+local retry = helpers.retry
+local ok = helpers.ok
+local source = helpers.source
+local wait = helpers.wait
+local uname = helpers.uname
+local load_adjust = helpers.load_adjust
+
+local monitor_memory_usage = {
+ memory_usage = function(self)
+ local handle
+ if iswin() then
+ handle = io.popen('wmic process where processid=' ..self.pid..' get WorkingSetSize')
+ else
+ handle = io.popen('ps -o rss= -p '..self.pid)
+ end
+ return tonumber(handle:read('*a'):match('%d+'))
+ end,
+ op = function(self)
+ retry(nil, 10000, function()
+ local val = self.memory_usage(self)
+ if self.max < val then
+ self.max = val
+ end
+ table.insert(self.hist, val)
+ ok(#self.hist > 20)
+ local result = {}
+ for key,value in ipairs(self.hist) do
+ if value ~= self.hist[key + 1] then
+ table.insert(result, value)
+ end
+ end
+ table.remove(self.hist, 1)
+ self.last = self.hist[#self.hist]
+ eq(#result, 1)
+ end)
+ end,
+ dump = function(self)
+ return 'max: '..self.max ..', last: '..self.last
+ end,
+ monitor_memory_usage = function(self, pid)
+ local obj = {
+ pid = pid,
+ max = 0,
+ last = 0,
+ hist = {},
+ }
+ setmetatable(obj, { __index = self })
+ obj:op()
+ return obj
+ end
+}
+setmetatable(monitor_memory_usage,
+{__call = function(self, pid)
+ return monitor_memory_usage.monitor_memory_usage(self, pid)
+end})
+
+describe('memory usage', function()
+ local function check_result(tbl, status, result)
+ if not status then
+ print('')
+ for key, val in pairs(tbl) do
+ print(key, val:dump())
+ end
+ error(result)
+ end
+ end
+
+ local function isasan()
+ local version = eval('execute("version")')
+ return version:match('-fsanitize=[a-z,]*address')
+ end
+
+ before_each(clear)
+
+ --[[
+ Case: if a local variable captures a:000, funccall object will be free
+ just after it finishes.
+ ]]--
+ it('function capture vargs', function()
+ if isasan() then
+ pending('ASAN build is difficult to estimate memory usage')
+ end
+ if iswin() and eval("executable('wmic')") == 0 then
+ pending('missing "wmic" command')
+ elseif eval("executable('ps')") == 0 then
+ pending('missing "ps" command')
+ end
+
+ local pid = eval('getpid()')
+ local before = monitor_memory_usage(pid)
+ source([[
+ func s:f(...)
+ let x = a:000
+ endfunc
+ for _ in range(10000)
+ call s:f(0)
+ endfor
+ ]])
+ wait()
+ local after = monitor_memory_usage(pid)
+ -- Estimate the limit of max usage as 2x initial usage.
+ -- The lower limit can fluctuate a bit, use 97%.
+ check_result({before=before, after=after},
+ pcall(ok, before.last * 97 / 100 < after.max))
+ check_result({before=before, after=after},
+ pcall(ok, before.last * 2 > after.max))
+ -- In this case, garbage collecting is not needed.
+ -- The value might fluctuate a bit, allow for 3% tolerance below and 5% above.
+ -- Based on various test runs.
+ local lower = after.last * 97 / 100
+ local upper = after.last * 105 / 100
+ check_result({before=before, after=after}, pcall(ok, lower < after.max))
+ check_result({before=before, after=after}, pcall(ok, after.max < upper))
+ end)
+
+ --[[
+ Case: if a local variable captures l: dict, funccall object will not be
+ free until garbage collector runs, but after that memory usage doesn't
+ increase so much even when rerun Xtest.vim since system memory caches.
+ ]]--
+ it('function capture lvars', function()
+ if isasan() then
+ pending('ASAN build is difficult to estimate memory usage')
+ end
+ if iswin() and eval("executable('wmic')") == 0 then
+ pending('missing "wmic" command')
+ elseif eval("executable('ps')") == 0 then
+ pending('missing "ps" command')
+ end
+
+ local pid = eval('getpid()')
+ local before = monitor_memory_usage(pid)
+ local fname = source([[
+ if !exists('s:defined_func')
+ func s:f()
+ let x = l:
+ endfunc
+ endif
+ let s:defined_func = 1
+ for _ in range(10000)
+ call s:f()
+ endfor
+ ]])
+ wait()
+ local after = monitor_memory_usage(pid)
+ for _ = 1, 3 do
+ feed_command('so '..fname)
+ wait()
+ end
+ local last = monitor_memory_usage(pid)
+ -- The usage may be a bit less than the last value, use 80%.
+ -- Allow for 20% tolerance at the upper limit. That's very permissive, but
+ -- otherwise the test fails sometimes. On Sourcehut CI with FreeBSD we need to
+ -- be even more permissive.
+ local upper_multiplier = uname() == 'freebsd' and 15 or 12
+ local lower = before.last * 8 / 10
+ local upper = load_adjust((after.max + (after.last - before.last)) * upper_multiplier / 10)
+ check_result({before=before, after=after, last=last},
+ pcall(ok, lower < last.last))
+ check_result({before=before, after=after, last=last},
+ pcall(ok, last.last < upper))
+ end)
+end)
diff --git a/test/functional/legacy/prompt_buffer_spec.lua b/test/functional/legacy/prompt_buffer_spec.lua
new file mode 100644
index 0000000000..513be807be
--- /dev/null
+++ b/test/functional/legacy/prompt_buffer_spec.lua
@@ -0,0 +1,153 @@
+local helpers = require('test.functional.helpers')(after_each)
+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
+
+describe('prompt buffer', function()
+ local screen
+
+ before_each(function()
+ clear()
+ screen = Screen.new(25, 10)
+ screen:attach()
+ source([[
+ func TextEntered(text)
+ if a:text == "exit"
+ set nomodified
+ stopinsert
+ close
+ else
+ call append(line("$") - 1, 'Command: "' . a:text . '"')
+ set nomodfied
+ call timer_start(20, {id -> TimerFunc(a:text)})
+ endif
+ endfunc
+
+ func TimerFunc(text)
+ call append(line("$") - 1, 'Result: "' . a:text .'"')
+ 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'))")
+ end)
+
+ after_each(function()
+ screen:detach()
+ end)
+
+ it('works', function()
+ screen:expect([[
+ ^ |
+ ~ |
+ ~ |
+ ~ |
+ [Prompt] |
+ other buffer |
+ ~ |
+ ~ |
+ ~ |
+ |
+ ]])
+ feed("i")
+ feed("hello\n")
+ screen:expect([[
+ % hello |
+ Command: "hello" |
+ Result: "hello" |
+ % ^ |
+ [Prompt] [+] |
+ other buffer |
+ ~ |
+ ~ |
+ ~ |
+ |
+ ]])
+ feed("exit\n")
+ screen:expect([[
+ ^other buffer |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ |
+ ]])
+ end)
+
+ it('editing', function()
+ screen:expect([[
+ ^ |
+ ~ |
+ ~ |
+ ~ |
+ [Prompt] |
+ other buffer |
+ ~ |
+ ~ |
+ ~ |
+ |
+ ]])
+ feed("i")
+ feed("hello<BS><BS>")
+ screen:expect([[
+ % hel^ |
+ ~ |
+ ~ |
+ ~ |
+ [Prompt] [+] |
+ other buffer |
+ ~ |
+ ~ |
+ ~ |
+ |
+ ]])
+ feed("<Left><Left><Left><BS>-")
+ screen:expect([[
+ % -^hel |
+ ~ |
+ ~ |
+ ~ |
+ [Prompt] [+] |
+ other buffer |
+ ~ |
+ ~ |
+ ~ |
+ |
+ ]])
+ feed("<End>x")
+ screen:expect([[
+ % -helx^ |
+ ~ |
+ ~ |
+ ~ |
+ [Prompt] [+] |
+ other buffer |
+ ~ |
+ ~ |
+ ~ |
+ |
+ ]])
+ feed("<C-U>exit\n")
+ screen:expect([[
+ ^other buffer |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ |
+ ]])
+ end)
+
+end)
diff --git a/test/functional/legacy/search_spec.lua b/test/functional/legacy/search_spec.lua
index 3ed06a22e7..a207b176d3 100644
--- a/test/functional/legacy/search_spec.lua
+++ b/test/functional/legacy/search_spec.lua
@@ -17,7 +17,10 @@ describe('search cmdline', function()
screen = Screen.new(20, 3)
screen:attach()
screen:set_default_attr_ids({
- inc = {reverse = true}
+ inc = {reverse = true},
+ err = { foreground = Screen.colors.Grey100, background = Screen.colors.Red },
+ more = { bold = true, foreground = Screen.colors.SeaGreen4 },
+ tilde = { bold = true, foreground = Screen.colors.Blue1 },
})
end)
@@ -404,15 +407,7 @@ describe('search cmdline', function()
end)
it('keeps the view after deleting a char from the search', function()
- screen:detach()
- screen = Screen.new(20, 6)
- screen:attach()
- screen:set_default_attr_ids({
- inc = {reverse = true}
- })
- screen:set_default_attr_ignore({
- {bold=true, reverse=true}, {bold=true, foreground=Screen.colors.Blue1}
- })
+ screen:try_resize(20, 6)
tenlines()
feed('/foo')
@@ -448,14 +443,7 @@ describe('search cmdline', function()
end)
it('restores original view after failed search', function()
- screen:detach()
- screen = Screen.new(40, 3)
- screen:attach()
- screen:set_default_attr_ids({
- inc = {reverse = true},
- err = { foreground = Screen.colors.Grey100, background = Screen.colors.Red },
- more = { bold = true, foreground = Screen.colors.SeaGreen4 },
- })
+ screen:try_resize(40, 3)
tenlines()
feed('0')
feed('/foo')
@@ -484,15 +472,7 @@ describe('search cmdline', function()
it("CTRL-G with 'incsearch' and ? goes in the right direction", function()
-- oldtest: Test_search_cmdline4().
- screen:detach()
- screen = Screen.new(40, 4)
- screen:attach()
- screen:set_default_attr_ids({
- inc = {reverse = true},
- err = { foreground = Screen.colors.Grey100, background = Screen.colors.Red },
- more = { bold = true, foreground = Screen.colors.SeaGreen4 },
- tilde = { bold = true, foreground = Screen.colors.Blue1 },
- })
+ screen:try_resize(40, 4)
command('enew!')
funcs.setline(1, {' 1 the first', ' 2 the second', ' 3 the third'})
command('set laststatus=0 shortmess+=s')
diff --git a/test/functional/lua/api_spec.lua b/test/functional/lua/api_spec.lua
index b1dc5c07fd..896554f7a3 100644
--- a/test/functional/lua/api_spec.lua
+++ b/test/functional/lua/api_spec.lua
@@ -39,7 +39,7 @@ describe('luaeval(vim.api.…)', function()
eq({false, 'Argument "pos" must be a [row, col] array'},
funcs.luaeval('{pcall(vim.api.nvim_win_set_cursor, 0, {1, 2, 3})}'))
-- Used to produce a memory leak due to a bug in nvim_win_set_cursor
- eq({false, 'Invalid window id'},
+ eq({false, 'Invalid window id: -1'},
funcs.luaeval('{pcall(vim.api.nvim_win_set_cursor, -1, {1, 2, 3})}'))
end)
@@ -155,41 +155,41 @@ describe('luaeval(vim.api.…)', function()
it('errors out correctly when working with API', function()
-- Conversion errors
- eq('Vim(call):E5108: Error while calling lua chunk for luaeval(): [string "<VimL compiled string>"]:1: Cannot convert given lua type',
+ eq('Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Cannot convert given lua type',
exc_exec([[call luaeval("vim.api.nvim__id(vim.api.nvim__id)")]]))
- eq('Vim(call):E5108: Error while calling lua chunk for luaeval(): [string "<VimL compiled string>"]:1: Cannot convert given lua table',
+ eq('Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Cannot convert given lua table',
exc_exec([[call luaeval("vim.api.nvim__id({1, foo=42})")]]))
- eq('Vim(call):E5108: Error while calling lua chunk for luaeval(): [string "<VimL compiled string>"]:1: Cannot convert given lua type',
+ eq('Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Cannot convert given lua type',
exc_exec([[call luaeval("vim.api.nvim__id({42, vim.api.nvim__id})")]]))
-- Errors in number of arguments
- eq('Vim(call):E5108: Error while calling lua chunk for luaeval(): [string "<VimL compiled string>"]:1: Expected 1 argument',
+ eq('Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Expected 1 argument',
exc_exec([[call luaeval("vim.api.nvim__id()")]]))
- eq('Vim(call):E5108: Error while calling lua chunk for luaeval(): [string "<VimL compiled string>"]:1: Expected 1 argument',
+ eq('Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Expected 1 argument',
exc_exec([[call luaeval("vim.api.nvim__id(1, 2)")]]))
- eq('Vim(call):E5108: Error while calling lua chunk for luaeval(): [string "<VimL compiled string>"]:1: Expected 2 arguments',
+ eq('Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Expected 2 arguments',
exc_exec([[call luaeval("vim.api.nvim_set_var(1, 2, 3)")]]))
-- Error in argument types
- eq('Vim(call):E5108: Error while calling lua chunk for luaeval(): [string "<VimL compiled string>"]:1: Expected lua string',
+ eq('Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Expected lua string',
exc_exec([[call luaeval("vim.api.nvim_set_var(1, 2)")]]))
- eq('Vim(call):E5108: Error while calling lua chunk for luaeval(): [string "<VimL compiled string>"]:1: Expected lua number',
+ eq('Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Expected lua number',
exc_exec([[call luaeval("vim.api.nvim_buf_get_lines(0, 'test', 1, false)")]]))
- eq('Vim(call):E5108: Error while calling lua chunk for luaeval(): [string "<VimL compiled string>"]:1: Number is not integral',
+ eq('Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Number is not integral',
exc_exec([[call luaeval("vim.api.nvim_buf_get_lines(0, 1.5, 1, false)")]]))
- eq('Vim(call):E5108: Error while calling lua chunk for luaeval(): [string "<VimL compiled string>"]:1: Expected lua table',
+ eq('Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Expected lua table',
exc_exec([[call luaeval("vim.api.nvim__id_float('test')")]]))
- eq('Vim(call):E5108: Error while calling lua chunk for luaeval(): [string "<VimL compiled string>"]:1: Unexpected type',
+ eq('Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Unexpected type',
exc_exec([[call luaeval("vim.api.nvim__id_float({[vim.type_idx]=vim.types.dictionary})")]]))
- eq('Vim(call):E5108: Error while calling lua chunk for luaeval(): [string "<VimL compiled string>"]:1: Expected lua table',
+ eq('Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Expected lua table',
exc_exec([[call luaeval("vim.api.nvim__id_array(1)")]]))
- eq('Vim(call):E5108: Error while calling lua chunk for luaeval(): [string "<VimL compiled string>"]:1: Unexpected type',
+ eq('Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Unexpected type',
exc_exec([[call luaeval("vim.api.nvim__id_array({[vim.type_idx]=vim.types.dictionary})")]]))
- eq('Vim(call):E5108: Error while calling lua chunk for luaeval(): [string "<VimL compiled string>"]:1: Expected lua table',
+ eq('Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Expected lua table',
exc_exec([[call luaeval("vim.api.nvim__id_dictionary(1)")]]))
- eq('Vim(call):E5108: Error while calling lua chunk for luaeval(): [string "<VimL compiled string>"]:1: Unexpected type',
+ eq('Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Unexpected type',
exc_exec([[call luaeval("vim.api.nvim__id_dictionary({[vim.type_idx]=vim.types.array})")]]))
-- TODO: check for errors with Tabpage argument
-- TODO: check for errors with Window argument
diff --git a/test/functional/lua/buffer_updates_spec.lua b/test/functional/lua/buffer_updates_spec.lua
index 990cb97fec..77f8189bb9 100644
--- a/test/functional/lua/buffer_updates_spec.lua
+++ b/test/functional/lua/buffer_updates_spec.lua
@@ -98,7 +98,7 @@ describe('lua: buffer event callbacks', function()
command('undo')
-- plugins can opt in to receive changedtick events, or choose
- -- to only recieve actual changes.
+ -- to only receive actual changes.
check_events({{ "test1", "lines", 1, tick, 3, 4, 5, 13 },
{ "test2", "lines", 1, tick, 3, 4, 5, 13 },
{ "test2", "changedtick", 1, tick+1 } })
@@ -111,7 +111,7 @@ describe('lua: buffer event callbacks', function()
tick = tick + 1
-- plugins can opt in to receive changedtick events, or choose
- -- to only recieve actual changes.
+ -- to only receive actual changes.
check_events({{ "test1", "lines", 1, tick, 6, 7, 9, 16 },
{ "test2", "lines", 1, tick, 6, 7, 9, 16 }})
@@ -203,4 +203,17 @@ describe('lua: buffer event callbacks', function()
{ "test1", "lines", 1, tick+1, 5, 6, 5, 27, 20, 20 }}, exec_lua("return get_events(...)" ))
end)
+ it('has valid cursor position while shifting', function()
+ meths.buf_set_lines(0, 0, -1, true, {'line1'})
+ exec_lua([[
+ vim.api.nvim_buf_attach(0, false, {
+ on_lines = function()
+ vim.api.nvim_set_var('listener_cursor_line', vim.api.nvim_win_get_cursor(0)[1])
+ end,
+ })
+ ]])
+ feed('>>')
+ eq(1, meths.get_var('listener_cursor_line'))
+ end)
+
end)
diff --git a/test/functional/lua/commands_spec.lua b/test/functional/lua/commands_spec.lua
index 26dcbe0534..cbc3aee557 100644
--- a/test/functional/lua/commands_spec.lua
+++ b/test/functional/lua/commands_spec.lua
@@ -13,6 +13,7 @@ local source = helpers.source
local dedent = helpers.dedent
local command = helpers.command
local exc_exec = helpers.exc_exec
+local pcall_err = helpers.pcall_err
local write_file = helpers.write_file
local redir_exec = helpers.redir_exec
local curbufmeths = helpers.curbufmeths
@@ -42,16 +43,16 @@ describe(':lua command', function()
eq({'', 'ETTS', 'TTSE', 'STTE'}, curbufmeths.get_lines(0, 100, false))
end)
it('throws catchable errors', function()
- eq([[Vim(lua):E5104: Error while creating lua chunk: [string "<VimL compiled string>"]:1: unexpected symbol near ')']],
- exc_exec('lua ()'))
- eq([[Vim(lua):E5105: Error while calling lua chunk: [string "<VimL compiled string>"]:1: TEST]],
+ eq([[Vim(lua):E5107: Error loading lua [string ":lua"]:1: unexpected symbol near ')']],
+ pcall_err(command, 'lua ()'))
+ eq([[Vim(lua):E5108: Error executing lua [string ":lua"]:1: TEST]],
exc_exec('lua error("TEST")'))
- eq([[Vim(lua):E5105: Error while calling lua chunk: [string "<VimL compiled string>"]:1: Invalid buffer id]],
+ eq([[Vim(lua):E5108: Error executing lua [string ":lua"]:1: Invalid buffer id: -10]],
exc_exec('lua vim.api.nvim_buf_set_lines(-10, 1, 1, false, {"TEST"})'))
eq({''}, curbufmeths.get_lines(0, 100, false))
end)
it('works with NULL errors', function()
- eq([=[Vim(lua):E5105: Error while calling lua chunk: [NULL]]=],
+ eq([=[Vim(lua):E5108: Error executing lua [NULL]]=],
exc_exec('lua error(nil)'))
end)
it('accepts embedded NLs without heredoc', function()
@@ -74,7 +75,7 @@ describe(':lua command', function()
it('works with long strings', function()
local s = ('x'):rep(100500)
- eq('\nE5104: Error while creating lua chunk: [string "<VimL compiled string>"]:1: unfinished string near \'<eof>\'', redir_exec(('lua vim.api.nvim_buf_set_lines(1, 1, 2, false, {"%s})'):format(s)))
+ eq('\nE5107: Error loading lua [string ":lua"]:1: unfinished string near \'<eof>\'', redir_exec(('lua vim.api.nvim_buf_set_lines(1, 1, 2, false, {"%s})'):format(s)))
eq({''}, curbufmeths.get_lines(0, -1, false))
eq('', redir_exec(('lua vim.api.nvim_buf_set_lines(1, 1, 2, false, {"%s"})'):format(s)))
@@ -82,7 +83,7 @@ describe(':lua command', function()
end)
it('can show multiline error messages', function()
- local screen = Screen.new(50,10)
+ local screen = Screen.new(40,10)
screen:attach()
screen:set_default_attr_ids({
[1] = {bold = true, foreground = Screen.colors.Blue1},
@@ -92,51 +93,51 @@ describe(':lua command', function()
})
feed(':lua error("fail\\nmuch error\\nsuch details")<cr>')
- screen:expect([[
- |
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {2: }|
- {3:E5105: Error while calling lua chunk: [string "<Vi}|
- {3:mL compiled string>"]:1: fail} |
- {3:much error} |
- {3:such details} |
- {4:Press ENTER or type command to continue}^ |
- ]])
+ screen:expect{grid=[[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {2: }|
+ {3:E5108: Error executing lua [string ":lua}|
+ {3:"]:1: fail} |
+ {3:much error} |
+ {3:such details} |
+ {4:Press ENTER or type command to continue}^ |
+ ]]}
feed('<cr>')
- screen:expect([[
- ^ |
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- |
- ]])
- eq('E5105: Error while calling lua chunk: [string "<VimL compiled string>"]:1: fail\nmuch error\nsuch details', eval('v:errmsg'))
+ screen:expect{grid=[[
+ ^ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+ eq('E5108: Error executing lua [string ":lua"]:1: fail\nmuch error\nsuch details', eval('v:errmsg'))
local status, err = pcall(command,'lua error("some error\\nin a\\nAPI command")')
- local expected = 'Vim(lua):E5105: Error while calling lua chunk: [string "<VimL compiled string>"]:1: some error\nin a\nAPI command'
+ local expected = 'Vim(lua):E5108: Error executing lua [string ":lua"]:1: some error\nin a\nAPI command'
eq(false, status)
eq(expected, string.sub(err, -string.len(expected)))
feed(':messages<cr>')
- screen:expect([[
- |
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {2: }|
- {3:E5105: Error while calling lua chunk: [string "<Vi}|
- {3:mL compiled string>"]:1: fail} |
- {3:much error} |
- {3:such details} |
- {4:Press ENTER or type command to continue}^ |
- ]])
+ screen:expect{grid=[[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {2: }|
+ {3:E5108: Error executing lua [string ":lua}|
+ {3:"]:1: fail} |
+ {3:much error} |
+ {3:such details} |
+ {4:Press ENTER or type command to continue}^ |
+ ]]}
end)
end)
@@ -167,13 +168,13 @@ describe(':luado command', function()
eq({''}, curbufmeths.get_lines(0, -1, false))
end)
it('fails on errors', function()
- eq([[Vim(luado):E5109: Error while creating lua chunk: [string "<VimL compiled string>"]:1: unexpected symbol near ')']],
+ eq([[Vim(luado):E5109: Error loading lua: [string ":luado"]:1: unexpected symbol near ')']],
exc_exec('luado ()'))
- eq([[Vim(luado):E5111: Error while calling lua function: [string "<VimL compiled string>"]:1: attempt to perform arithmetic on global 'liness' (a nil value)]],
+ eq([[Vim(luado):E5111: Error calling lua: [string ":luado"]:1: attempt to perform arithmetic on global 'liness' (a nil value)]],
exc_exec('luado return liness + 1'))
end)
it('works with NULL errors', function()
- eq([=[Vim(luado):E5111: Error while calling lua function: [NULL]]=],
+ eq([=[Vim(luado):E5111: Error calling lua: [NULL]]=],
exc_exec('luado error(nil)'))
end)
it('fails in sandbox when needed', function()
@@ -185,7 +186,7 @@ describe(':luado command', function()
it('works with long strings', function()
local s = ('x'):rep(100500)
- eq('\nE5109: Error while creating lua chunk: [string "<VimL compiled string>"]:1: unfinished string near \'<eof>\'', redir_exec(('luado return "%s'):format(s)))
+ eq('\nE5109: Error loading lua: [string ":luado"]:1: unfinished string near \'<eof>\'', redir_exec(('luado return "%s'):format(s)))
eq({''}, curbufmeths.get_lines(0, -1, false))
eq('', redir_exec(('luado return "%s"'):format(s)))
diff --git a/test/functional/lua/luaeval_spec.lua b/test/functional/lua/luaeval_spec.lua
index 760105df6b..75966393b1 100644
--- a/test/functional/lua/luaeval_spec.lua
+++ b/test/functional/lua/luaeval_spec.lua
@@ -1,13 +1,16 @@
-- Test suite for testing luaeval() function
local helpers = require('test.functional.helpers')(after_each)
+local Screen = require('test.functional.ui.screen')
-local redir_exec = helpers.redir_exec
+local pcall_err = helpers.pcall_err
local exc_exec = helpers.exc_exec
+local exec_lua = helpers.exec_lua
local command = helpers.command
local meths = helpers.meths
local funcs = helpers.funcs
local clear = helpers.clear
local eval = helpers.eval
+local feed = helpers.feed
local NIL = helpers.NIL
local eq = helpers.eq
@@ -184,23 +187,210 @@ describe('luaeval()', function()
it('issues an error in some cases', function()
eq("Vim(call):E5100: Cannot convert given lua table: table should either have a sequence of positive integer keys or contain only string keys",
exc_exec('call luaeval("{1, foo=2}")'))
- eq("Vim(call):E5101: Cannot convert given lua type",
- exc_exec('call luaeval("vim.api.nvim_buf_get_lines")'))
- startswith("Vim(call):E5107: Error while creating lua chunk for luaeval(): ",
+
+ startswith("Vim(call):E5107: Error loading lua [string \"luaeval()\"]:",
exc_exec('call luaeval("1, 2, 3")'))
- startswith("Vim(call):E5108: Error while calling lua chunk for luaeval(): ",
+ startswith("Vim(call):E5108: Error executing lua [string \"luaeval()\"]:",
exc_exec('call luaeval("(nil)()")'))
- eq("Vim(call):E5101: Cannot convert given lua type",
- exc_exec('call luaeval("{42, vim.api}")'))
- eq("Vim(call):E5101: Cannot convert given lua type",
- exc_exec('call luaeval("{foo=42, baz=vim.api}")'))
- -- The following should not crash: conversion error happens inside
- eq("Vim(call):E5101: Cannot convert given lua type",
- exc_exec('call luaeval("vim.api")'))
- -- The following should not show internal error
- eq("\nE5101: Cannot convert given lua type\n0",
- redir_exec('echo luaeval("vim.api")'))
+ end)
+
+ it('should handle sending lua functions to viml', function()
+ eq(true, exec_lua [[
+ can_pass_lua_callback_to_vim_from_lua_result = nil
+
+ vim.fn.call(function()
+ can_pass_lua_callback_to_vim_from_lua_result = true
+ end, {})
+
+ return can_pass_lua_callback_to_vim_from_lua_result
+ ]])
+ end)
+
+ it('run functions even in timers', function()
+ eq(true, exec_lua [[
+ can_pass_lua_callback_to_vim_from_lua_result = nil
+
+ vim.fn.timer_start(50, function()
+ can_pass_lua_callback_to_vim_from_lua_result = true
+ end)
+
+ vim.wait(1000, function()
+ return can_pass_lua_callback_to_vim_from_lua_result
+ end)
+
+ return can_pass_lua_callback_to_vim_from_lua_result
+ ]])
+ end)
+
+ it('can run named functions more than once', function()
+ eq(5, exec_lua [[
+ count_of_vals = 0
+
+ vim.fn.timer_start(5, function()
+ count_of_vals = count_of_vals + 1
+ end, {['repeat'] = 5})
+
+ vim.fn.wait(1000, function()
+ return count_of_vals >= 5
+ end)
+
+ return count_of_vals
+ ]])
+ end)
+
+ it('can handle clashing names', function()
+ eq(1, exec_lua [[
+ local f_loc = function() return 1 end
+
+ local result = nil
+ vim.fn.timer_start(100, function()
+ result = f_loc()
+ end)
+
+ local f_loc = function() return 2 end
+ vim.wait(1000, function() return result ~= nil end)
+
+ return result
+ ]])
+ end)
+
+ it('can handle functions with errors', function()
+ eq(true, exec_lua [[
+ vim.fn.timer_start(10, function()
+ error("dead function")
+ end)
+
+ vim.wait(1000, function() return false end)
+
+ return true
+ ]])
+ end)
+
+ it('should handle passing functions around', function()
+ command [[
+ function VimCanCallLuaCallbacks(Concat, Cb)
+ let message = a:Concat("Hello Vim", "I'm Lua")
+ call a:Cb(message)
+ endfunction
+ ]]
+
+ eq("Hello Vim I'm Lua", exec_lua [[
+ can_pass_lua_callback_to_vim_from_lua_result = ""
+
+ vim.fn.VimCanCallLuaCallbacks(
+ function(greeting, message) return greeting .. " " .. message end,
+ function(message) can_pass_lua_callback_to_vim_from_lua_result = message end
+ )
+
+ return can_pass_lua_callback_to_vim_from_lua_result
+ ]])
+ end)
+
+ it('should handle funcrefs', function()
+ command [[
+ function VimCanCallLuaCallbacks(Concat, Cb)
+ let message = a:Concat("Hello Vim", "I'm Lua")
+ call a:Cb(message)
+ endfunction
+ ]]
+
+ eq("Hello Vim I'm Lua", exec_lua [[
+ can_pass_lua_callback_to_vim_from_lua_result = ""
+
+ vim.funcref('VimCanCallLuaCallbacks')(
+ function(greeting, message) return greeting .. " " .. message end,
+ function(message) can_pass_lua_callback_to_vim_from_lua_result = message end
+ )
+
+ return can_pass_lua_callback_to_vim_from_lua_result
+ ]])
+ end)
+
+ it('should work with metatables using __call', function()
+ eq(1, exec_lua [[
+ local this_is_local_variable = false
+ local callable_table = setmetatable({x = 1}, {
+ __call = function(t, ...)
+ this_is_local_variable = t.x
+ end
+ })
+
+ vim.fn.timer_start(5, callable_table)
+
+ vim.wait(1000, function()
+ return this_is_local_variable
+ end)
+
+ return this_is_local_variable
+ ]])
+ end)
+
+ it('should handle being called from a timer once.', function()
+ eq(3, exec_lua [[
+ local this_is_local_variable = false
+ local callable_table = setmetatable({5, 4, 3, 2, 1}, {
+ __call = function(t, ...) this_is_local_variable = t[3] end
+ })
+
+ vim.fn.timer_start(5, callable_table)
+ vim.wait(1000, function()
+ return this_is_local_variable
+ end)
+
+ return this_is_local_variable
+ ]])
+ end)
+
+ it('should call functions once with __call metamethod', function()
+ eq(true, exec_lua [[
+ local this_is_local_variable = false
+ local callable_table = setmetatable({a = true, b = false}, {
+ __call = function(t, ...) this_is_local_variable = t.a end
+ })
+
+ assert(getmetatable(callable_table).__call)
+ vim.fn.call(callable_table, {})
+
+ return this_is_local_variable
+ ]])
+ end)
+
+ it('should work with lists using __call', function()
+ eq(3, exec_lua [[
+ local this_is_local_variable = false
+ local mt = {
+ __call = function(t, ...)
+ this_is_local_variable = t[3]
+ end
+ }
+ local callable_table = setmetatable({5, 4, 3, 2, 1}, mt)
+
+ -- Call it once...
+ vim.fn.timer_start(5, callable_table)
+ vim.wait(1000, function()
+ return this_is_local_variable
+ end)
+
+ assert(this_is_local_variable)
+ this_is_local_variable = false
+
+ vim.fn.timer_start(5, callable_table)
+ vim.wait(1000, function()
+ return this_is_local_variable
+ end)
+
+ return this_is_local_variable
+ ]])
+ end)
+
+ it('should not work with tables not using __call', function()
+ eq({false, 'Vim:E921: Invalid callback argument'}, exec_lua [[
+ local this_is_local_variable = false
+ local callable_table = setmetatable({x = 1}, {})
+
+ return {pcall(function() vim.fn.timer_start(5, callable_table) end)}
+ ]])
end)
it('correctly converts containers with type_idx', function()
@@ -237,19 +427,99 @@ describe('luaeval()', function()
it('errors out correctly when doing incorrect things in lua', function()
-- Conversion errors
- eq('Vim(call):E5108: Error while calling lua chunk for luaeval(): [string "<VimL compiled string>"]:1: attempt to call field \'xxx_nonexistent_key_xxx\' (a nil value)',
+ eq('Vim(call):E5108: Error executing lua [string "luaeval()"]:1: attempt to call field \'xxx_nonexistent_key_xxx\' (a nil value)',
exc_exec([[call luaeval("vim.xxx_nonexistent_key_xxx()")]]))
- eq('Vim(call):E5108: Error while calling lua chunk for luaeval(): [string "<VimL compiled string>"]:1: ERROR',
+ eq('Vim(call):E5108: Error executing lua [string "luaeval()"]:1: ERROR',
exc_exec([[call luaeval("error('ERROR')")]]))
- eq('Vim(call):E5108: Error while calling lua chunk for luaeval(): [NULL]',
+ eq('Vim(call):E5108: Error executing lua [NULL]',
exc_exec([[call luaeval("error(nil)")]]))
end)
it('does not leak memory when called with too long line',
function()
local s = ('x'):rep(65536)
- eq('Vim(call):E5107: Error while creating lua chunk for luaeval(): [string "<VimL compiled string>"]:1: unexpected symbol near \')\'',
+ eq('Vim(call):E5107: Error loading lua [string "luaeval()"]:1: unexpected symbol near \')\'',
exc_exec([[call luaeval("(']] .. s ..[[' + )")]]))
eq(s, funcs.luaeval('"' .. s .. '"'))
end)
end)
+
+describe('v:lua', function()
+ before_each(function()
+ exec_lua([[
+ function _G.foo(a,b,n)
+ _G.val = n
+ return a+b
+ end
+ mymod = {}
+ function mymod.noisy(name)
+ vim.api.nvim_set_current_line("hey "..name)
+ end
+ function mymod.crashy()
+ nonexistent()
+ end
+ function mymod.omni(findstart, base)
+ if findstart == 1 then
+ return 5
+ else
+ if base == 'st' then
+ return {'stuff', 'steam', 'strange things'}
+ end
+ end
+ end
+ vim.api.nvim_buf_set_option(0, 'omnifunc', 'v:lua.mymod.omni')
+ ]])
+ end)
+
+ it('works in expressions', function()
+ eq(7, eval('v:lua.foo(3,4,v:null)'))
+ eq(true, exec_lua([[return _G.val == vim.NIL]]))
+ eq(NIL, eval('v:lua.mymod.noisy("eval")'))
+ eq("hey eval", meths.get_current_line())
+
+ eq("Vim:E5108: Error executing lua [string \"<nvim>\"]:10: attempt to call global 'nonexistent' (a nil value)",
+ pcall_err(eval, 'v:lua.mymod.crashy()'))
+ end)
+
+ it('works in :call', function()
+ command(":call v:lua.mymod.noisy('command')")
+ eq("hey command", meths.get_current_line())
+ eq("Vim(call):E5108: Error executing lua [string \"<nvim>\"]:10: attempt to call global 'nonexistent' (a nil value)",
+ pcall_err(command, 'call v:lua.mymod.crashy()'))
+ end)
+
+ it('works in func options', function()
+ local screen = Screen.new(60, 8)
+ screen:set_default_attr_ids({
+ [1] = {bold = true, foreground = Screen.colors.Blue1},
+ [2] = {background = Screen.colors.WebGray},
+ [3] = {background = Screen.colors.LightMagenta},
+ [4] = {bold = true},
+ [5] = {bold = true, foreground = Screen.colors.SeaGreen4},
+ })
+ screen:attach()
+ feed('isome st<c-x><c-o>')
+ screen:expect{grid=[[
+ some stuff^ |
+ {1:~ }{2: stuff }{1: }|
+ {1:~ }{3: steam }{1: }|
+ {1:~ }{3: strange things }{1: }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {4:-- Omni completion (^O^N^P) }{5:match 1 of 3} |
+ ]]}
+ 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: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(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'"))
+ end)
+end)
diff --git a/test/functional/lua/overrides_spec.lua b/test/functional/lua/overrides_spec.lua
index f6439001ac..1bccc02847 100644
--- a/test/functional/lua/overrides_spec.lua
+++ b/test/functional/lua/overrides_spec.lua
@@ -54,11 +54,12 @@ describe('print', function()
v_tblout = setmetatable({}, meta_tblout)
]])
eq('', redir_exec('luafile ' .. fname))
- eq('\nE5105: Error while calling lua chunk: E5114: Error while converting print argument #2: [NULL]',
+ -- TODO(bfredl): these look weird, print() should not use "E5114:" style errors..
+ eq('\nE5108: Error executing lua E5114: Error while converting print argument #2: [NULL]',
redir_exec('lua print("foo", v_nilerr, "bar")'))
- eq('\nE5105: Error while calling lua chunk: E5114: Error while converting print argument #2: Xtest-functional-lua-overrides-luafile:2: abc',
+ eq('\nE5108: Error executing lua E5114: Error while converting print argument #2: Xtest-functional-lua-overrides-luafile:2: abc',
redir_exec('lua print("foo", v_abcerr, "bar")'))
- eq('\nE5105: Error while calling lua chunk: E5114: Error while converting print argument #2: <Unknown error: lua_tolstring returned NULL for tostring result>',
+ eq('\nE5108: Error executing lua E5114: Error while converting print argument #2: <Unknown error: lua_tolstring returned NULL for tostring result>',
redir_exec('lua print("foo", v_tblout, "bar")'))
end)
it('prints strings with NULs and NLs correctly', function()
@@ -156,7 +157,8 @@ describe('debug.debug', function()
lua_debug> ^ |
]])
feed('<C-c>')
- screen:expect([[
+ screen:expect{grid=[[
+ {0:~ }|
{0:~ }|
{0:~ }|
{0:~ }|
@@ -167,11 +169,10 @@ describe('debug.debug', function()
lua_debug> print("TEST") |
TEST |
|
- {E:E5105: Error while calling lua chunk: [string "<VimL }|
- {E:compiled string>"]:5: attempt to perform arithmetic o}|
- {E:n local 'a' (a nil value)} |
+ {E:E5108: Error executing lua [string ":lua"]:5: attempt}|
+ {E: to perform arithmetic on local 'a' (a nil value)} |
Interrupt: {cr:Press ENTER or type command to continue}^ |
- ]])
+ ]]}
feed('<C-l>:lua Test()\n')
screen:expect([[
{0:~ }|
@@ -190,7 +191,8 @@ describe('debug.debug', function()
lua_debug> ^ |
]])
feed('\n')
- screen:expect([[
+ screen:expect{grid=[[
+ {0:~ }|
{0:~ }|
{0:~ }|
{0:~ }|
@@ -201,11 +203,10 @@ describe('debug.debug', function()
{0:~ }|
nil |
lua_debug> |
- {E:E5105: Error while calling lua chunk: [string "<VimL }|
- {E:compiled string>"]:5: attempt to perform arithmetic o}|
- {E:n local 'a' (a nil value)} |
+ {E:E5108: Error executing lua [string ":lua"]:5: attempt}|
+ {E: to perform arithmetic on local 'a' (a nil value)} |
{cr:Press ENTER or type command to continue}^ |
- ]])
+ ]]}
end)
it("can be safely exited with 'cont'", function()
@@ -298,14 +299,11 @@ describe('package.path/package.cpath', function()
end
return new_paths
end
- local function execute_lua(cmd, ...)
- return meths.execute_lua(cmd, {...})
- end
local function eval_lua(expr, ...)
- return meths.execute_lua('return ' .. expr, {...})
+ return meths.exec_lua('return '..expr, {...})
end
local function set_path(which, value)
- return execute_lua('package[select(1, ...)] = select(2, ...)', which, value)
+ return exec_lua('package[select(1, ...)] = select(2, ...)', which, value)
end
it('contains directories from &runtimepath on first invocation', function()
diff --git a/test/functional/lua/treesitter_spec.lua b/test/functional/lua/treesitter_spec.lua
new file mode 100644
index 0000000000..aa3d55b06d
--- /dev/null
+++ b/test/functional/lua/treesitter_spec.lua
@@ -0,0 +1,512 @@
+-- Test suite for testing interactions with API bindings
+local helpers = require('test.functional.helpers')(after_each)
+local Screen = require('test.functional.ui.screen')
+
+local clear = helpers.clear
+local eq = helpers.eq
+local insert = helpers.insert
+local exec_lua = helpers.exec_lua
+local feed = helpers.feed
+local pcall_err = helpers.pcall_err
+local matches = helpers.matches
+
+before_each(clear)
+
+describe('treesitter API', function()
+ -- error tests not requiring a parser library
+ it('handles missing language', function()
+ eq("Error executing lua: .../treesitter.lua: no parser for 'borklang' language",
+ pcall_err(exec_lua, "parser = vim.treesitter.create_parser(0, 'borklang')"))
+
+ -- actual message depends on platform
+ matches("Error executing lua: Failed to load parser: uv_dlopen: .+",
+ pcall_err(exec_lua, "parser = vim.treesitter.require_language('borklang', 'borkbork.so')"))
+
+ eq("Error executing lua: .../treesitter.lua: no parser for 'borklang' language",
+ pcall_err(exec_lua, "parser = vim.treesitter.inspect_language('borklang')"))
+ end)
+
+end)
+
+describe('treesitter API with C parser', function()
+ local function check_parser()
+ local status, msg = unpack(exec_lua([[ return {pcall(vim.treesitter.require_language, 'c')} ]]))
+ if not status then
+ if helpers.isCI() then
+ error("treesitter C parser not found, required on CI: " .. msg)
+ else
+ pending('no C parser, skipping')
+ end
+ end
+ return status
+ end
+
+ it('parses buffer', function()
+ if not check_parser() then return end
+
+ insert([[
+ int main() {
+ int x = 3;
+ }]])
+
+ exec_lua([[
+ parser = vim.treesitter.get_parser(0, "c")
+ tree = parser:parse()
+ root = tree:root()
+ lang = vim.treesitter.inspect_language('c')
+ ]])
+
+ eq("<tree>", exec_lua("return tostring(tree)"))
+ eq("<node translation_unit>", exec_lua("return tostring(root)"))
+ eq({0,0,3,0}, exec_lua("return {root:range()}"))
+
+ eq(1, exec_lua("return root:child_count()"))
+ exec_lua("child = root:child(0)")
+ eq("<node function_definition>", exec_lua("return tostring(child)"))
+ eq({0,0,2,1}, exec_lua("return {child:range()}"))
+
+ eq("function_definition", exec_lua("return child:type()"))
+ eq(true, exec_lua("return child:named()"))
+ eq("number", type(exec_lua("return child:symbol()")))
+ eq({'function_definition', true}, exec_lua("return lang.symbols[child:symbol()]"))
+
+ exec_lua("anon = root:descendant_for_range(0,8,0,9)")
+ eq("(", exec_lua("return anon:type()"))
+ eq(false, exec_lua("return anon:named()"))
+ eq("number", type(exec_lua("return anon:symbol()")))
+ eq({'(', false}, exec_lua("return lang.symbols[anon:symbol()]"))
+
+ exec_lua("descendant = root:descendant_for_range(1,2,1,12)")
+ eq("<node declaration>", exec_lua("return tostring(descendant)"))
+ eq({1,2,1,12}, exec_lua("return {descendant:range()}"))
+ eq("(declaration type: (primitive_type) declarator: (init_declarator declarator: (identifier) value: (number_literal)))", exec_lua("return descendant:sexpr()"))
+
+ eq(true, exec_lua("return child == child"))
+ -- separate lua object, but represents same node
+ eq(true, exec_lua("return child == root:child(0)"))
+ eq(false, exec_lua("return child == descendant2"))
+ eq(false, exec_lua("return child == nil"))
+ eq(false, exec_lua("return child == tree"))
+
+ feed("2G7|ay")
+ exec_lua([[
+ tree2 = parser:parse()
+ root2 = tree2:root()
+ descendant2 = root2:descendant_for_range(1,2,1,13)
+ ]])
+ eq(false, exec_lua("return tree2 == tree1"))
+ eq(false, exec_lua("return root2 == root"))
+ eq("<node declaration>", exec_lua("return tostring(descendant2)"))
+ eq({1,2,1,13}, exec_lua("return {descendant2:range()}"))
+
+ -- orginal tree did not change
+ eq({1,2,1,12}, exec_lua("return {descendant:range()}"))
+
+ -- unchanged buffer: return the same tree
+ eq(true, exec_lua("return parser:parse() == tree2"))
+ end)
+
+ 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 query = [[
+ ((call_expression function: (identifier) @minfunc (argument_list (identifier) @min_id)) (eq? @minfunc "MIN"))
+ "for" @keyword
+ (primitive_type) @type
+ (field_expression argument: (identifier) @fieldarg)
+ ]]
+
+ it('support query and iter by capture', function()
+ if not check_parser() then return end
+
+ insert(test_text)
+
+ local res = exec_lua([[
+ cquery = vim.treesitter.parse_query("c", ...)
+ parser = vim.treesitter.get_parser(0, "c")
+ tree = parser:parse()
+ res = {}
+ for cid, node in cquery:iter_captures(tree:root(), 0, 7, 14) do
+ -- can't transmit node over RPC. just check the name and range
+ table.insert(res, {cquery.captures[cid], node:type(), node:range()})
+ end
+ return res
+ ]], query)
+
+ eq({
+ { "type", "primitive_type", 8, 2, 8, 6 },
+ { "keyword", "for", 9, 2, 9, 5 },
+ { "type", "primitive_type", 9, 7, 9, 13 },
+ { "minfunc", "identifier", 11, 12, 11, 15 },
+ { "fieldarg", "identifier", 11, 16, 11, 18 },
+ { "min_id", "identifier", 11, 27, 11, 32 },
+ { "minfunc", "identifier", 12, 13, 12, 16 },
+ { "fieldarg", "identifier", 12, 17, 12, 19 },
+ { "min_id", "identifier", 12, 29, 12, 35 },
+ { "fieldarg", "identifier", 13, 14, 13, 16 }
+ }, res)
+ end)
+
+ it('support query and iter by match', function()
+ if not check_parser() then return end
+
+ insert(test_text)
+
+ local res = exec_lua([[
+ cquery = vim.treesitter.parse_query("c", ...)
+ parser = vim.treesitter.get_parser(0, "c")
+ tree = parser:parse()
+ res = {}
+ for pattern, match in cquery:iter_matches(tree:root(), 0, 7, 14) do
+ -- can't transmit node over RPC. just check the name and range
+ local mrepr = {}
+ for cid,node in pairs(match) do
+ table.insert(mrepr, {cquery.captures[cid], node:type(), node:range()})
+ end
+ table.insert(res, {pattern, mrepr})
+ end
+ return res
+ ]], query)
+
+ eq({
+ { 3, { { "type", "primitive_type", 8, 2, 8, 6 } } },
+ { 2, { { "keyword", "for", 9, 2, 9, 5 } } },
+ { 3, { { "type", "primitive_type", 9, 7, 9, 13 } } },
+ { 4, { { "fieldarg", "identifier", 11, 16, 11, 18 } } },
+ { 1, { { "minfunc", "identifier", 11, 12, 11, 15 }, { "min_id", "identifier", 11, 27, 11, 32 } } },
+ { 4, { { "fieldarg", "identifier", 12, 17, 12, 19 } } },
+ { 1, { { "minfunc", "identifier", 12, 13, 12, 16 }, { "min_id", "identifier", 12, 29, 12, 35 } } },
+ { 4, { { "fieldarg", "identifier", 13, 14, 13, 16 } } }
+ }, res)
+ end)
+
+ it('supports highlighting', function()
+ if not check_parser() then return end
+
+ local hl_text = [[
+/// Schedule Lua callback on main loop's event queue
+static int nlua_schedule(lua_State *const lstate)
+{
+ if (lua_type(lstate, 1) != LUA_TFUNCTION
+ || lstate != lstate) {
+ lua_pushliteral(lstate, "vim.schedule: expected function");
+ return lua_error(lstate);
+ }
+
+ LuaRef cb = nlua_ref(lstate, 1);
+
+ multiqueue_put(main_loop.events, nlua_schedule_event,
+ 1, (void *)(ptrdiff_t)cb);
+ return 0;
+}]]
+
+ local hl_query = [[
+(ERROR) @ErrorMsg
+
+"if" @keyword
+"else" @keyword
+"for" @keyword
+"return" @keyword
+
+"const" @type
+"static" @type
+"struct" @type
+"enum" @type
+"extern" @type
+
+(string_literal) @string
+
+(number_literal) @number
+(char_literal) @string
+
+(type_identifier) @type
+((type_identifier) @Special (#eq? @Special "LuaRef"))
+
+(primitive_type) @type
+(sized_type_specifier) @type
+
+; defaults to very magic syntax, for best compatibility
+((identifier) @Identifier (#match? @Identifier "^l(u)a_"))
+; still support \M etc prefixes
+((identifier) @Constant (#match? @Constant "\M^\[A-Z_]\+$"))
+
+((binary_expression left: (identifier) @WarningMsg.left right: (identifier) @WarningMsg.right) (#eq? @WarningMsg.left @WarningMsg.right))
+
+(comment) @comment
+]]
+
+ local screen = Screen.new(65, 18)
+ screen:attach()
+ screen:set_default_attr_ids({
+ [1] = {bold = true, foreground = Screen.colors.Blue1},
+ [2] = {foreground = Screen.colors.Blue1},
+ [3] = {bold = true, foreground = Screen.colors.SeaGreen4},
+ [4] = {bold = true, foreground = Screen.colors.Brown},
+ [5] = {foreground = Screen.colors.Magenta},
+ [6] = {foreground = Screen.colors.Red},
+ [7] = {bold = true, foreground = Screen.colors.SlateBlue},
+ [8] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red},
+ [9] = {foreground = Screen.colors.Magenta, background = Screen.colors.Red},
+ [10] = {foreground = Screen.colors.Red, background = Screen.colors.Red},
+ [11] = {foreground = Screen.colors.Cyan4},
+ })
+
+ insert(hl_text)
+ screen:expect{grid=[[
+ /// Schedule Lua callback on main loop's event queue |
+ static int nlua_schedule(lua_State *const lstate) |
+ { |
+ if (lua_type(lstate, 1) != LUA_TFUNCTION |
+ || lstate != lstate) { |
+ lua_pushliteral(lstate, "vim.schedule: expected function"); |
+ return lua_error(lstate); |
+ } |
+ |
+ LuaRef cb = nlua_ref(lstate, 1); |
+ |
+ multiqueue_put(main_loop.events, nlua_schedule_event, |
+ 1, (void *)(ptrdiff_t)cb); |
+ return 0; |
+ ^} |
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+
+ exec_lua([[
+ local TSHighlighter = vim.treesitter.TSHighlighter
+ local query = ...
+ test_hl = TSHighlighter.new(query, 0, "c")
+ ]], hl_query)
+ screen:expect{grid=[[
+ {2:/// Schedule Lua callback on main loop's event queue} |
+ {3:static} {3:int} nlua_schedule({3:lua_State} *{3:const} lstate) |
+ { |
+ {4:if} ({11:lua_type}(lstate, {5:1}) != {5:LUA_TFUNCTION} |
+ || {6:lstate} != {6:lstate}) { |
+ {11:lua_pushliteral}(lstate, {5:"vim.schedule: expected function"}); |
+ {4:return} {11:lua_error}(lstate); |
+ } |
+ |
+ {7:LuaRef} cb = nlua_ref(lstate, {5:1}); |
+ |
+ multiqueue_put(main_loop.events, nlua_schedule_event, |
+ {5:1}, ({3:void} *)({3:ptrdiff_t})cb); |
+ {4:return} {5:0}; |
+ ^} |
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+
+ feed('7Go*/<esc>')
+ screen:expect{grid=[[
+ {2:/// Schedule Lua callback on main loop's event queue} |
+ {3:static} {3:int} nlua_schedule({3:lua_State} *{3:const} lstate) |
+ { |
+ {4:if} ({11:lua_type}(lstate, {5:1}) != {5:LUA_TFUNCTION} |
+ || {6:lstate} != {6:lstate}) { |
+ {11:lua_pushliteral}(lstate, {5:"vim.schedule: expected function"}); |
+ {4:return} {11:lua_error}(lstate); |
+ {8:*^/} |
+ } |
+ |
+ {7:LuaRef} cb = nlua_ref(lstate, {5:1}); |
+ |
+ multiqueue_put(main_loop.events, nlua_schedule_event, |
+ {5:1}, ({3:void} *)({3:ptrdiff_t})cb); |
+ {4:return} {5:0}; |
+ } |
+ {1:~ }|
+ |
+ ]]}
+
+ feed('3Go/*<esc>')
+ screen:expect{grid=[[
+ {2:/// Schedule Lua callback on main loop's event queue} |
+ {3:static} {3:int} nlua_schedule({3:lua_State} *{3:const} lstate) |
+ { |
+ {2:/^*} |
+ {2: if (lua_type(lstate, 1) != LUA_TFUNCTION} |
+ {2: || lstate != lstate) {} |
+ {2: lua_pushliteral(lstate, "vim.schedule: expected function");} |
+ {2: return lua_error(lstate);} |
+ {2:*/} |
+ } |
+ |
+ {7:LuaRef} cb = nlua_ref(lstate, {5:1}); |
+ |
+ multiqueue_put(main_loop.events, nlua_schedule_event, |
+ {5:1}, ({3:void} *)({3:ptrdiff_t})cb); |
+ {4:return} {5:0}; |
+ {8:}} |
+ |
+ ]]}
+
+ feed("gg$")
+ feed("~")
+ screen:expect{grid=[[
+ {2:/// Schedule Lua callback on main loop's event queu^E} |
+ {3:static} {3:int} nlua_schedule({3:lua_State} *{3:const} lstate) |
+ { |
+ {2:/*} |
+ {2: if (lua_type(lstate, 1) != LUA_TFUNCTION} |
+ {2: || lstate != lstate) {} |
+ {2: lua_pushliteral(lstate, "vim.schedule: expected function");} |
+ {2: return lua_error(lstate);} |
+ {2:*/} |
+ } |
+ |
+ {7:LuaRef} cb = nlua_ref(lstate, {5:1}); |
+ |
+ multiqueue_put(main_loop.events, nlua_schedule_event, |
+ {5:1}, ({3:void} *)({3:ptrdiff_t})cb); |
+ {4:return} {5:0}; |
+ {8:}} |
+ |
+ ]]}
+
+
+ feed("re")
+ screen:expect{grid=[[
+ {2:/// Schedule Lua callback on main loop's event queu^e} |
+ {3:static} {3:int} nlua_schedule({3:lua_State} *{3:const} lstate) |
+ { |
+ {2:/*} |
+ {2: if (lua_type(lstate, 1) != LUA_TFUNCTION} |
+ {2: || lstate != lstate) {} |
+ {2: lua_pushliteral(lstate, "vim.schedule: expected function");} |
+ {2: return lua_error(lstate);} |
+ {2:*/} |
+ } |
+ |
+ {7:LuaRef} cb = nlua_ref(lstate, {5:1}); |
+ |
+ multiqueue_put(main_loop.events, nlua_schedule_event, |
+ {5:1}, ({3:void} *)({3:ptrdiff_t})cb); |
+ {4:return} {5:0}; |
+ {8:}} |
+ |
+ ]]}
+ end)
+
+ it('inspects language', function()
+ if not check_parser() then return end
+
+ local keys, fields, symbols = unpack(exec_lua([[
+ local lang = vim.treesitter.inspect_language('c')
+ local keys, symbols = {}, {}
+ for k,_ in pairs(lang) do
+ keys[k] = true
+ end
+
+ -- symbols array can have "holes" and is thus not a valid msgpack array
+ -- but we don't care about the numbers here (checked in the parser test)
+ for _, v in pairs(lang.symbols) do
+ table.insert(symbols, v)
+ end
+ return {keys, lang.fields, symbols}
+ ]]))
+
+ eq({fields=true, symbols=true}, keys)
+
+ local fset = {}
+ for _,f in pairs(fields) do
+ eq("string", type(f))
+ fset[f] = true
+ end
+ eq(true, fset["directive"])
+ eq(true, fset["initializer"])
+
+ local has_named, has_anonymous
+ for _,s in pairs(symbols) do
+ eq("string", type(s[1]))
+ eq("boolean", type(s[2]))
+ if s[1] == "for_statement" and s[2] == true then
+ has_named = true
+ elseif s[1] == "|=" and s[2] == false then
+ has_anonymous = true
+ end
+ end
+ eq({true,true}, {has_named,has_anonymous})
+ end)
+ it('allows to set simple ranges', function()
+ if not check_parser() then return end
+
+ insert(test_text)
+
+ local res = exec_lua([[
+ parser = vim.treesitter.get_parser(0, "c")
+ return { parser:parse():root():range() }
+ ]])
+
+ eq({0, 0, 19, 0}, res)
+
+ -- The following sets the included ranges for the current parser
+ -- As stated here, this only includes the function (thus the whole buffer, without the last line)
+ local res2 = exec_lua([[
+ local root = parser:parse():root()
+ parser:set_included_ranges({root:child(0)})
+ parser.valid = false
+ return { parser:parse():root():range() }
+ ]])
+
+ eq({0, 0, 18, 1}, res2)
+ end)
+ it("allows to set complex ranges", function()
+ if not check_parser() then return end
+
+ insert(test_text)
+
+
+ local res = exec_lua([[
+ parser = vim.treesitter.get_parser(0, "c")
+ query = vim.treesitter.parse_query("c", "(declaration) @decl")
+
+ local nodes = {}
+ for _, node in query:iter_captures(parser:parse():root(), 0, 0, 19) do
+ table.insert(nodes, node)
+ end
+
+ parser:set_included_ranges(nodes)
+
+ local root = parser:parse():root()
+
+ local res = {}
+ for i=0,(root:named_child_count() - 1) do
+ table.insert(res, { root:named_child(i):range() })
+ end
+ return res
+ ]])
+
+ eq({
+ { 2, 2, 2, 40 },
+ { 3, 3, 3, 32 },
+ { 4, 7, 4, 8 },
+ { 4, 8, 4, 25 },
+ { 8, 2, 8, 6 },
+ { 8, 7, 8, 33 },
+ { 9, 8, 9, 20 },
+ { 10, 4, 10, 5 },
+ { 10, 5, 10, 20 },
+ { 14, 9, 14, 27 } }, res)
+ end)
+end)
diff --git a/test/functional/lua/uri_spec.lua b/test/functional/lua/uri_spec.lua
new file mode 100644
index 0000000000..f782769935
--- /dev/null
+++ b/test/functional/lua/uri_spec.lua
@@ -0,0 +1,150 @@
+local helpers = require('test.functional.helpers')(after_each)
+local clear = helpers.clear
+local exec_lua = helpers.exec_lua
+local eq = helpers.eq
+
+describe('URI methods', function()
+ before_each(function()
+ clear()
+ end)
+
+ describe('file path to uri', function()
+ describe('encode Unix file path', function()
+ it('file path includes only ascii charactors', function()
+ exec_lua("filepath = '/Foo/Bar/Baz.txt'")
+
+ eq('file:///Foo/Bar/Baz.txt', exec_lua("return vim.uri_from_fname(filepath)"))
+ end)
+
+ it('file path including white space', function()
+ exec_lua("filepath = '/Foo /Bar/Baz.txt'")
+
+ eq('file:///Foo%20/Bar/Baz.txt', exec_lua("return vim.uri_from_fname(filepath)"))
+ end)
+
+ it('file path including Unicode charactors', function()
+ exec_lua("filepath = '/xy/åäö/ɧ/汉语/↥/🤦/🦄/aÌŠ/بÙÙŠÙŽÙ‘.txt'")
+
+ -- The URI encoding should be case-insensitive
+ eq('file:///xy/%c3%a5%c3%a4%c3%b6/%c9%a7/%e6%b1%89%e8%af%ad/%e2%86%a5/%f0%9f%a4%a6/%f0%9f%a6%84/a%cc%8a/%d8%a8%d9%90%d9%8a%d9%8e%d9%91.txt', exec_lua("return vim.uri_from_fname(filepath)"))
+ end)
+ end)
+
+ describe('encode Windows filepath', function()
+ it('file path includes only ascii charactors', function()
+ exec_lua([[filepath = 'C:\\Foo\\Bar\\Baz.txt']])
+
+ eq('file:///C:/Foo/Bar/Baz.txt', exec_lua("return vim.uri_from_fname(filepath)"))
+ end)
+
+ it('file path including white space', function()
+ exec_lua([[filepath = 'C:\\Foo \\Bar\\Baz.txt']])
+
+ eq('file:///C:/Foo%20/Bar/Baz.txt', exec_lua("return vim.uri_from_fname(filepath)"))
+ end)
+
+ it('file path including Unicode charactors', function()
+ exec_lua([[filepath = 'C:\\xy\\åäö\\ɧ\\汉语\\↥\\🤦\\🦄\\aÌŠ\\بÙÙŠÙŽÙ‘.txt']])
+
+ eq('file:///C:/xy/%c3%a5%c3%a4%c3%b6/%c9%a7/%e6%b1%89%e8%af%ad/%e2%86%a5/%f0%9f%a4%a6/%f0%9f%a6%84/a%cc%8a/%d8%a8%d9%90%d9%8a%d9%8e%d9%91.txt', exec_lua("return vim.uri_from_fname(filepath)"))
+ end)
+ end)
+ end)
+
+ describe('uri to filepath', function()
+ describe('decode Unix file path', function()
+ it('file path includes only ascii charactors', function()
+ exec_lua("uri = 'file:///Foo/Bar/Baz.txt'")
+
+ eq('/Foo/Bar/Baz.txt', exec_lua("return vim.uri_to_fname(uri)"))
+ end)
+
+ it('file path including white space', function()
+ exec_lua("uri = 'file:///Foo%20/Bar/Baz.txt'")
+
+ eq('/Foo /Bar/Baz.txt', exec_lua("return vim.uri_to_fname(uri)"))
+ end)
+
+ it('file path including Unicode charactors', function()
+ local test_case = [[
+ local uri = 'file:///xy/%C3%A5%C3%A4%C3%B6/%C9%A7/%E6%B1%89%E8%AF%AD/%E2%86%A5/%F0%9F%A4%A6/%F0%9F%A6%84/a%CC%8A/%D8%A8%D9%90%D9%8A%D9%8E%D9%91.txt'
+ return vim.uri_to_fname(uri)
+ ]]
+
+ eq('/xy/åäö/ɧ/汉语/↥/🤦/🦄/aÌŠ/بÙÙŠÙŽÙ‘.txt', exec_lua(test_case))
+ end)
+ end)
+
+ describe('decode Windows filepath', function()
+ it('file path includes only ascii charactors', function()
+ local test_case = [[
+ local uri = 'file:///C:/Foo/Bar/Baz.txt'
+ return vim.uri_to_fname(uri)
+ ]]
+
+ eq('C:\\Foo\\Bar\\Baz.txt', exec_lua(test_case))
+ end)
+
+ it('file path includes only ascii charactors with encoded colon character', function()
+ local test_case = [[
+ local uri = 'file:///C%3A/Foo/Bar/Baz.txt'
+ return vim.uri_to_fname(uri)
+ ]]
+
+ eq('C:\\Foo\\Bar\\Baz.txt', exec_lua(test_case))
+ end)
+
+ it('file path including white space', function()
+ local test_case = [[
+ local uri = 'file:///C:/Foo%20/Bar/Baz.txt'
+ return vim.uri_to_fname(uri)
+ ]]
+
+ eq('C:\\Foo \\Bar\\Baz.txt', exec_lua(test_case))
+ end)
+
+ it('file path including Unicode charactors', function()
+ local test_case = [[
+ local uri = 'file:///C:/xy/%C3%A5%C3%A4%C3%B6/%C9%A7/%E6%B1%89%E8%AF%AD/%E2%86%A5/%F0%9F%A4%A6/%F0%9F%A6%84/a%CC%8A/%D8%A8%D9%90%D9%8A%D9%8E%D9%91.txt'
+ return vim.uri_to_fname(uri)
+ ]]
+
+ eq('C:\\xy\\åäö\\ɧ\\汉语\\↥\\🤦\\🦄\\aÌŠ\\بÙÙŠÙŽÙ‘.txt', exec_lua(test_case))
+ end)
+ end)
+
+ describe('decode non-file URI', function()
+ it('uri_to_fname returns non-file URI unchanged', function()
+ eq('jdt1.23+x-z://content/%5C/', exec_lua [[
+ return vim.uri_to_fname('jdt1.23+x-z://content/%5C/')
+ ]])
+ end)
+
+ it('uri_to_fname returns non-file upper-case scheme URI unchanged', function()
+ eq('JDT://content/%5C/', exec_lua [[
+ return vim.uri_to_fname('JDT://content/%5C/')
+ ]])
+ end)
+ end)
+
+ describe('decode URI without scheme', function()
+ it('fails because URI must have a scheme', function()
+ eq(false, exec_lua [[
+ return pcall(vim.uri_to_fname, 'not_an_uri.txt')
+ ]])
+ end)
+ end)
+
+ end)
+
+ describe('uri to bufnr', function()
+ it('uri_to_bufnr & uri_from_bufnr returns original uri for non-file uris', function()
+ local uri = 'jdt://contents/java.base/java.util/List.class?=sql/%5C/home%5C/user%5C/.jabba%5C/jdk%5C/openjdk%5C@1.14.0%5C/lib%5C/jrt-fs.jar%60java.base=/javadoc_location=/https:%5C/%5C/docs.oracle.com%5C/en%5C/java%5C/javase%5C/14%5C/docs%5C/api%5C/=/%3Cjava.util(List.class'
+ local test_case = string.format([[
+ local uri = '%s'
+ return vim.uri_from_bufnr(vim.uri_to_bufnr(uri))
+ ]], uri)
+ eq(uri, exec_lua(test_case))
+ end)
+ end)
+end)
diff --git a/test/functional/lua/utility_functions_spec.lua b/test/functional/lua/utility_functions_spec.lua
deleted file mode 100644
index ea2b1fc8a9..0000000000
--- a/test/functional/lua/utility_functions_spec.lua
+++ /dev/null
@@ -1,291 +0,0 @@
--- Test suite for testing interactions with API bindings
-local helpers = require('test.functional.helpers')(after_each)
-local Screen = require('test.functional.ui.screen')
-
-local funcs = helpers.funcs
-local clear = helpers.clear
-local eq = helpers.eq
-local eval = helpers.eval
-local feed = helpers.feed
-local pcall_err = helpers.pcall_err
-local exec_lua = helpers.exec_lua
-
-before_each(clear)
-
-describe('lua stdlib', function()
- -- İ: `tolower("İ")` is `i` which has length 1 while `İ` itself has
- -- length 2 (in bytes).
- -- Ⱥ: `tolower("Ⱥ")` is `ⱥ` which has length 2 while `Ⱥ` itself has
- -- length 3 (in bytes).
- --
- -- Note: 'i' !=? 'İ' and 'ⱥ' !=? 'Ⱥ' on some systems.
- -- Note: Built-in Nvim comparison (on systems lacking `strcasecmp`) works
- -- only on ASCII characters.
- it('vim.stricmp', function()
- eq(0, funcs.luaeval('vim.stricmp("a", "A")'))
- eq(0, funcs.luaeval('vim.stricmp("A", "a")'))
- eq(0, funcs.luaeval('vim.stricmp("a", "a")'))
- eq(0, funcs.luaeval('vim.stricmp("A", "A")'))
-
- eq(0, funcs.luaeval('vim.stricmp("", "")'))
- eq(0, funcs.luaeval('vim.stricmp("\\0", "\\0")'))
- eq(0, funcs.luaeval('vim.stricmp("\\0\\0", "\\0\\0")'))
- eq(0, funcs.luaeval('vim.stricmp("\\0\\0\\0", "\\0\\0\\0")'))
- eq(0, funcs.luaeval('vim.stricmp("\\0\\0\\0A", "\\0\\0\\0a")'))
- eq(0, funcs.luaeval('vim.stricmp("\\0\\0\\0a", "\\0\\0\\0A")'))
- eq(0, funcs.luaeval('vim.stricmp("\\0\\0\\0a", "\\0\\0\\0a")'))
-
- eq(0, funcs.luaeval('vim.stricmp("a\\0", "A\\0")'))
- eq(0, funcs.luaeval('vim.stricmp("A\\0", "a\\0")'))
- eq(0, funcs.luaeval('vim.stricmp("a\\0", "a\\0")'))
- eq(0, funcs.luaeval('vim.stricmp("A\\0", "A\\0")'))
-
- eq(0, funcs.luaeval('vim.stricmp("\\0a", "\\0A")'))
- eq(0, funcs.luaeval('vim.stricmp("\\0A", "\\0a")'))
- eq(0, funcs.luaeval('vim.stricmp("\\0a", "\\0a")'))
- eq(0, funcs.luaeval('vim.stricmp("\\0A", "\\0A")'))
-
- eq(0, funcs.luaeval('vim.stricmp("\\0a\\0", "\\0A\\0")'))
- eq(0, funcs.luaeval('vim.stricmp("\\0A\\0", "\\0a\\0")'))
- eq(0, funcs.luaeval('vim.stricmp("\\0a\\0", "\\0a\\0")'))
- eq(0, funcs.luaeval('vim.stricmp("\\0A\\0", "\\0A\\0")'))
-
- eq(-1, funcs.luaeval('vim.stricmp("a", "B")'))
- eq(-1, funcs.luaeval('vim.stricmp("A", "b")'))
- eq(-1, funcs.luaeval('vim.stricmp("a", "b")'))
- eq(-1, funcs.luaeval('vim.stricmp("A", "B")'))
-
- eq(-1, funcs.luaeval('vim.stricmp("", "\\0")'))
- eq(-1, funcs.luaeval('vim.stricmp("\\0", "\\0\\0")'))
- eq(-1, funcs.luaeval('vim.stricmp("\\0\\0", "\\0\\0\\0")'))
- eq(-1, funcs.luaeval('vim.stricmp("\\0\\0\\0A", "\\0\\0\\0b")'))
- eq(-1, funcs.luaeval('vim.stricmp("\\0\\0\\0a", "\\0\\0\\0B")'))
- eq(-1, funcs.luaeval('vim.stricmp("\\0\\0\\0a", "\\0\\0\\0b")'))
-
- eq(-1, funcs.luaeval('vim.stricmp("a\\0", "B\\0")'))
- eq(-1, funcs.luaeval('vim.stricmp("A\\0", "b\\0")'))
- eq(-1, funcs.luaeval('vim.stricmp("a\\0", "b\\0")'))
- eq(-1, funcs.luaeval('vim.stricmp("A\\0", "B\\0")'))
-
- eq(-1, funcs.luaeval('vim.stricmp("\\0a", "\\0B")'))
- eq(-1, funcs.luaeval('vim.stricmp("\\0A", "\\0b")'))
- eq(-1, funcs.luaeval('vim.stricmp("\\0a", "\\0b")'))
- eq(-1, funcs.luaeval('vim.stricmp("\\0A", "\\0B")'))
-
- eq(-1, funcs.luaeval('vim.stricmp("\\0a\\0", "\\0B\\0")'))
- eq(-1, funcs.luaeval('vim.stricmp("\\0A\\0", "\\0b\\0")'))
- eq(-1, funcs.luaeval('vim.stricmp("\\0a\\0", "\\0b\\0")'))
- eq(-1, funcs.luaeval('vim.stricmp("\\0A\\0", "\\0B\\0")'))
-
- eq(1, funcs.luaeval('vim.stricmp("c", "B")'))
- eq(1, funcs.luaeval('vim.stricmp("C", "b")'))
- eq(1, funcs.luaeval('vim.stricmp("c", "b")'))
- eq(1, funcs.luaeval('vim.stricmp("C", "B")'))
-
- eq(1, funcs.luaeval('vim.stricmp("\\0", "")'))
- eq(1, funcs.luaeval('vim.stricmp("\\0\\0", "\\0")'))
- eq(1, funcs.luaeval('vim.stricmp("\\0\\0\\0", "\\0\\0")'))
- eq(1, funcs.luaeval('vim.stricmp("\\0\\0\\0\\0", "\\0\\0\\0")'))
- eq(1, funcs.luaeval('vim.stricmp("\\0\\0\\0C", "\\0\\0\\0b")'))
- eq(1, funcs.luaeval('vim.stricmp("\\0\\0\\0c", "\\0\\0\\0B")'))
- eq(1, funcs.luaeval('vim.stricmp("\\0\\0\\0c", "\\0\\0\\0b")'))
-
- eq(1, funcs.luaeval('vim.stricmp("c\\0", "B\\0")'))
- eq(1, funcs.luaeval('vim.stricmp("C\\0", "b\\0")'))
- eq(1, funcs.luaeval('vim.stricmp("c\\0", "b\\0")'))
- eq(1, funcs.luaeval('vim.stricmp("C\\0", "B\\0")'))
-
- eq(1, funcs.luaeval('vim.stricmp("c\\0", "B")'))
- eq(1, funcs.luaeval('vim.stricmp("C\\0", "b")'))
- eq(1, funcs.luaeval('vim.stricmp("c\\0", "b")'))
- eq(1, funcs.luaeval('vim.stricmp("C\\0", "B")'))
-
- eq(1, funcs.luaeval('vim.stricmp("\\0c", "\\0B")'))
- eq(1, funcs.luaeval('vim.stricmp("\\0C", "\\0b")'))
- eq(1, funcs.luaeval('vim.stricmp("\\0c", "\\0b")'))
- eq(1, funcs.luaeval('vim.stricmp("\\0C", "\\0B")'))
-
- eq(1, funcs.luaeval('vim.stricmp("\\0c\\0", "\\0B\\0")'))
- eq(1, funcs.luaeval('vim.stricmp("\\0C\\0", "\\0b\\0")'))
- eq(1, funcs.luaeval('vim.stricmp("\\0c\\0", "\\0b\\0")'))
- eq(1, funcs.luaeval('vim.stricmp("\\0C\\0", "\\0B\\0")'))
- end)
-
- it("vim.str_utfindex/str_byteindex", function()
- exec_lua([[_G.test_text = "xy åäö ɧ 汉语 ↥ 🤦x🦄 aÌŠ بÙÙŠÙŽÙ‘"]])
- local indicies32 = {[0]=0,1,2,3,5,7,9,10,12,13,16,19,20,23,24,28,29,33,34,35,37,38,40,42,44,46,48}
- local indicies16 = {[0]=0,1,2,3,5,7,9,10,12,13,16,19,20,23,24,28,28,29,33,33,34,35,37,38,40,42,44,46,48}
- for i,k in pairs(indicies32) do
- eq(k, exec_lua("return vim.str_byteindex(_G.test_text, ...)", i), i)
- end
- for i,k in pairs(indicies16) do
- eq(k, exec_lua("return vim.str_byteindex(_G.test_text, ..., true)", i), i)
- end
- local i32, i16 = 0, 0
- for k = 0,48 do
- if indicies32[i32] < k then
- i32 = i32 + 1
- end
- if indicies16[i16] < k then
- i16 = i16 + 1
- if indicies16[i16+1] == indicies16[i16] then
- i16 = i16 + 1
- end
- end
- eq({i32, i16}, exec_lua("return {vim.str_utfindex(_G.test_text, ...)}", k), k)
- end
- end)
-
- it("vim.schedule", function()
- exec_lua([[
- test_table = {}
- vim.schedule(function()
- table.insert(test_table, "xx")
- end)
- table.insert(test_table, "yy")
- ]])
- eq({"yy","xx"}, exec_lua("return test_table"))
-
- -- type checked args
- eq('Error executing lua: vim.schedule: expected function',
- pcall_err(exec_lua, "vim.schedule('stringly')"))
-
- eq('Error executing lua: vim.schedule: expected function',
- pcall_err(exec_lua, "vim.schedule()"))
-
- exec_lua([[
- vim.schedule(function()
- error("big failure\nvery async")
- end)
- ]])
-
- feed("<cr>")
- eq('Error executing vim.schedule lua callback: [string "<nvim>"]:2: big failure\nvery async', eval("v:errmsg"))
-
- local screen = Screen.new(60,5)
- screen:set_default_attr_ids({
- [1] = {bold = true, foreground = Screen.colors.Blue1},
- [2] = {bold = true, reverse = true},
- [3] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red},
- [4] = {bold = true, foreground = Screen.colors.SeaGreen4},
- })
- screen:attach()
- screen:expect{grid=[[
- ^ |
- {1:~ }|
- {1:~ }|
- {1:~ }|
- |
- ]]}
-
- -- nvim_command causes a vimL exception, check that it is properly caught
- -- and propagated as an error message in async contexts.. #10809
- exec_lua([[
- vim.schedule(function()
- vim.api.nvim_command(":echo 'err")
- end)
- ]])
- screen:expect{grid=[[
- |
- {2: }|
- {3:Error executing vim.schedule lua callback: [string "<nvim>"]}|
- {3::2: Vim(echo):E115: Missing quote: 'err} |
- {4:Press ENTER or type command to continue}^ |
- ]]}
- end)
-
- it("vim.split", function()
- local split = function(str, sep)
- return exec_lua('return vim.split(...)', str, sep)
- end
-
- local tests = {
- { "a,b", ",", false, { 'a', 'b' } },
- { ":aa::bb:", ":", false, { '', 'aa', '', 'bb', '' } },
- { "::ee::ff:", ":", false, { '', '', 'ee', '', 'ff', '' } },
- { "ab", ".", false, { '', '', '' } },
- { "a1b2c", "[0-9]", false, { 'a', 'b', 'c' } },
- { "xy", "", false, { 'x', 'y' } },
- { "here be dragons", " ", false, { "here", "be", "dragons"} },
- { "axaby", "ab?", false, { '', 'x', 'y' } },
- { "f v2v v3v w2w ", "([vw])2%1", false, { 'f ', ' v3v ', ' ' } },
- { "x*yz*oo*l", "*", true, { 'x', 'yz', 'oo', 'l' } },
- }
-
- for _, t in ipairs(tests) do
- eq(t[4], split(t[1], t[2], t[3]))
- end
-
- local loops = {
- { "abc", ".-" },
- }
-
- for _, t in ipairs(loops) do
- local status, err = pcall(split, t[1], t[2])
- eq(false, status)
- assert(string.match(err, "Infinite loop detected"))
- end
- end)
-
- it('vim.trim', function()
- local trim = function(s)
- return exec_lua('return vim.trim(...)', s)
- end
-
- local trims = {
- { " a", "a" },
- { " b ", "b" },
- { "\tc" , "c" },
- { "r\n", "r" },
- }
-
- for _, t in ipairs(trims) do
- assert(t[2], trim(t[1]))
- end
-
- local status, err = pcall(trim, 2)
- eq(false, status)
- assert(string.match(err, "Only strings can be trimmed"))
- end)
-
- it('vim.inspect', function()
- -- just make sure it basically works, it has its own test suite
- local inspect = function(t, opts)
- return exec_lua('return vim.inspect(...)', t, opts)
- end
-
- eq('2', inspect(2))
- eq('{+a = {+b = 1+}+}',
- inspect({ a = { b = 1 } }, { newline = '+', indent = '' }))
-
- -- special value vim.inspect.KEY works
- eq('{ KEY_a = "x", KEY_b = "y"}', exec_lua([[
- return vim.inspect({a="x", b="y"}, {newline = '', process = function(item, path)
- if path[#path] == vim.inspect.KEY then
- return 'KEY_'..item
- end
- return item
- end})
- ]]))
- end)
-
- it("vim.deepcopy", function()
- local is_dc = exec_lua([[
- local a = { x = { 1, 2 }, y = 5}
- local b = vim.deepcopy(a)
-
- local count = 0
- for _ in pairs(b) do count = count + 1 end
-
- return b.x[1] == 1 and b.x[2] == 2 and b.y == 5 and count == 2
- and tostring(a) ~= tostring(b)
- ]])
-
- assert(is_dc)
- end)
-
- it('vim.pesc', function()
- eq('foo%-bar', exec_lua([[return vim.pesc('foo-bar')]]))
- eq('foo%%%-bar', exec_lua([[return vim.pesc(vim.pesc('foo-bar'))]]))
- end)
-end)
diff --git a/test/functional/lua/vim_spec.lua b/test/functional/lua/vim_spec.lua
new file mode 100644
index 0000000000..9b2697b3c2
--- /dev/null
+++ b/test/functional/lua/vim_spec.lua
@@ -0,0 +1,1235 @@
+-- Test suite for testing interactions with API bindings
+local helpers = require('test.functional.helpers')(after_each)
+local Screen = require('test.functional.ui.screen')
+
+local funcs = helpers.funcs
+local meths = helpers.meths
+local command = helpers.command
+local clear = helpers.clear
+local eq = helpers.eq
+local ok = helpers.ok
+local eval = helpers.eval
+local feed = helpers.feed
+local pcall_err = helpers.pcall_err
+local exec_lua = helpers.exec_lua
+local matches = helpers.matches
+local source = helpers.source
+local NIL = helpers.NIL
+local retry = helpers.retry
+
+before_each(clear)
+
+describe('lua stdlib', function()
+ -- İ: `tolower("İ")` is `i` which has length 1 while `İ` itself has
+ -- length 2 (in bytes).
+ -- Ⱥ: `tolower("Ⱥ")` is `ⱥ` which has length 2 while `Ⱥ` itself has
+ -- length 3 (in bytes).
+ --
+ -- Note: 'i' !=? 'İ' and 'ⱥ' !=? 'Ⱥ' on some systems.
+ -- Note: Built-in Nvim comparison (on systems lacking `strcasecmp`) works
+ -- only on ASCII characters.
+ it('vim.stricmp', function()
+ eq(0, funcs.luaeval('vim.stricmp("a", "A")'))
+ eq(0, funcs.luaeval('vim.stricmp("A", "a")'))
+ eq(0, funcs.luaeval('vim.stricmp("a", "a")'))
+ eq(0, funcs.luaeval('vim.stricmp("A", "A")'))
+
+ eq(0, funcs.luaeval('vim.stricmp("", "")'))
+ eq(0, funcs.luaeval('vim.stricmp("\\0", "\\0")'))
+ eq(0, funcs.luaeval('vim.stricmp("\\0\\0", "\\0\\0")'))
+ eq(0, funcs.luaeval('vim.stricmp("\\0\\0\\0", "\\0\\0\\0")'))
+ eq(0, funcs.luaeval('vim.stricmp("\\0\\0\\0A", "\\0\\0\\0a")'))
+ eq(0, funcs.luaeval('vim.stricmp("\\0\\0\\0a", "\\0\\0\\0A")'))
+ eq(0, funcs.luaeval('vim.stricmp("\\0\\0\\0a", "\\0\\0\\0a")'))
+
+ eq(0, funcs.luaeval('vim.stricmp("a\\0", "A\\0")'))
+ eq(0, funcs.luaeval('vim.stricmp("A\\0", "a\\0")'))
+ eq(0, funcs.luaeval('vim.stricmp("a\\0", "a\\0")'))
+ eq(0, funcs.luaeval('vim.stricmp("A\\0", "A\\0")'))
+
+ eq(0, funcs.luaeval('vim.stricmp("\\0a", "\\0A")'))
+ eq(0, funcs.luaeval('vim.stricmp("\\0A", "\\0a")'))
+ eq(0, funcs.luaeval('vim.stricmp("\\0a", "\\0a")'))
+ eq(0, funcs.luaeval('vim.stricmp("\\0A", "\\0A")'))
+
+ eq(0, funcs.luaeval('vim.stricmp("\\0a\\0", "\\0A\\0")'))
+ eq(0, funcs.luaeval('vim.stricmp("\\0A\\0", "\\0a\\0")'))
+ eq(0, funcs.luaeval('vim.stricmp("\\0a\\0", "\\0a\\0")'))
+ eq(0, funcs.luaeval('vim.stricmp("\\0A\\0", "\\0A\\0")'))
+
+ eq(-1, funcs.luaeval('vim.stricmp("a", "B")'))
+ eq(-1, funcs.luaeval('vim.stricmp("A", "b")'))
+ eq(-1, funcs.luaeval('vim.stricmp("a", "b")'))
+ eq(-1, funcs.luaeval('vim.stricmp("A", "B")'))
+
+ eq(-1, funcs.luaeval('vim.stricmp("", "\\0")'))
+ eq(-1, funcs.luaeval('vim.stricmp("\\0", "\\0\\0")'))
+ eq(-1, funcs.luaeval('vim.stricmp("\\0\\0", "\\0\\0\\0")'))
+ eq(-1, funcs.luaeval('vim.stricmp("\\0\\0\\0A", "\\0\\0\\0b")'))
+ eq(-1, funcs.luaeval('vim.stricmp("\\0\\0\\0a", "\\0\\0\\0B")'))
+ eq(-1, funcs.luaeval('vim.stricmp("\\0\\0\\0a", "\\0\\0\\0b")'))
+
+ eq(-1, funcs.luaeval('vim.stricmp("a\\0", "B\\0")'))
+ eq(-1, funcs.luaeval('vim.stricmp("A\\0", "b\\0")'))
+ eq(-1, funcs.luaeval('vim.stricmp("a\\0", "b\\0")'))
+ eq(-1, funcs.luaeval('vim.stricmp("A\\0", "B\\0")'))
+
+ eq(-1, funcs.luaeval('vim.stricmp("\\0a", "\\0B")'))
+ eq(-1, funcs.luaeval('vim.stricmp("\\0A", "\\0b")'))
+ eq(-1, funcs.luaeval('vim.stricmp("\\0a", "\\0b")'))
+ eq(-1, funcs.luaeval('vim.stricmp("\\0A", "\\0B")'))
+
+ eq(-1, funcs.luaeval('vim.stricmp("\\0a\\0", "\\0B\\0")'))
+ eq(-1, funcs.luaeval('vim.stricmp("\\0A\\0", "\\0b\\0")'))
+ eq(-1, funcs.luaeval('vim.stricmp("\\0a\\0", "\\0b\\0")'))
+ eq(-1, funcs.luaeval('vim.stricmp("\\0A\\0", "\\0B\\0")'))
+
+ eq(1, funcs.luaeval('vim.stricmp("c", "B")'))
+ eq(1, funcs.luaeval('vim.stricmp("C", "b")'))
+ eq(1, funcs.luaeval('vim.stricmp("c", "b")'))
+ eq(1, funcs.luaeval('vim.stricmp("C", "B")'))
+
+ eq(1, funcs.luaeval('vim.stricmp("\\0", "")'))
+ eq(1, funcs.luaeval('vim.stricmp("\\0\\0", "\\0")'))
+ eq(1, funcs.luaeval('vim.stricmp("\\0\\0\\0", "\\0\\0")'))
+ eq(1, funcs.luaeval('vim.stricmp("\\0\\0\\0\\0", "\\0\\0\\0")'))
+ eq(1, funcs.luaeval('vim.stricmp("\\0\\0\\0C", "\\0\\0\\0b")'))
+ eq(1, funcs.luaeval('vim.stricmp("\\0\\0\\0c", "\\0\\0\\0B")'))
+ eq(1, funcs.luaeval('vim.stricmp("\\0\\0\\0c", "\\0\\0\\0b")'))
+
+ eq(1, funcs.luaeval('vim.stricmp("c\\0", "B\\0")'))
+ eq(1, funcs.luaeval('vim.stricmp("C\\0", "b\\0")'))
+ eq(1, funcs.luaeval('vim.stricmp("c\\0", "b\\0")'))
+ eq(1, funcs.luaeval('vim.stricmp("C\\0", "B\\0")'))
+
+ eq(1, funcs.luaeval('vim.stricmp("c\\0", "B")'))
+ eq(1, funcs.luaeval('vim.stricmp("C\\0", "b")'))
+ eq(1, funcs.luaeval('vim.stricmp("c\\0", "b")'))
+ eq(1, funcs.luaeval('vim.stricmp("C\\0", "B")'))
+
+ eq(1, funcs.luaeval('vim.stricmp("\\0c", "\\0B")'))
+ eq(1, funcs.luaeval('vim.stricmp("\\0C", "\\0b")'))
+ eq(1, funcs.luaeval('vim.stricmp("\\0c", "\\0b")'))
+ eq(1, funcs.luaeval('vim.stricmp("\\0C", "\\0B")'))
+
+ eq(1, funcs.luaeval('vim.stricmp("\\0c\\0", "\\0B\\0")'))
+ eq(1, funcs.luaeval('vim.stricmp("\\0C\\0", "\\0b\\0")'))
+ eq(1, funcs.luaeval('vim.stricmp("\\0c\\0", "\\0b\\0")'))
+ eq(1, funcs.luaeval('vim.stricmp("\\0C\\0", "\\0B\\0")'))
+ end)
+
+ it('vim.startswith', function()
+ eq(true, funcs.luaeval('vim.startswith("123", "1")'))
+ eq(true, funcs.luaeval('vim.startswith("123", "")'))
+ eq(true, funcs.luaeval('vim.startswith("123", "123")'))
+ eq(true, funcs.luaeval('vim.startswith("", "")'))
+
+ eq(false, funcs.luaeval('vim.startswith("123", " ")'))
+ eq(false, funcs.luaeval('vim.startswith("123", "2")'))
+ eq(false, funcs.luaeval('vim.startswith("123", "1234")'))
+
+ eq("string", type(pcall_err(funcs.luaeval, 'vim.startswith("123", nil)')))
+ eq("string", type(pcall_err(funcs.luaeval, 'vim.startswith(nil, "123")')))
+ end)
+
+ it('vim.endswith', function()
+ eq(true, funcs.luaeval('vim.endswith("123", "3")'))
+ eq(true, funcs.luaeval('vim.endswith("123", "")'))
+ eq(true, funcs.luaeval('vim.endswith("123", "123")'))
+ eq(true, funcs.luaeval('vim.endswith("", "")'))
+
+ eq(false, funcs.luaeval('vim.endswith("123", " ")'))
+ eq(false, funcs.luaeval('vim.endswith("123", "2")'))
+ eq(false, funcs.luaeval('vim.endswith("123", "1234")'))
+
+ eq("string", type(pcall_err(funcs.luaeval, 'vim.endswith("123", nil)')))
+ eq("string", type(pcall_err(funcs.luaeval, 'vim.endswith(nil, "123")')))
+ end)
+
+ it("vim.str_utfindex/str_byteindex", function()
+ exec_lua([[_G.test_text = "xy åäö ɧ 汉语 ↥ 🤦x🦄 aÌŠ بÙÙŠÙŽÙ‘"]])
+ local indicies32 = {[0]=0,1,2,3,5,7,9,10,12,13,16,19,20,23,24,28,29,33,34,35,37,38,40,42,44,46,48}
+ local indicies16 = {[0]=0,1,2,3,5,7,9,10,12,13,16,19,20,23,24,28,28,29,33,33,34,35,37,38,40,42,44,46,48}
+ for i,k in pairs(indicies32) do
+ eq(k, exec_lua("return vim.str_byteindex(_G.test_text, ...)", i), i)
+ end
+ for i,k in pairs(indicies16) do
+ eq(k, exec_lua("return vim.str_byteindex(_G.test_text, ..., true)", i), i)
+ end
+ local i32, i16 = 0, 0
+ for k = 0,48 do
+ if indicies32[i32] < k then
+ i32 = i32 + 1
+ end
+ if indicies16[i16] < k then
+ i16 = i16 + 1
+ if indicies16[i16+1] == indicies16[i16] then
+ i16 = i16 + 1
+ end
+ end
+ eq({i32, i16}, exec_lua("return {vim.str_utfindex(_G.test_text, ...)}", k), k)
+ end
+ end)
+
+ it("vim.schedule", function()
+ exec_lua([[
+ test_table = {}
+ vim.schedule(function()
+ table.insert(test_table, "xx")
+ end)
+ table.insert(test_table, "yy")
+ ]])
+ eq({"yy","xx"}, exec_lua("return test_table"))
+
+ -- Validates args.
+ eq('Error executing lua: vim.schedule: expected function',
+ pcall_err(exec_lua, "vim.schedule('stringly')"))
+ eq('Error executing lua: vim.schedule: expected function',
+ pcall_err(exec_lua, "vim.schedule()"))
+
+ exec_lua([[
+ vim.schedule(function()
+ error("big failure\nvery async")
+ end)
+ ]])
+
+ feed("<cr>")
+ eq('Error executing vim.schedule lua callback: [string "<nvim>"]:2: big failure\nvery async', eval("v:errmsg"))
+
+ local screen = Screen.new(60,5)
+ screen:set_default_attr_ids({
+ [1] = {bold = true, foreground = Screen.colors.Blue1},
+ [2] = {bold = true, reverse = true},
+ [3] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red},
+ [4] = {bold = true, foreground = Screen.colors.SeaGreen4},
+ })
+ screen:attach()
+ screen:expect{grid=[[
+ ^ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+
+ -- nvim_command causes a vimL exception, check that it is properly caught
+ -- and propagated as an error message in async contexts.. #10809
+ exec_lua([[
+ vim.schedule(function()
+ vim.api.nvim_command(":echo 'err")
+ end)
+ ]])
+ screen:expect{grid=[[
+ |
+ {2: }|
+ {3:Error executing vim.schedule lua callback: [string "<nvim>"]}|
+ {3::2: Vim(echo):E115: Missing quote: 'err} |
+ {4:Press ENTER or type command to continue}^ |
+ ]]}
+ end)
+
+ it("vim.split", function()
+ local split = function(str, sep, plain)
+ return exec_lua('return vim.split(...)', str, sep, plain)
+ end
+
+ local tests = {
+ { "a,b", ",", false, { 'a', 'b' } },
+ { ":aa::bb:", ":", false, { '', 'aa', '', 'bb', '' } },
+ { "::ee::ff:", ":", false, { '', '', 'ee', '', 'ff', '' } },
+ { "ab", ".", false, { '', '', '' } },
+ { "a1b2c", "[0-9]", false, { 'a', 'b', 'c' } },
+ { "xy", "", false, { 'x', 'y' } },
+ { "here be dragons", " ", false, { "here", "be", "dragons"} },
+ { "axaby", "ab?", false, { '', 'x', 'y' } },
+ { "f v2v v3v w2w ", "([vw])2%1", false, { 'f ', ' v3v ', ' ' } },
+ { "", "", false, {} },
+ { "", "a", false, { '' } },
+ { "x*yz*oo*l", "*", true, { 'x', 'yz', 'oo', 'l' } },
+ }
+
+ for _, t in ipairs(tests) do
+ eq(t[4], split(t[1], t[2], t[3]))
+ end
+
+ local loops = {
+ { "abc", ".-" },
+ }
+
+ for _, t in ipairs(loops) do
+ matches(".*Infinite loop detected", pcall_err(split, t[1], t[2]))
+ end
+
+ -- Validates args.
+ eq(true, pcall(split, 'string', 'string'))
+ eq('Error executing lua: .../shared.lua: s: expected string, got number',
+ pcall_err(split, 1, 'string'))
+ eq('Error executing lua: .../shared.lua: sep: expected string, got number',
+ pcall_err(split, 'string', 1))
+ eq('Error executing lua: .../shared.lua: plain: expected boolean, got number',
+ pcall_err(split, 'string', 'string', 1))
+ end)
+
+ it('vim.trim', function()
+ local trim = function(s)
+ return exec_lua('return vim.trim(...)', s)
+ end
+
+ local trims = {
+ { " a", "a" },
+ { " b ", "b" },
+ { "\tc" , "c" },
+ { "r\n", "r" },
+ }
+
+ for _, t in ipairs(trims) do
+ assert(t[2], trim(t[1]))
+ end
+
+ -- Validates args.
+ eq('Error executing lua: .../shared.lua: s: expected string, got number',
+ pcall_err(trim, 2))
+ end)
+
+ it('vim.inspect', function()
+ -- just make sure it basically works, it has its own test suite
+ local inspect = function(t, opts)
+ return exec_lua('return vim.inspect(...)', t, opts)
+ end
+
+ eq('2', inspect(2))
+ eq('{+a = {+b = 1+}+}',
+ inspect({ a = { b = 1 } }, { newline = '+', indent = '' }))
+
+ -- special value vim.inspect.KEY works
+ eq('{ KEY_a = "x", KEY_b = "y"}', exec_lua([[
+ return vim.inspect({a="x", b="y"}, {newline = '', process = function(item, path)
+ if path[#path] == vim.inspect.KEY then
+ return 'KEY_'..item
+ end
+ return item
+ end})
+ ]]))
+ end)
+
+ it("vim.deepcopy", function()
+ ok(exec_lua([[
+ local a = { x = { 1, 2 }, y = 5}
+ local b = vim.deepcopy(a)
+
+ return b.x[1] == 1 and b.x[2] == 2 and b.y == 5 and vim.tbl_count(b) == 2
+ and tostring(a) ~= tostring(b)
+ ]]))
+
+ ok(exec_lua([[
+ local a = {}
+ local b = vim.deepcopy(a)
+
+ return vim.tbl_islist(b) and vim.tbl_count(b) == 0 and tostring(a) ~= tostring(b)
+ ]]))
+
+ ok(exec_lua([[
+ local a = vim.empty_dict()
+ local b = vim.deepcopy(a)
+
+ return not vim.tbl_islist(b) and vim.tbl_count(b) == 0
+ ]]))
+
+ ok(exec_lua([[
+ local a = {x = vim.empty_dict(), y = {}}
+ local b = vim.deepcopy(a)
+
+ return not vim.tbl_islist(b.x) and vim.tbl_islist(b.y)
+ and vim.tbl_count(b) == 2
+ and tostring(a) ~= tostring(b)
+ ]]))
+
+ ok(exec_lua([[
+ local f1 = function() return 1 end
+ local f2 = function() return 2 end
+ local t1 = {f = f1}
+ local t2 = vim.deepcopy(t1)
+ t1.f = f2
+ return t1.f() ~= t2.f()
+ ]]))
+
+ eq('Error executing lua: .../shared.lua: Cannot deepcopy object of type thread',
+ pcall_err(exec_lua, [[
+ local thread = coroutine.create(function () return 0 end)
+ local t = {thr = thread}
+ vim.deepcopy(t)
+ ]]))
+ end)
+
+ it('vim.pesc', function()
+ eq('foo%-bar', exec_lua([[return vim.pesc('foo-bar')]]))
+ eq('foo%%%-bar', exec_lua([[return vim.pesc(vim.pesc('foo-bar'))]]))
+
+ -- Validates args.
+ eq('Error executing lua: .../shared.lua: s: expected string, got number',
+ pcall_err(exec_lua, [[return vim.pesc(2)]]))
+ 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
+ eq(true, exec_lua("return vim.tbl_contains({ 1, 2, 3 }, ...)", v))
+ end
+ for _, v in pairs(exec_lua("return vim.tbl_keys({a=1, b=2, c=3})")) do
+ eq(true, exec_lua("return vim.tbl_contains({ 'a', 'b', 'c' }, ...)", v))
+ end
+ end)
+
+ it('vim.tbl_values', function()
+ eq({}, exec_lua("return vim.tbl_values({})"))
+ for _, v in pairs(exec_lua("return vim.tbl_values({'a', 'b', 'c'})")) do
+ eq(true, exec_lua("return vim.tbl_contains({ 'a', 'b', 'c' }, ...)", v))
+ end
+ for _, v in pairs(exec_lua("return vim.tbl_values({a=1, b=2, c=3})")) do
+ eq(true, exec_lua("return vim.tbl_contains({ 1, 2, 3 }, ...)", v))
+ end
+ end)
+
+ it('vim.tbl_map', function()
+ eq({}, exec_lua([[
+ return vim.tbl_map(function(v) return v * 2 end, {})
+ ]]))
+ eq({2, 4, 6}, exec_lua([[
+ return vim.tbl_map(function(v) return v * 2 end, {1, 2, 3})
+ ]]))
+ eq({{i=2}, {i=4}, {i=6}}, exec_lua([[
+ return vim.tbl_map(function(v) return { i = v.i * 2 } end, {{i=1}, {i=2}, {i=3}})
+ ]]))
+ end)
+
+ it('vim.tbl_filter', function()
+ eq({}, exec_lua([[
+ return vim.tbl_filter(function(v) return (v % 2) == 0 end, {})
+ ]]))
+ eq({2}, exec_lua([[
+ return vim.tbl_filter(function(v) return (v % 2) == 0 end, {1, 2, 3})
+ ]]))
+ eq({{i=2}}, exec_lua([[
+ return vim.tbl_filter(function(v) return (v.i % 2) == 0 end, {{i=1}, {i=2}, {i=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())"))
+ eq(true, exec_lua("return vim.tbl_islist({'a', 'b', 'c'})"))
+ eq(false, exec_lua("return vim.tbl_islist({'a', '32', a='hello', b='baz'})"))
+ 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'})"))
+ end)
+
+ it('vim.tbl_isempty', function()
+ eq(true, exec_lua("return vim.tbl_isempty({})"))
+ eq(false, exec_lua("return vim.tbl_isempty({ 1, 2, 3 })"))
+ eq(false, exec_lua("return vim.tbl_isempty({a=1, b=2, c=3})"))
+ end)
+
+ it('vim.tbl_extend', function()
+ ok(exec_lua([[
+ local a = {x = 1}
+ local b = {y = 2}
+ local c = vim.tbl_extend("keep", a, b)
+
+ return c.x == 1 and b.y == 2 and vim.tbl_count(c) == 2
+ ]]))
+
+ ok(exec_lua([[
+ local a = {x = 1}
+ local b = {y = 2}
+ local c = {z = 3}
+ local d = vim.tbl_extend("keep", a, b, c)
+
+ return d.x == 1 and d.y == 2 and d.z == 3 and vim.tbl_count(d) == 3
+ ]]))
+
+ ok(exec_lua([[
+ local a = {x = 1}
+ local b = {x = 3}
+ local c = vim.tbl_extend("keep", a, b)
+
+ return c.x == 1 and vim.tbl_count(c) == 1
+ ]]))
+
+ ok(exec_lua([[
+ local a = {x = 1}
+ local b = {x = 3}
+ local c = vim.tbl_extend("force", a, b)
+
+ return c.x == 3 and vim.tbl_count(c) == 1
+ ]]))
+
+ ok(exec_lua([[
+ local a = vim.empty_dict()
+ local b = {}
+ local c = vim.tbl_extend("keep", a, b)
+
+ return not vim.tbl_islist(c) and vim.tbl_count(c) == 0
+ ]]))
+
+ ok(exec_lua([[
+ local a = {}
+ local b = vim.empty_dict()
+ local c = vim.tbl_extend("keep", a, b)
+
+ return vim.tbl_islist(c) and vim.tbl_count(c) == 0
+ ]]))
+
+ ok(exec_lua([[
+ local a = {x = {a = 1, b = 2}}
+ local b = {x = {a = 2, c = {y = 3}}}
+ local c = vim.tbl_extend("keep", a, b)
+
+ local count = 0
+ for _ in pairs(c) do count = count + 1 end
+
+ return c.x.a == 1 and c.x.b == 2 and c.x.c == nil and count == 1
+ ]]))
+
+ eq('Error executing lua: .../shared.lua: invalid "behavior": nil',
+ pcall_err(exec_lua, [[
+ return vim.tbl_extend()
+ ]])
+ )
+
+ eq('Error executing lua: .../shared.lua: wrong number of arguments (given 1, expected at least 3)',
+ pcall_err(exec_lua, [[
+ return vim.tbl_extend("keep")
+ ]])
+ )
+
+ eq('Error executing lua: .../shared.lua: wrong number of arguments (given 2, expected at least 3)',
+ pcall_err(exec_lua, [[
+ return vim.tbl_extend("keep", {})
+ ]])
+ )
+ end)
+
+ it('vim.tbl_deep_extend', function()
+ ok(exec_lua([[
+ local a = {x = {a = 1, b = 2}}
+ local b = {x = {a = 2, c = {y = 3}}}
+ local c = vim.tbl_deep_extend("keep", a, b)
+
+ local count = 0
+ for _ in pairs(c) do count = count + 1 end
+
+ return c.x.a == 1 and c.x.b == 2 and c.x.c.y == 3 and count == 1
+ ]]))
+
+ ok(exec_lua([[
+ local a = {x = {a = 1, b = 2}}
+ local b = {x = {a = 2, c = {y = 3}}}
+ local c = vim.tbl_deep_extend("force", a, b)
+
+ local count = 0
+ for _ in pairs(c) do count = count + 1 end
+
+ return c.x.a == 2 and c.x.b == 2 and c.x.c.y == 3 and count == 1
+ ]]))
+
+ ok(exec_lua([[
+ local a = {x = {a = 1, b = 2}}
+ local b = {x = {a = 2, c = {y = 3}}}
+ local c = {x = {c = 4, d = {y = 4}}}
+ local d = vim.tbl_deep_extend("keep", a, b, c)
+
+ local count = 0
+ for _ in pairs(c) do count = count + 1 end
+
+ return d.x.a == 1 and d.x.b == 2 and d.x.c.y == 3 and d.x.d.y == 4 and count == 1
+ ]]))
+
+ ok(exec_lua([[
+ local a = {x = {a = 1, b = 2}}
+ local b = {x = {a = 2, c = {y = 3}}}
+ local c = {x = {c = 4, d = {y = 4}}}
+ local d = vim.tbl_deep_extend("force", a, b, c)
+
+ local count = 0
+ for _ in pairs(c) do count = count + 1 end
+
+ return d.x.a == 2 and d.x.b == 2 and d.x.c == 4 and d.x.d.y == 4 and count == 1
+ ]]))
+
+ ok(exec_lua([[
+ local a = vim.empty_dict()
+ local b = {}
+ local c = vim.tbl_deep_extend("keep", a, b)
+
+ local count = 0
+ for _ in pairs(c) do count = count + 1 end
+
+ return not vim.tbl_islist(c) and count == 0
+ ]]))
+
+ ok(exec_lua([[
+ local a = {}
+ local b = vim.empty_dict()
+ local c = vim.tbl_deep_extend("keep", a, b)
+
+ local count = 0
+ for _ in pairs(c) do count = count + 1 end
+
+ return vim.tbl_islist(c) and count == 0
+ ]]))
+
+ eq('Error executing lua: .../shared.lua: invalid "behavior": nil',
+ pcall_err(exec_lua, [[
+ return vim.tbl_deep_extend()
+ ]])
+ )
+
+ eq('Error executing lua: .../shared.lua: wrong number of arguments (given 1, expected at least 3)',
+ pcall_err(exec_lua, [[
+ return vim.tbl_deep_extend("keep")
+ ]])
+ )
+
+ eq('Error executing lua: .../shared.lua: wrong number of arguments (given 2, expected at least 3)',
+ pcall_err(exec_lua, [[
+ return vim.tbl_deep_extend("keep", {})
+ ]])
+ )
+ end)
+
+ it('vim.tbl_count', function()
+ eq(0, exec_lua [[ return vim.tbl_count({}) ]])
+ eq(0, exec_lua [[ return vim.tbl_count(vim.empty_dict()) ]])
+ eq(0, exec_lua [[ return vim.tbl_count({nil}) ]])
+ eq(0, exec_lua [[ return vim.tbl_count({a=nil}) ]])
+ eq(1, exec_lua [[ return vim.tbl_count({1}) ]])
+ eq(2, exec_lua [[ return vim.tbl_count({1, 2}) ]])
+ eq(2, exec_lua [[ return vim.tbl_count({1, nil, 3}) ]])
+ eq(1, exec_lua [[ return vim.tbl_count({a=1}) ]])
+ eq(2, exec_lua [[ return vim.tbl_count({a=1, b=2}) ]])
+ eq(2, exec_lua [[ return vim.tbl_count({a=1, b=nil, c=3}) ]])
+ end)
+
+ it('vim.deep_equal', function()
+ eq(true, exec_lua [[ return vim.deep_equal({a=1}, {a=1}) ]])
+ eq(true, exec_lua [[ return vim.deep_equal({a={b=1}}, {a={b=1}}) ]])
+ eq(true, exec_lua [[ return vim.deep_equal({a={b={nil}}}, {a={b={}}}) ]])
+ eq(true, exec_lua [[ return vim.deep_equal({a=1, [5]=5}, {nil,nil,nil,nil,5,a=1}) ]])
+ eq(false, exec_lua [[ return vim.deep_equal(1, {nil,nil,nil,nil,5,a=1}) ]])
+ eq(false, exec_lua [[ return vim.deep_equal(1, 3) ]])
+ eq(false, exec_lua [[ return vim.deep_equal(nil, 3) ]])
+ eq(false, exec_lua [[ return vim.deep_equal({a=1}, {a=2}) ]])
+ end)
+
+ it('vim.list_extend', function()
+ eq({1,2,3}, exec_lua [[ return vim.list_extend({1}, {2,3}) ]])
+ eq('Error executing lua: .../shared.lua: src: expected table, got nil',
+ pcall_err(exec_lua, [[ return vim.list_extend({1}, nil) ]]))
+ eq({1,2}, exec_lua [[ return vim.list_extend({1}, {2;a=1}) ]])
+ eq(true, exec_lua [[ local a = {1} return vim.list_extend(a, {2;a=1}) == a ]])
+ eq({2}, exec_lua [[ return vim.list_extend({}, {2;a=1}, 1) ]])
+ eq({}, exec_lua [[ return vim.list_extend({}, {2;a=1}, 2) ]])
+ eq({}, exec_lua [[ return vim.list_extend({}, {2;a=1}, 1, -1) ]])
+ eq({2}, exec_lua [[ return vim.list_extend({}, {2;a=1}, -1, 2) ]])
+ end)
+
+ it('vim.tbl_add_reverse_lookup', function()
+ eq(true, exec_lua [[
+ local a = { A = 1 }
+ vim.tbl_add_reverse_lookup(a)
+ return vim.deep_equal(a, { A = 1; [1] = 'A'; })
+ ]])
+ -- Throw an error for trying to do it twice (run into an existing key)
+ local code = [[
+ local res = {}
+ local a = { A = 1 }
+ vim.tbl_add_reverse_lookup(a)
+ assert(vim.deep_equal(a, { A = 1; [1] = 'A'; }))
+ vim.tbl_add_reverse_lookup(a)
+ ]]
+ matches('Error executing lua: .../shared.lua: The reverse lookup found an existing value for "[1A]" while processing key "[1A]"',
+ pcall_err(exec_lua, code))
+ end)
+
+ 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
+ eq(false, exec_lua([[return vim.api.nvim_call_function('sin', {0.0}) == 0.0 ]]))
+
+ source([[
+ func! FooFunc(test)
+ let g:test = a:test
+ return {}
+ endfunc
+ func! VarArg(...)
+ return a:000
+ endfunc
+ func! Nilly()
+ return [v:null, v:null]
+ endfunc
+ ]])
+ eq(true, exec_lua([[return next(vim.fn.FooFunc(3)) == nil ]]))
+ eq(3, eval("g:test"))
+ -- compat: nvim_call_function uses "special" value for empty dict
+ eq(true, exec_lua([[return next(vim.api.nvim_call_function("FooFunc", {5})) == true ]]))
+ eq(5, eval("g:test"))
+
+ eq({2, "foo", true}, exec_lua([[return vim.fn.VarArg(2, "foo", true)]]))
+
+ eq(true, exec_lua([[
+ local x = vim.fn.Nilly()
+ return #x == 2 and x[1] == vim.NIL and x[2] == vim.NIL
+ ]]))
+ eq({NIL, NIL}, exec_lua([[return vim.fn.Nilly()]]))
+
+ -- error handling
+ eq({false, 'Vim:E714: List required'}, exec_lua([[return {pcall(vim.fn.add, "aa", "bb")}]]))
+ end)
+
+ it('vim.rpcrequest and vim.rpcnotify', function()
+ exec_lua([[
+ chan = vim.fn.jobstart({'cat'}, {rpc=true})
+ vim.rpcrequest(chan, 'nvim_set_current_line', 'meow')
+ ]])
+ eq('meow', meths.get_current_line())
+ command("let x = [3, 'aa', v:true, v:null]")
+ eq(true, exec_lua([[
+ ret = vim.rpcrequest(chan, 'nvim_get_var', 'x')
+ return #ret == 4 and ret[1] == 3 and ret[2] == 'aa' and ret[3] == true and ret[4] == vim.NIL
+ ]]))
+ eq({3, 'aa', true, NIL}, exec_lua([[return ret]]))
+
+ eq({{}, {}, false, true}, exec_lua([[
+ vim.rpcrequest(chan, 'nvim_exec', 'let xx = {}\nlet yy = []', false)
+ local dict = vim.rpcrequest(chan, 'nvim_eval', 'xx')
+ local list = vim.rpcrequest(chan, 'nvim_eval', 'yy')
+ return {dict, list, vim.tbl_islist(dict), vim.tbl_islist(list)}
+ ]]))
+
+ exec_lua([[
+ vim.rpcrequest(chan, 'nvim_set_var', 'aa', {})
+ vim.rpcrequest(chan, 'nvim_set_var', 'bb', vim.empty_dict())
+ ]])
+ eq({1, 1}, eval('[type(g:aa) == type([]), type(g:bb) == type({})]'))
+
+ -- error handling
+ eq({false, 'Invalid channel: 23'},
+ exec_lua([[return {pcall(vim.rpcrequest, 23, 'foo')}]]))
+ eq({false, 'Invalid channel: 23'},
+ exec_lua([[return {pcall(vim.rpcnotify, 23, 'foo')}]]))
+
+ eq({false, 'Vim:E121: Undefined variable: foobar'},
+ exec_lua([[return {pcall(vim.rpcrequest, chan, 'nvim_eval', "foobar")}]]))
+
+
+ -- rpcnotify doesn't wait on request
+ eq('meow', exec_lua([[
+ vim.rpcnotify(chan, 'nvim_set_current_line', 'foo')
+ return vim.api.nvim_get_current_line()
+ ]]))
+ retry(10, nil, function()
+ eq('foo', meths.get_current_line())
+ end)
+
+ local screen = Screen.new(50,7)
+ screen:set_default_attr_ids({
+ [1] = {bold = true, foreground = Screen.colors.Blue1},
+ [2] = {bold = true, reverse = true},
+ [3] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red},
+ [4] = {bold = true, foreground = Screen.colors.SeaGreen4},
+ })
+ screen:attach()
+ exec_lua([[
+ timer = vim.loop.new_timer()
+ timer:start(20, 0, function ()
+ -- notify ok (executed later when safe)
+ vim.rpcnotify(chan, 'nvim_set_var', 'yy', {3, vim.NIL})
+ -- rpcrequest an error
+ vim.rpcrequest(chan, 'nvim_set_current_line', 'bork')
+ end)
+ ]])
+ screen:expect{grid=[[
+ foo |
+ {1:~ }|
+ {2: }|
+ {3:Error executing luv callback:} |
+ {3:[string "<nvim>"]:6: E5560: rpcrequest must not be}|
+ {3: called in a lua loop callback} |
+ {4:Press ENTER or type command to continue}^ |
+ ]]}
+ feed('<cr>')
+ eq({3, NIL}, meths.get_var('yy'))
+
+ exec_lua([[timer:close()]])
+ end)
+
+ it('vim.empty_dict()', function()
+ eq({true, false, true, true}, exec_lua([[
+ vim.api.nvim_set_var('listy', {})
+ vim.api.nvim_set_var('dicty', vim.empty_dict())
+ local listy = vim.fn.eval("listy")
+ local dicty = vim.fn.eval("dicty")
+ return {vim.tbl_islist(listy), vim.tbl_islist(dicty), next(listy) == nil, next(dicty) == nil}
+ ]]))
+
+ -- vim.empty_dict() gives new value each time
+ -- equality is not overriden (still by ref)
+ -- non-empty table uses the usual heuristics (ignores the tag)
+ eq({false, {"foo"}, {namey="bar"}}, exec_lua([[
+ local aa = vim.empty_dict()
+ local bb = vim.empty_dict()
+ local equally = (aa == bb)
+ aa[1] = "foo"
+ bb["namey"] = "bar"
+ return {equally, aa, bb}
+ ]]))
+
+ eq("{ {}, vim.empty_dict() }", exec_lua("return vim.inspect({{}, vim.empty_dict()})"))
+ eq('{}', exec_lua([[ return vim.fn.json_encode(vim.empty_dict()) ]]))
+ eq('{"a": {}, "b": []}', exec_lua([[ return vim.fn.json_encode({a=vim.empty_dict(), b={}}) ]]))
+ end)
+
+ it('vim.validate', function()
+ exec_lua("vim.validate{arg1={{}, 'table' }}")
+ exec_lua("vim.validate{arg1={{}, 't' }}")
+ exec_lua("vim.validate{arg1={nil, 't', true }}")
+ exec_lua("vim.validate{arg1={{ foo='foo' }, 't' }}")
+ exec_lua("vim.validate{arg1={{ 'foo' }, 't' }}")
+ exec_lua("vim.validate{arg1={'foo', 'string' }}")
+ exec_lua("vim.validate{arg1={'foo', 's' }}")
+ exec_lua("vim.validate{arg1={'', 's' }}")
+ exec_lua("vim.validate{arg1={nil, 's', true }}")
+ exec_lua("vim.validate{arg1={1, 'number' }}")
+ exec_lua("vim.validate{arg1={1, 'n' }}")
+ exec_lua("vim.validate{arg1={0, 'n' }}")
+ exec_lua("vim.validate{arg1={0.1, 'n' }}")
+ exec_lua("vim.validate{arg1={nil, 'n', true }}")
+ exec_lua("vim.validate{arg1={true, 'boolean' }}")
+ exec_lua("vim.validate{arg1={true, 'b' }}")
+ exec_lua("vim.validate{arg1={false, 'b' }}")
+ exec_lua("vim.validate{arg1={nil, 'b', true }}")
+ exec_lua("vim.validate{arg1={function()end, 'function' }}")
+ exec_lua("vim.validate{arg1={function()end, 'f' }}")
+ exec_lua("vim.validate{arg1={nil, 'f', true }}")
+ exec_lua("vim.validate{arg1={nil, 'nil' }}")
+ exec_lua("vim.validate{arg1={nil, 'nil', true }}")
+ exec_lua("vim.validate{arg1={coroutine.create(function()end), 'thread' }}")
+ exec_lua("vim.validate{arg1={nil, 'thread', true }}")
+ exec_lua("vim.validate{arg1={{}, 't' }, arg2={ 'foo', 's' }}")
+ exec_lua("vim.validate{arg1={2, function(a) return (a % 2) == 0 end, 'even number' }}")
+
+ eq("Error executing lua: .../shared.lua: 1: expected table, got number",
+ pcall_err(exec_lua, "vim.validate{ 1, 'x' }"))
+ eq("Error executing lua: .../shared.lua: invalid type name: x",
+ pcall_err(exec_lua, "vim.validate{ arg1={ 1, 'x' }}"))
+ eq("Error executing lua: .../shared.lua: invalid type name: 1",
+ pcall_err(exec_lua, "vim.validate{ arg1={ 1, 1 }}"))
+ eq("Error executing lua: .../shared.lua: invalid type name: nil",
+ pcall_err(exec_lua, "vim.validate{ arg1={ 1 }}"))
+
+ -- Validated parameters are required by default.
+ eq("Error executing lua: .../shared.lua: arg1: expected string, got nil",
+ pcall_err(exec_lua, "vim.validate{ arg1={ nil, 's' }}"))
+ -- Explicitly required.
+ eq("Error executing lua: .../shared.lua: arg1: expected string, got nil",
+ pcall_err(exec_lua, "vim.validate{ arg1={ nil, 's', false }}"))
+
+ eq("Error executing lua: .../shared.lua: arg1: expected table, got number",
+ pcall_err(exec_lua, "vim.validate{arg1={1, 't'}}"))
+ eq("Error executing lua: .../shared.lua: arg2: expected string, got number",
+ pcall_err(exec_lua, "vim.validate{arg1={{}, 't'}, arg2={1, 's'}}"))
+ eq("Error executing lua: .../shared.lua: arg2: expected string, got nil",
+ pcall_err(exec_lua, "vim.validate{arg1={{}, 't'}, arg2={nil, 's'}}"))
+ eq("Error executing lua: .../shared.lua: arg2: expected string, got nil",
+ pcall_err(exec_lua, "vim.validate{arg1={{}, 't'}, arg2={nil, 's'}}"))
+ eq("Error executing lua: .../shared.lua: arg1: expected even number, got 3",
+ pcall_err(exec_lua, "vim.validate{arg1={3, function(a) return a == 1 end, 'even number'}}"))
+ eq("Error executing lua: .../shared.lua: arg1: expected ?, got 3",
+ pcall_err(exec_lua, "vim.validate{arg1={3, function(a) return a == 1 end}}"))
+ end)
+
+ it('vim.is_callable', function()
+ eq(true, exec_lua("return vim.is_callable(function()end)"))
+ eq(true, exec_lua([[
+ local meta = { __call = function()end }
+ local function new_callable()
+ return setmetatable({}, meta)
+ end
+ local callable = new_callable()
+ return vim.is_callable(callable)
+ ]]))
+
+ eq(false, exec_lua("return vim.is_callable(1)"))
+ eq(false, exec_lua("return vim.is_callable('foo')"))
+ eq(false, exec_lua("return vim.is_callable({})"))
+ end)
+
+ it('vim.g', function()
+ exec_lua [[
+ vim.api.nvim_set_var("testing", "hi")
+ vim.api.nvim_set_var("other", 123)
+ vim.api.nvim_set_var("to_delete", {hello="world"})
+ ]]
+
+ eq('hi', funcs.luaeval "vim.g.testing")
+ eq(123, funcs.luaeval "vim.g.other")
+ eq(NIL, funcs.luaeval "vim.g.nonexistant")
+
+ eq({hello="world"}, funcs.luaeval "vim.g.to_delete")
+ exec_lua [[
+ vim.g.to_delete = nil
+ ]]
+ eq(NIL, funcs.luaeval "vim.g.to_delete")
+ end)
+
+ it('vim.b', function()
+ exec_lua [[
+ vim.api.nvim_buf_set_var(0, "testing", "hi")
+ vim.api.nvim_buf_set_var(0, "other", 123)
+ vim.api.nvim_buf_set_var(0, "to_delete", {hello="world"})
+ ]]
+
+ eq('hi', funcs.luaeval "vim.b.testing")
+ eq(123, funcs.luaeval "vim.b.other")
+ eq(NIL, funcs.luaeval "vim.b.nonexistant")
+
+ eq({hello="world"}, funcs.luaeval "vim.b.to_delete")
+ exec_lua [[
+ vim.b.to_delete = nil
+ ]]
+ eq(NIL, funcs.luaeval "vim.b.to_delete")
+
+ exec_lua [[
+ vim.cmd "vnew"
+ ]]
+
+ eq(NIL, funcs.luaeval "vim.b.testing")
+ eq(NIL, funcs.luaeval "vim.b.other")
+ eq(NIL, funcs.luaeval "vim.b.nonexistant")
+ end)
+
+ it('vim.w', function()
+ exec_lua [[
+ vim.api.nvim_win_set_var(0, "testing", "hi")
+ vim.api.nvim_win_set_var(0, "other", 123)
+ vim.api.nvim_win_set_var(0, "to_delete", {hello="world"})
+ ]]
+
+ eq('hi', funcs.luaeval "vim.w.testing")
+ eq(123, funcs.luaeval "vim.w.other")
+ eq(NIL, funcs.luaeval "vim.w.nonexistant")
+
+ eq({hello="world"}, funcs.luaeval "vim.w.to_delete")
+ exec_lua [[
+ vim.w.to_delete = nil
+ ]]
+ eq(NIL, funcs.luaeval "vim.w.to_delete")
+
+ exec_lua [[
+ vim.cmd "vnew"
+ ]]
+
+ eq(NIL, funcs.luaeval "vim.w.testing")
+ eq(NIL, funcs.luaeval "vim.w.other")
+ eq(NIL, funcs.luaeval "vim.w.nonexistant")
+ end)
+
+ it('vim.t', function()
+ exec_lua [[
+ vim.api.nvim_tabpage_set_var(0, "testing", "hi")
+ vim.api.nvim_tabpage_set_var(0, "other", 123)
+ vim.api.nvim_tabpage_set_var(0, "to_delete", {hello="world"})
+ ]]
+
+ eq('hi', funcs.luaeval "vim.t.testing")
+ eq(123, funcs.luaeval "vim.t.other")
+ eq(NIL, funcs.luaeval "vim.t.nonexistant")
+
+ eq({hello="world"}, funcs.luaeval "vim.t.to_delete")
+ exec_lua [[
+ vim.t.to_delete = nil
+ ]]
+ eq(NIL, funcs.luaeval "vim.t.to_delete")
+
+ exec_lua [[
+ vim.cmd "tabnew"
+ ]]
+
+ eq(NIL, funcs.luaeval "vim.t.testing")
+ eq(NIL, funcs.luaeval "vim.t.other")
+ eq(NIL, funcs.luaeval "vim.t.nonexistant")
+ end)
+
+ it('vim.env', function()
+ exec_lua [[
+ vim.fn.setenv("A", 123)
+ ]]
+ eq('123', funcs.luaeval "vim.env.A")
+ eq(true, funcs.luaeval "vim.env.B == nil")
+ end)
+
+ it('vim.v', function()
+ eq(funcs.luaeval "vim.api.nvim_get_vvar('progpath')", funcs.luaeval "vim.v.progpath")
+ eq(false, funcs.luaeval "vim.v['false']")
+ eq(NIL, funcs.luaeval "vim.v.null")
+ end)
+
+ it('vim.bo', function()
+ eq('', funcs.luaeval "vim.bo.filetype")
+ exec_lua [[
+ vim.api.nvim_buf_set_option(0, "filetype", "markdown")
+ BUF = vim.api.nvim_create_buf(false, true)
+ vim.api.nvim_buf_set_option(BUF, "modifiable", false)
+ ]]
+ eq(false, funcs.luaeval "vim.bo.modified")
+ eq('markdown', funcs.luaeval "vim.bo.filetype")
+ eq(false, funcs.luaeval "vim.bo[BUF].modifiable")
+ exec_lua [[
+ vim.bo.filetype = ''
+ vim.bo[BUF].modifiable = true
+ ]]
+ eq('', funcs.luaeval "vim.bo.filetype")
+ eq(true, funcs.luaeval "vim.bo[BUF].modifiable")
+ matches("^Error executing lua: .*: Invalid option name: 'nosuchopt'$",
+ pcall_err(exec_lua, 'return vim.bo.nosuchopt'))
+ matches("^Error executing lua: .*: Expected lua string$",
+ pcall_err(exec_lua, 'return vim.bo[0][0].autoread'))
+ end)
+
+ it('vim.wo', function()
+ exec_lua [[
+ vim.api.nvim_win_set_option(0, "cole", 2)
+ vim.cmd "split"
+ vim.api.nvim_win_set_option(0, "cole", 2)
+ ]]
+ eq(2, funcs.luaeval "vim.wo.cole")
+ exec_lua [[
+ vim.wo.conceallevel = 0
+ ]]
+ eq(0, funcs.luaeval "vim.wo.cole")
+ eq(0, funcs.luaeval "vim.wo[0].cole")
+ eq(0, funcs.luaeval "vim.wo[1001].cole")
+ matches("^Error executing lua: .*: Invalid option name: 'notanopt'$",
+ pcall_err(exec_lua, 'return vim.wo.notanopt'))
+ matches("^Error executing lua: .*: Expected lua string$",
+ pcall_err(exec_lua, 'return vim.wo[0][0].list'))
+ eq(2, funcs.luaeval "vim.wo[1000].cole")
+ exec_lua [[
+ vim.wo[1000].cole = 0
+ ]]
+ eq(0, funcs.luaeval "vim.wo[1000].cole")
+ end)
+
+ it('vim.cmd', function()
+ exec_lua [[
+ vim.cmd "autocmd BufNew * ++once lua BUF = vim.fn.expand('<abuf>')"
+ vim.cmd "new"
+ ]]
+ eq('2', funcs.luaeval "BUF")
+ eq(2, funcs.luaeval "#vim.api.nvim_list_bufs()")
+ end)
+
+ it('vim.regex', function()
+ exec_lua [[
+ re1 = vim.regex"ab\\+c"
+ vim.cmd "set nomagic ignorecase"
+ re2 = vim.regex"xYz"
+ ]]
+ eq({}, exec_lua[[return {re1:match_str("x ac")}]])
+ eq({3,7}, exec_lua[[return {re1:match_str("ac abbc")}]])
+
+ meths.buf_set_lines(0, 0, -1, true, {"yy", "abc abbc"})
+ eq({}, exec_lua[[return {re1:match_line(0, 0)}]])
+ eq({0,3}, exec_lua[[return {re1:match_line(0, 1)}]])
+ eq({3,7}, exec_lua[[return {re1:match_line(0, 1, 1)}]])
+ eq({3,7}, exec_lua[[return {re1:match_line(0, 1, 1, 8)}]])
+ eq({}, exec_lua[[return {re1:match_line(0, 1, 1, 7)}]])
+ eq({0,3}, exec_lua[[return {re1:match_line(0, 1, 0, 7)}]])
+ end)
+
+ it('vim.defer_fn', function()
+ eq(false, exec_lua [[
+ vim.g.test = false
+ vim.defer_fn(function() vim.g.test = true end, 150)
+ return vim.g.test
+ ]])
+ exec_lua [[vim.wait(1000, function() return vim.g.test end)]]
+ eq(true, exec_lua[[return vim.g.test]])
+ end)
+
+ it('vim.region', function()
+ helpers.insert(helpers.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] ]])
+ end)
+
+ describe('vim.wait', function()
+ before_each(function()
+ exec_lua[[
+ -- high precision timer
+ get_time = function()
+ return vim.fn.reltimefloat(vim.fn.reltime())
+ end
+ ]]
+ end)
+
+ it('should run from lua', function()
+ exec_lua[[vim.wait(100, function() return true end)]]
+ end)
+
+ it('should wait the expected time if false', function()
+ eq({time = true, wait_result = {false, -1}}, exec_lua[[
+ start_time = get_time()
+ wait_succeed, wait_fail_val = vim.wait(200, function() return false end)
+
+ return {
+ -- 150ms waiting or more results in true. Flaky tests are bad.
+ time = (start_time + 0.15) < get_time(),
+ wait_result = {wait_succeed, wait_fail_val}
+ }
+ ]])
+ 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:start(100, 0, vim.schedule_wrap(function()
+ vim.g.timer_result = true
+ end))
+
+ -- Would wait ten seconds if results blocked.
+ wait_result = vim.wait(10000, function() return vim.g.timer_result end)
+
+ return {
+ time = (start_time + 5) > get_time(),
+ wait_result = wait_result,
+ }
+ ]])
+ end)
+
+ it('should work with vim.defer_fn', function()
+ eq({time = true, wait_result = true}, exec_lua[[
+ start_time = get_time()
+
+ vim.defer_fn(function() vim.g.timer_result = true end, 100)
+ wait_result = vim.wait(10000, function() return vim.g.timer_result end)
+
+ return {
+ time = (start_time + 5) > get_time(),
+ wait_result = wait_result,
+ }
+ ]])
+ end)
+
+ it('should require functions to be passed', function()
+ local pcall_result = exec_lua [[
+ return {pcall(function() vim.wait(1000, 13) end)}
+ ]]
+
+ eq(pcall_result[1], false)
+ matches('condition must be a function', pcall_result[2])
+ end)
+
+ it('should not crash when callback errors', function()
+ local pcall_result = exec_lua [[
+ return {pcall(function() vim.wait(1000, function() error("As Expected") end) end)}
+ ]]
+
+ eq(pcall_result[1], false)
+ matches('As Expected', pcall_result[2])
+ end)
+
+ it('should call callbacks exactly once if they return true immediately', function()
+ eq(true, exec_lua [[
+ vim.g.wait_count = 0
+ vim.wait(1000, function()
+ vim.g.wait_count = vim.g.wait_count + 1
+ return true
+ end, 20)
+ return vim.g.wait_count == 1
+ ]])
+ end)
+
+ it('should call callbacks few times with large `interval`', function()
+ eq(true, exec_lua [[
+ vim.g.wait_count = 0
+ vim.wait(50, function() vim.g.wait_count = vim.g.wait_count + 1 end, 200)
+ return vim.g.wait_count < 5
+ ]])
+ end)
+
+ it('should play nice with `not` when fails', function()
+ eq(true, exec_lua [[
+ if not vim.wait(50, function() end) then
+ return true
+ end
+
+ return false
+ ]])
+ end)
+
+ it('should play nice with `if` when success', function()
+ eq(true, exec_lua [[
+ if vim.wait(50, function() return true end) then
+ return true
+ end
+
+ return false
+ ]])
+ end)
+
+ it('should return immediately with false if timeout is 0', function()
+ eq({false, -1}, exec_lua [[
+ return {
+ vim.wait(0, function() return false end)
+ }
+ ]])
+ end)
+
+ it('should work with tables with __call', function()
+ eq(true, exec_lua [[
+ local t = setmetatable({}, {__call = function(...) return true end})
+ return vim.wait(100, t, 10)
+ ]])
+ end)
+
+ it('should work with tables with __call that change', function()
+ eq(true, exec_lua [[
+ local t = {count = 0}
+ setmetatable(t, {
+ __call = function()
+ t.count = t.count + 1
+ return t.count > 3
+ end
+ })
+
+ return vim.wait(1000, t, 10)
+ ]])
+ end)
+
+ it('should not work with negative intervals', function()
+ local pcall_result = exec_lua [[
+ return pcall(function() vim.wait(1000, function() return false end, -1) end)
+ ]]
+
+ eq(false, pcall_result)
+ end)
+
+ it('should not work with weird intervals', function()
+ local pcall_result = exec_lua [[
+ return pcall(function() vim.wait(1000, function() return false end, 'a string value') end)
+ ]]
+
+ eq(false, pcall_result)
+ end)
+ end)
+end)
diff --git a/test/functional/normal/jump_spec.lua b/test/functional/normal/jump_spec.lua
index 5bed541752..9e7158e2f7 100644
--- a/test/functional/normal/jump_spec.lua
+++ b/test/functional/normal/jump_spec.lua
@@ -5,6 +5,7 @@ local command = helpers.command
local eq = helpers.eq
local funcs = helpers.funcs
local feed = helpers.feed
+local redir_exec = helpers.redir_exec
local write_file = helpers.write_file
describe('jumplist', function()
@@ -46,3 +47,93 @@ describe('jumplist', function()
eq(buf1, funcs.bufnr('%'))
end)
end)
+
+describe("jumpoptions=stack behaves like 'tagstack'", function()
+ before_each(function()
+ clear()
+ feed(':clearjumps<cr>')
+
+ -- Add lines so that we have locations to jump to.
+ for i = 1,101,1
+ do
+ feed('iLine ' .. i .. '<cr><esc>')
+ end
+
+ -- Jump around to add some locations to the jump list.
+ feed('0gg')
+ feed('10gg')
+ feed('20gg')
+ feed('30gg')
+ feed('40gg')
+ feed('50gg')
+
+ feed(':set jumpoptions=stack<cr>')
+ end)
+
+ after_each(function()
+ feed('set jumpoptions=')
+ end)
+
+ it('discards the tail when navigating from the middle', function()
+ feed('<C-O>')
+ feed('<C-O>')
+
+ eq( '\n'
+ .. ' jump line col file/text\n'
+ .. ' 4 102 0 \n'
+ .. ' 3 1 0 Line 1\n'
+ .. ' 2 10 0 Line 10\n'
+ .. ' 1 20 0 Line 20\n'
+ .. '> 0 30 0 Line 30\n'
+ .. ' 1 40 0 Line 40\n'
+ .. ' 2 50 0 Line 50',
+ redir_exec('jumps'))
+
+ feed('90gg')
+
+ eq( '\n'
+ .. ' jump line col file/text\n'
+ .. ' 5 102 0 \n'
+ .. ' 4 1 0 Line 1\n'
+ .. ' 3 10 0 Line 10\n'
+ .. ' 2 20 0 Line 20\n'
+ .. ' 1 30 0 Line 30\n'
+ .. '>',
+ redir_exec('jumps'))
+ end)
+
+ it('does not add the same location twice adjacently', function()
+ feed('60gg')
+ feed('60gg')
+
+ eq( '\n'
+ .. ' jump line col file/text\n'
+ .. ' 7 102 0 \n'
+ .. ' 6 1 0 Line 1\n'
+ .. ' 5 10 0 Line 10\n'
+ .. ' 4 20 0 Line 20\n'
+ .. ' 3 30 0 Line 30\n'
+ .. ' 2 40 0 Line 40\n'
+ .. ' 1 50 0 Line 50\n'
+ .. '>',
+ redir_exec('jumps'))
+ end)
+
+ it('does add the same location twice nonadjacently', function()
+ feed('10gg')
+ feed('20gg')
+
+ eq( '\n'
+ .. ' jump line col file/text\n'
+ .. ' 8 102 0 \n'
+ .. ' 7 1 0 Line 1\n'
+ .. ' 6 10 0 Line 10\n'
+ .. ' 5 20 0 Line 20\n'
+ .. ' 4 30 0 Line 30\n'
+ .. ' 3 40 0 Line 40\n'
+ .. ' 2 50 0 Line 50\n'
+ .. ' 1 10 0 Line 10\n'
+ .. '>',
+ redir_exec('jumps'))
+ end)
+end)
diff --git a/test/functional/normal/put_spec.lua b/test/functional/normal/put_spec.lua
index 40a4f051e3..26967ecbba 100644
--- a/test/functional/normal/put_spec.lua
+++ b/test/functional/normal/put_spec.lua
@@ -6,8 +6,8 @@ local insert = helpers.insert
local feed = helpers.feed
local expect = helpers.expect
local eq = helpers.eq
-local map = helpers.map
-local filter = helpers.filter
+local map = helpers.tbl_map
+local filter = helpers.tbl_filter
local feed_command = helpers.feed_command
local curbuf_contents = helpers.curbuf_contents
local funcs = helpers.funcs
@@ -307,7 +307,7 @@ describe('put command', function()
-- }}}
-- Conversion functions {{{
- local function convert_characterwise(expect_base, conversion_table,
+ local function convert_charwise(expect_base, conversion_table,
virtualedit_end, visual_put)
expect_base = dedent(expect_base)
-- There is no difference between 'P' and 'p' when VIsual_active
@@ -335,7 +335,7 @@ describe('put command', function()
expect_base = expect_base:gsub('(test_stringx?)"', '%1.')
end
return expect_base
- end -- convert_characterwise()
+ end -- convert_charwise()
local function make_back(string)
local prev_line
@@ -500,7 +500,7 @@ describe('put command', function()
local function run_normal_mode_tests(test_string, base_map, extra_setup,
virtualedit_end, selection_string)
local function convert_closure(e, c)
- return convert_characterwise(e, c, virtualedit_end, selection_string)
+ return convert_charwise(e, c, virtualedit_end, selection_string)
end
local function expect_normal_creator(expect_base, conversion_table)
local test_expect = expect_creator(convert_closure, expect_base, conversion_table)
diff --git a/test/functional/normal/tabpage_spec.lua b/test/functional/normal/tabpage_spec.lua
new file mode 100644
index 0000000000..d1d6854b07
--- /dev/null
+++ b/test/functional/normal/tabpage_spec.lua
@@ -0,0 +1,38 @@
+local helpers = require('test.functional.helpers')(after_each)
+
+local clear = helpers.clear
+local command = helpers.command
+local eq = helpers.eq
+local feed = helpers.feed
+local eval = helpers.eval
+
+describe('tabpage', function()
+ before_each(clear)
+
+ it('advances to the next page via <C-W>gt', function()
+ -- add some tabpages
+ command('tabnew')
+ command('tabnew')
+ command('tabnew')
+
+ eq(4, eval('tabpagenr()'))
+
+ feed('<C-W>gt')
+
+ eq(1, eval('tabpagenr()'))
+ end)
+
+ it('retreats to the previous page via <C-W>gT', function()
+ -- add some tabpages
+ command('tabnew')
+ command('tabnew')
+ command('tabnew')
+
+ eq(4, eval('tabpagenr()'))
+
+ feed('<C-W>gT')
+
+ eq(3, eval('tabpagenr()'))
+ end)
+end)
+
diff --git a/test/functional/options/chars_spec.lua b/test/functional/options/chars_spec.lua
index 1330c29e61..5439ca3dba 100644
--- a/test/functional/options/chars_spec.lua
+++ b/test/functional/options/chars_spec.lua
@@ -16,10 +16,6 @@ describe("'fillchars'", function()
screen:attach()
end)
- after_each(function()
- screen:detach()
- end)
-
local function shouldfail(val,errval)
errval = errval or val
eq('Vim(set):E474: Invalid argument: fillchars='..errval,
@@ -71,16 +67,29 @@ describe("'fillchars'", function()
shouldfail('eob:xy') -- two ascii chars
shouldfail('eob:\255', 'eob:<ff>') -- invalid UTF-8
end)
- it('is local to window', function()
- clear()
- screen = Screen.new(50, 5)
- screen:attach()
+ it('has global value', function()
+ screen:try_resize(50, 5)
insert("foo\nbar")
command('set laststatus=0')
command('1,2fold')
command('vsplit')
command('set fillchars=fold:x')
screen:expect([[
+ ^+-- 2 lines: fooxxxxxxxx│+-- 2 lines: fooxxxxxxx|
+ ~ │~ |
+ ~ │~ |
+ ~ │~ |
+ |
+ ]])
+ end)
+ it('has local window value', function()
+ screen:try_resize(50, 5)
+ insert("foo\nbar")
+ command('set laststatus=0')
+ command('1,2fold')
+ command('vsplit')
+ command('setl fillchars=fold:x')
+ screen:expect([[
^+-- 2 lines: fooxxxxxxxx│+-- 2 lines: foo·······|
~ │~ |
~ │~ |
@@ -100,16 +109,25 @@ describe("'listchars'", function()
screen:attach()
end)
- after_each(function()
- screen:detach()
+ it('has global value', function()
+ feed('i<tab><tab><tab><esc>')
+ command('set list laststatus=0')
+ command('vsplit')
+ command('set listchars=tab:<->')
+ screen:expect([[
+ <------><------>^<------> │<------><------><------>|
+ ~ │~ |
+ ~ │~ |
+ ~ │~ |
+ |
+ ]])
end)
-
- it('is local to window', function()
+ it('has value local to window', function()
feed('i<tab><tab><tab><esc>')
- command('set laststatus=0')
- command('set list listchars=tab:<->')
+ command('set list laststatus=0')
+ command('setl listchars=tab:<->')
command('vsplit')
- command('set listchars&')
+ command('setl listchars<')
screen:expect([[
> > ^> │<------><------><------>|
~ │~ |
diff --git a/test/functional/options/defaults_spec.lua b/test/functional/options/defaults_spec.lua
index 490a04186d..11ce26410d 100644
--- a/test/functional/options/defaults_spec.lua
+++ b/test/functional/options/defaults_spec.lua
@@ -224,9 +224,6 @@ describe('startup defaults', function()
XDG_DATA_HOME=xdgdir,
NVIM_LOG_FILE='', -- Empty is invalid.
}})
- -- server_start() calls ELOG, which tickles log_path_init().
- pcall(command, 'call serverstart(serverlist()[0])')
-
eq(xdgdir..'/'..datasubdir..'/log', string.gsub(eval('$NVIM_LOG_FILE'), '\\', '/'))
end)
it('defaults to stdpath("data")/log if invalid', function()
@@ -235,9 +232,6 @@ describe('startup defaults', function()
XDG_DATA_HOME=xdgdir,
NVIM_LOG_FILE='.', -- Any directory is invalid.
}})
- -- server_start() calls ELOG, which tickles log_path_init().
- pcall(command, 'call serverstart(serverlist()[0])')
-
eq(xdgdir..'/'..datasubdir..'/log', string.gsub(eval('$NVIM_LOG_FILE'), '\\', '/'))
end)
it('defaults to .nvimlog if stdpath("data") is invalid', function()
@@ -245,9 +239,6 @@ describe('startup defaults', function()
XDG_DATA_HOME='Xtest-missing-xdg-dir',
NVIM_LOG_FILE='.', -- Any directory is invalid.
}})
- -- server_start() calls ELOG, which tickles log_path_init().
- pcall(command, 'call serverstart(serverlist()[0])')
-
eq('.nvimlog', eval('$NVIM_LOG_FILE'))
end)
end)
@@ -302,6 +293,14 @@ describe('XDG-based defaults', function()
-- TODO(jkeyes): tests below fail on win32 because of path separator.
if helpers.pending_win32(pending) then return end
+ local function vimruntime_and_libdir()
+ local vimruntime = eval('$VIMRUNTIME')
+ -- libdir is hard to calculate reliably across various ci platforms
+ -- local libdir = string.gsub(vimruntime, "share/nvim/runtime$", "lib/nvim")
+ local libdir = meths._get_lib_dir()
+ return vimruntime, libdir
+ end
+
describe('with too long XDG variables', function()
before_each(function()
clear({env={
@@ -317,6 +316,8 @@ describe('XDG-based defaults', function()
end)
it('are correctly set', function()
+ local vimruntime, libdir = vimruntime_and_libdir()
+
eq((('/x'):rep(4096) .. '/nvim'
.. ',' .. ('/a'):rep(2048) .. '/nvim'
.. ',' .. ('/b'):rep(2048) .. '/nvim'
@@ -325,7 +326,8 @@ describe('XDG-based defaults', function()
.. ',' .. ('/A'):rep(2048) .. '/nvim/site'
.. ',' .. ('/B'):rep(2048) .. '/nvim/site'
.. (',' .. '/C/nvim/site'):rep(512)
- .. ',' .. eval('$VIMRUNTIME')
+ .. ',' .. vimruntime
+ .. ',' .. libdir
.. (',' .. '/C/nvim/site/after'):rep(512)
.. ',' .. ('/B'):rep(2048) .. '/nvim/site/after'
.. ',' .. ('/A'):rep(2048) .. '/nvim/site/after'
@@ -348,7 +350,8 @@ describe('XDG-based defaults', function()
.. ',' .. ('/A'):rep(2048) .. '/nvim/site'
.. ',' .. ('/B'):rep(2048) .. '/nvim/site'
.. (',' .. '/C/nvim/site'):rep(512)
- .. ',' .. eval('$VIMRUNTIME')
+ .. ',' .. vimruntime
+ .. ',' .. libdir
.. (',' .. '/C/nvim/site/after'):rep(512)
.. ',' .. ('/B'):rep(2048) .. '/nvim/site/after'
.. ',' .. ('/A'):rep(2048) .. '/nvim/site/after'
@@ -377,11 +380,13 @@ describe('XDG-based defaults', function()
end)
it('are not expanded', function()
+ local vimruntime, libdir = vimruntime_and_libdir()
eq(('$XDG_DATA_HOME/nvim'
.. ',$XDG_DATA_DIRS/nvim'
.. ',$XDG_CONFIG_HOME/nvim/site'
.. ',$XDG_CONFIG_DIRS/nvim/site'
- .. ',' .. eval('$VIMRUNTIME')
+ .. ',' .. vimruntime
+ .. ',' .. libdir
.. ',$XDG_CONFIG_DIRS/nvim/site/after'
.. ',$XDG_CONFIG_HOME/nvim/site/after'
.. ',$XDG_DATA_DIRS/nvim/after'
@@ -396,7 +401,8 @@ describe('XDG-based defaults', function()
.. ',$XDG_DATA_DIRS/nvim'
.. ',$XDG_CONFIG_HOME/nvim/site'
.. ',$XDG_CONFIG_DIRS/nvim/site'
- .. ',' .. eval('$VIMRUNTIME')
+ .. ',' .. vimruntime
+ .. ',' .. libdir
.. ',$XDG_CONFIG_DIRS/nvim/site/after'
.. ',$XDG_CONFIG_HOME/nvim/site/after'
.. ',$XDG_DATA_DIRS/nvim/after'
@@ -411,7 +417,8 @@ describe('XDG-based defaults', function()
.. ',$XDG_DATA_DIRS/nvim'
.. ',$XDG_CONFIG_HOME/nvim/site'
.. ',$XDG_CONFIG_DIRS/nvim/site'
- .. ',' .. eval('$VIMRUNTIME')
+ .. ',' .. vimruntime
+ .. ',' .. libdir
.. ',$XDG_CONFIG_DIRS/nvim/site/after'
.. ',$XDG_CONFIG_HOME/nvim/site/after'
.. ',$XDG_DATA_DIRS/nvim/after'
@@ -435,13 +442,15 @@ describe('XDG-based defaults', function()
end)
it('are escaped properly', function()
+ local vimruntime, libdir = vimruntime_and_libdir()
eq(('\\, \\, \\,/nvim'
.. ',\\,-\\,-\\,/nvim'
.. ',-\\,-\\,-/nvim'
.. ',\\,=\\,=\\,/nvim/site'
.. ',\\,≡\\,≡\\,/nvim/site'
.. ',≡\\,≡\\,≡/nvim/site'
- .. ',' .. eval('$VIMRUNTIME')
+ .. ',' .. vimruntime
+ .. ',' .. libdir
.. ',≡\\,≡\\,≡/nvim/site/after'
.. ',\\,≡\\,≡\\,/nvim/site/after'
.. ',\\,=\\,=\\,/nvim/site/after'
@@ -460,7 +469,8 @@ describe('XDG-based defaults', function()
.. ',\\,=\\,=\\,/nvim/site'
.. ',\\,≡\\,≡\\,/nvim/site'
.. ',≡\\,≡\\,≡/nvim/site'
- .. ',' .. eval('$VIMRUNTIME')
+ .. ',' .. vimruntime
+ .. ',' .. libdir
.. ',≡\\,≡\\,≡/nvim/site/after'
.. ',\\,≡\\,≡\\,/nvim/site/after'
.. ',\\,=\\,=\\,/nvim/site/after'
diff --git a/test/functional/options/keymap_spec.lua b/test/functional/options/keymap_spec.lua
index 7f6d623dc7..52a714f7a8 100644
--- a/test/functional/options/keymap_spec.lua
+++ b/test/functional/options/keymap_spec.lua
@@ -30,7 +30,7 @@ describe("'keymap' / :lmap", function()
command('lmapclear <buffer>')
command('set keymap=dvorak')
command('set nomore')
- local bindings = funcs.nvim_command_output('lmap')
+ local bindings = funcs.nvim_exec('lmap', true)
eq(dedent([[
l " @_
diff --git a/test/functional/options/num_options_spec.lua b/test/functional/options/num_options_spec.lua
index deda5c9118..4754c14f5b 100644
--- a/test/functional/options/num_options_spec.lua
+++ b/test/functional/options/num_options_spec.lua
@@ -65,14 +65,12 @@ describe(':set validation', function()
should_succeed('regexpengine', 2)
should_fail('report', -1, 'E487')
should_succeed('report', 0)
- should_fail('scrolloff', -1, 'E49')
- should_fail('sidescrolloff', -1, 'E487')
should_fail('sidescroll', -1, 'E487')
should_fail('cmdwinheight', 0, 'E487')
should_fail('updatetime', -1, 'E487')
should_fail('foldlevel', -5, 'E487')
- should_fail('foldcolumn', 13, 'E474')
+ should_fail('foldcolumn', '13', 'E474')
should_fail('conceallevel', 4, 'E474')
should_fail('numberwidth', 21, 'E474')
should_fail('numberwidth', 0, 'E487')
@@ -82,6 +80,22 @@ describe(':set validation', function()
meths.set_option('window', -10)
eq(23, meths.get_option('window'))
eq('', eval("v:errmsg"))
+
+ -- 'scrolloff' and 'sidescrolloff' can have a -1 value when
+ -- set for the current window, but not globally
+ feed_command('setglobal scrolloff=-1')
+ eq('E487', eval("v:errmsg"):match("E%d*"))
+
+ feed_command('setglobal sidescrolloff=-1')
+ eq('E487', eval("v:errmsg"):match("E%d*"))
+
+ feed_command('let v:errmsg=""')
+
+ feed_command('setlocal scrolloff=-1')
+ eq('', eval("v:errmsg"))
+
+ feed_command('setlocal sidescrolloff=-1')
+ eq('', eval("v:errmsg"))
end)
it('set wmh/wh wmw/wiw checks', function()
diff --git a/test/functional/options/shortmess_spec.lua b/test/functional/options/shortmess_spec.lua
index 8ea9a19464..a56e9c09b4 100644
--- a/test/functional/options/shortmess_spec.lua
+++ b/test/functional/options/shortmess_spec.lua
@@ -25,7 +25,7 @@ describe("'shortmess'", function()
~ |
~ |
~ |
- "foo" [New File] |
+ "foo" [New] |
]])
eq(1, eval('bufnr("%")'))
@@ -50,7 +50,7 @@ describe("'shortmess'", function()
~ |
~ |
~ |
- "foo" [New File] |
+ "foo" [New] |
]])
eq(1, eval('bufnr("%")'))
feed(':edit bar<CR>')
@@ -59,7 +59,7 @@ describe("'shortmess'", function()
~ |
~ |
~ |
- "bar" [New File] |
+ "bar" [New] |
]])
eq(2, eval('bufnr("%")'))
feed(':bprevious<CR>')
@@ -68,7 +68,7 @@ describe("'shortmess'", function()
~ |
~ |
~ |
- "foo" [New file] --No lines in buffer-- |
+ "foo" [New] --No lines in buffer-- |
]])
eq(1, eval('bufnr("%")'))
diff --git a/test/functional/plugin/health_spec.lua b/test/functional/plugin/health_spec.lua
index 3525e235de..a78ed07876 100644
--- a/test/functional/plugin/health_spec.lua
+++ b/test/functional/plugin/health_spec.lua
@@ -116,8 +116,6 @@ describe('health.vim', function()
screen:set_default_attr_ids({
Ok = { foreground = Screen.colors.Grey3, background = 6291200 },
Error = { foreground = Screen.colors.Grey100, background = Screen.colors.Red },
- })
- screen:set_default_attr_ignore({
Heading = { bold=true, foreground=Screen.colors.Magenta },
Heading2 = { foreground = Screen.colors.SlateBlue },
Bar = { foreground=Screen.colors.Purple },
@@ -126,18 +124,18 @@ describe('health.vim', function()
command("checkhealth foo success1")
command("1tabclose")
command("set laststatus=0")
- screen:expect([[
+ screen:expect{grid=[[
^ |
- health#foo#check |
- ========================================================================|
- - {Error:ERROR:} No healthcheck found for "foo" plugin. |
+ {Heading:health#foo#check} |
+ {Bar:========================================================================}|
+ {Bullet: -} {Error:ERROR:} No healthcheck found for "foo" plugin. |
|
- health#success1#check |
- ========================================================================|
- ## report 1 |
- - {Ok:OK:} everything is fine |
+ {Heading:health#success1#check} |
+ {Bar:========================================================================}|
+ {Heading2:##}{Heading: report 1} |
+ {Bullet: -} {Ok:OK:} everything is fine |
|
- ]])
+ ]]}
end)
it("gracefully handles invalid healthcheck", function()
diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua
new file mode 100644
index 0000000000..1002011999
--- /dev/null
+++ b/test/functional/plugin/lsp_spec.lua
@@ -0,0 +1,1620 @@
+local helpers = require('test.functional.helpers')(after_each)
+
+local assert_log = helpers.assert_log
+local clear = helpers.clear
+local buf_lines = helpers.buf_lines
+local dedent = helpers.dedent
+local exec_lua = helpers.exec_lua
+local eq = helpers.eq
+local pcall_err = helpers.pcall_err
+local pesc = helpers.pesc
+local insert = helpers.insert
+local retry = helpers.retry
+local NIL = helpers.NIL
+
+-- Use these to get access to a coroutine so that I can run async tests and use
+-- yield.
+local run, stop = helpers.run, helpers.stop
+
+-- TODO(justinmk): hangs on Windows https://github.com/neovim/neovim/pull/11837
+if helpers.pending_win32(pending) then return end
+
+-- Fake LSP server.
+local fake_lsp_code = 'test/functional/fixtures/fake-lsp-server.lua'
+local fake_lsp_logfile = 'Xtest-fake-lsp.log'
+
+teardown(function()
+ os.remove(fake_lsp_logfile)
+end)
+
+local function fake_lsp_server_setup(test_name, timeout_ms)
+ exec_lua([=[
+ lsp = require('vim.lsp')
+ local test_name, fixture_filename, logfile, timeout = ...
+ TEST_RPC_CLIENT_ID = lsp.start_client {
+ cmd_env = {
+ NVIM_LOG_FILE = logfile;
+ };
+ cmd = {
+ vim.v.progpath, '-Es', '-u', 'NONE', '--headless',
+ "-c", string.format("lua TEST_NAME = %q", test_name),
+ "-c", string.format("lua TIMEOUT = %d", timeout),
+ "-c", "luafile "..fixture_filename,
+ };
+ callbacks = setmetatable({}, {
+ __index = function(t, method)
+ return function(...)
+ return vim.rpcrequest(1, 'callback', ...)
+ end
+ end;
+ });
+ root_dir = vim.loop.cwd();
+ on_init = function(client, result)
+ TEST_RPC_CLIENT = client
+ vim.rpcrequest(1, "init", result)
+ end;
+ on_exit = function(...)
+ vim.rpcnotify(1, "exit", ...)
+ end;
+ }
+ ]=], test_name, fake_lsp_code, fake_lsp_logfile, timeout_ms or 1e3)
+end
+
+local function test_rpc_server(config)
+ if config.test_name then
+ clear()
+ fake_lsp_server_setup(config.test_name, config.timeout_ms or 1e3)
+ end
+ local client = setmetatable({}, {
+ __index = function(_, name)
+ -- Workaround for not being able to yield() inside __index for Lua 5.1 :(
+ -- Otherwise I would just return the value here.
+ return function(...)
+ return exec_lua([=[
+ local name = ...
+ if type(TEST_RPC_CLIENT[name]) == 'function' then
+ return TEST_RPC_CLIENT[name](select(2, ...))
+ else
+ return TEST_RPC_CLIENT[name]
+ end
+ ]=], name, ...)
+ end
+ end;
+ })
+ local code, signal
+ local function on_request(method, args)
+ if method == "init" then
+ if config.on_init then
+ config.on_init(client, unpack(args))
+ end
+ return NIL
+ end
+ if method == 'callback' then
+ if config.on_callback then
+ config.on_callback(unpack(args))
+ end
+ end
+ return NIL
+ end
+ local function on_notify(method, args)
+ if method == 'exit' then
+ code, signal = unpack(args)
+ return stop()
+ end
+ end
+ -- TODO specify timeout?
+ -- run(on_request, on_notify, config.on_setup, 1000)
+ run(on_request, on_notify, config.on_setup)
+ if config.on_exit then
+ config.on_exit(code, signal)
+ end
+ stop()
+ if config.test_name then
+ exec_lua("lsp._vim_exit_handler()")
+ end
+end
+
+describe('LSP', function()
+ describe('server_name specified', function()
+ before_each(function()
+ clear()
+ -- Run an instance of nvim on the file which contains our "scripts".
+ -- Pass TEST_NAME to pick the script.
+ local test_name = "basic_init"
+ exec_lua([=[
+ lsp = require('vim.lsp')
+ local test_name, fixture_filename, logfile = ...
+ function test__start_client()
+ return lsp.start_client {
+ cmd_env = {
+ NVIM_LOG_FILE = logfile;
+ };
+ cmd = {
+ vim.v.progpath, '-Es', '-u', 'NONE', '--headless',
+ "-c", string.format("lua TEST_NAME = %q", test_name),
+ "-c", "luafile "..fixture_filename;
+ };
+ root_dir = vim.loop.cwd();
+ }
+ end
+ TEST_CLIENT1 = test__start_client()
+ ]=], test_name, fake_lsp_code, fake_lsp_logfile)
+ end)
+
+ after_each(function()
+ exec_lua("lsp._vim_exit_handler()")
+ -- exec_lua("lsp.stop_all_clients(true)")
+ end)
+
+ it('start_client(), stop_client()', function()
+ retry(nil, 4000, function()
+ eq(1, exec_lua('return #lsp.get_active_clients()'))
+ end)
+ eq(2, exec_lua([[
+ TEST_CLIENT2 = test__start_client()
+ return TEST_CLIENT2
+ ]]))
+ eq(3, exec_lua([[
+ TEST_CLIENT3 = test__start_client()
+ return TEST_CLIENT3
+ ]]))
+ retry(nil, 4000, function()
+ eq(3, exec_lua('return #lsp.get_active_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()'))
+ 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()'))
+ end)
+ end)
+
+ it('stop_client() also works on client objects', function()
+ exec_lua([[
+ TEST_CLIENT2 = test__start_client()
+ TEST_CLIENT3 = test__start_client()
+ ]])
+ retry(nil, 4000, function()
+ eq(3, exec_lua('return #lsp.get_active_clients()'))
+ end)
+ -- Stop all clients.
+ exec_lua('lsp.stop_client(lsp.get_active_clients())')
+ retry(nil, 4000, function()
+ eq(0, exec_lua('return #lsp.get_active_clients()'))
+ end)
+ end)
+ end)
+
+ describe('basic_init test', function()
+ it('should run correctly', function()
+ local expected_callbacks = {
+ {NIL, "test", {}, 1};
+ }
+ test_rpc_server {
+ test_name = "basic_init";
+ on_init = function(client, _init_result)
+ -- client is a dummy object which will queue up commands to be run
+ -- once the server initializes. It can't accept lua callbacks or
+ -- other types that may be unserializable for now.
+ client.stop()
+ end;
+ -- If the program timed out, then code will be nil.
+ on_exit = function(code, signal)
+ eq(0, code, "exit code", fake_lsp_logfile)
+ eq(0, signal, "exit signal", fake_lsp_logfile)
+ end;
+ -- Note that NIL must be used here.
+ -- on_callback(err, method, result, client_id)
+ on_callback = function(...)
+ eq(table.remove(expected_callbacks), {...})
+ end;
+ }
+ end)
+
+ it('should fail', function()
+ local expected_callbacks = {
+ {NIL, "test", {}, 1};
+ }
+ test_rpc_server {
+ test_name = "basic_init";
+ on_init = function(client)
+ client.notify('test')
+ client.stop()
+ end;
+ on_exit = function(code, signal)
+ eq(101, code, "exit code", fake_lsp_logfile) -- See fake-lsp-server.lua
+ eq(0, signal, "exit signal", fake_lsp_logfile)
+ assert_log(pesc([[assert_eq failed: left == "\"shutdown\"", right == "\"test\""]]),
+ fake_lsp_logfile)
+ end;
+ on_callback = function(...)
+ eq(table.remove(expected_callbacks), {...}, "expected callback")
+ end;
+ }
+ end)
+
+ it('should succeed with manual shutdown', function()
+ local expected_callbacks = {
+ {NIL, "shutdown", {}, 1, NIL};
+ {NIL, "test", {}, 1};
+ }
+ test_rpc_server {
+ test_name = "basic_init";
+ on_init = function(client)
+ eq(0, client.resolved_capabilities().text_document_did_change)
+ client.request('shutdown')
+ client.notify('exit')
+ end;
+ on_exit = function(code, signal)
+ eq(0, code, "exit code", fake_lsp_logfile)
+ eq(0, signal, "exit signal", fake_lsp_logfile)
+ end;
+ on_callback = function(...)
+ eq(table.remove(expected_callbacks), {...}, "expected callback")
+ end;
+ }
+ end)
+
+ it('should verify capabilities sent', function()
+ local expected_callbacks = {
+ {NIL, "shutdown", {}, 1};
+ }
+ test_rpc_server {
+ test_name = "basic_check_capabilities";
+ on_init = function(client)
+ client.stop()
+ end;
+ on_exit = function(code, signal)
+ eq(0, code, "exit code", fake_lsp_logfile)
+ eq(0, signal, "exit signal", fake_lsp_logfile)
+ end;
+ on_callback = function(...)
+ eq(table.remove(expected_callbacks), {...}, "expected callback")
+ end;
+ }
+ end)
+
+ it('should not send didOpen if the buffer closes before init', function()
+ local expected_callbacks = {
+ {NIL, "shutdown", {}, 1};
+ {NIL, "finish", {}, 1};
+ }
+ local client
+ test_rpc_server {
+ test_name = "basic_finish";
+ 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";
+ })
+ ]]
+ eq(1, exec_lua("return TEST_RPC_CLIENT_ID"))
+ eq(true, exec_lua("return lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID)"))
+ eq(true, exec_lua("return lsp.buf_is_attached(BUFFER, TEST_RPC_CLIENT_ID)"))
+ exec_lua [[
+ vim.api.nvim_command(BUFFER.."bwipeout")
+ ]]
+ end;
+ on_init = function(_client)
+ client = _client
+ local full_kind = exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Full")
+ eq(full_kind, client.resolved_capabilities().text_document_did_change)
+ eq(true, client.resolved_capabilities().text_document_open_close)
+ client.notify('finish')
+ end;
+ on_exit = function(code, signal)
+ eq(0, code, "exit code", fake_lsp_logfile)
+ eq(0, signal, "exit signal", fake_lsp_logfile)
+ end;
+ on_callback = function(err, method, params, client_id)
+ eq(table.remove(expected_callbacks), {err, method, params, client_id}, "expected callback")
+ if method == 'finish' then
+ client.stop()
+ end
+ end;
+ }
+ end)
+
+ it('should check the body sent attaching before init', function()
+ local expected_callbacks = {
+ {NIL, "shutdown", {}, 1};
+ {NIL, "finish", {}, 1};
+ {NIL, "start", {}, 1};
+ }
+ local client
+ test_rpc_server {
+ test_name = "basic_check_buffer_open";
+ 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";
+ })
+ ]]
+ exec_lua [[
+ assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID))
+ ]]
+ end;
+ on_init = function(_client)
+ client = _client
+ local full_kind = exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Full")
+ eq(full_kind, client.resolved_capabilities().text_document_did_change)
+ eq(true, client.resolved_capabilities().text_document_open_close)
+ exec_lua [[
+ assert(not lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID), "Shouldn't attach twice")
+ ]]
+ end;
+ on_exit = function(code, signal)
+ eq(0, code, "exit code", fake_lsp_logfile)
+ eq(0, signal, "exit signal", fake_lsp_logfile)
+ end;
+ on_callback = function(err, method, params, client_id)
+ if method == 'start' then
+ client.notify('finish')
+ end
+ eq(table.remove(expected_callbacks), {err, method, params, client_id}, "expected callback")
+ if method == 'finish' then
+ client.stop()
+ end
+ end;
+ }
+ end)
+
+ it('should check the body sent attaching after init', function()
+ local expected_callbacks = {
+ {NIL, "shutdown", {}, 1};
+ {NIL, "finish", {}, 1};
+ {NIL, "start", {}, 1};
+ }
+ local client
+ test_rpc_server {
+ test_name = "basic_check_buffer_open";
+ 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";
+ })
+ ]]
+ end;
+ on_init = function(_client)
+ client = _client
+ local full_kind = exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Full")
+ eq(full_kind, client.resolved_capabilities().text_document_did_change)
+ eq(true, client.resolved_capabilities().text_document_open_close)
+ exec_lua [[
+ assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID))
+ ]]
+ end;
+ on_exit = function(code, signal)
+ eq(0, code, "exit code", fake_lsp_logfile)
+ eq(0, signal, "exit signal", fake_lsp_logfile)
+ end;
+ on_callback = function(err, method, params, client_id)
+ if method == 'start' then
+ client.notify('finish')
+ end
+ eq(table.remove(expected_callbacks), {err, method, params, client_id}, "expected callback")
+ if method == 'finish' then
+ client.stop()
+ end
+ end;
+ }
+ end)
+
+ it('should check the body and didChange full', function()
+ local expected_callbacks = {
+ {NIL, "shutdown", {}, 1};
+ {NIL, "finish", {}, 1};
+ {NIL, "start", {}, 1};
+ }
+ local client
+ test_rpc_server {
+ test_name = "basic_check_buffer_open_and_change";
+ 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";
+ })
+ ]]
+ end;
+ on_init = function(_client)
+ client = _client
+ local full_kind = exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Full")
+ eq(full_kind, client.resolved_capabilities().text_document_did_change)
+ eq(true, client.resolved_capabilities().text_document_open_close)
+ exec_lua [[
+ assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID))
+ ]]
+ end;
+ on_exit = function(code, signal)
+ eq(0, code, "exit code", fake_lsp_logfile)
+ eq(0, signal, "exit signal", fake_lsp_logfile)
+ end;
+ on_callback = function(err, method, params, client_id)
+ if method == 'start' then
+ exec_lua [[
+ vim.api.nvim_buf_set_lines(BUFFER, 1, 2, false, {
+ "boop";
+ })
+ ]]
+ client.notify('finish')
+ end
+ eq(table.remove(expected_callbacks), {err, method, params, client_id}, "expected callback")
+ if method == 'finish' then
+ client.stop()
+ end
+ end;
+ }
+ end)
+
+ it('should check the body and didChange full with noeol', function()
+ local expected_callbacks = {
+ {NIL, "shutdown", {}, 1};
+ {NIL, "finish", {}, 1};
+ {NIL, "start", {}, 1};
+ }
+ local client
+ test_rpc_server {
+ test_name = "basic_check_buffer_open_and_change_noeol";
+ 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.api.nvim_buf_set_option(BUFFER, 'eol', false)
+ ]]
+ end;
+ on_init = function(_client)
+ client = _client
+ local full_kind = exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Full")
+ eq(full_kind, client.resolved_capabilities().text_document_did_change)
+ eq(true, client.resolved_capabilities().text_document_open_close)
+ exec_lua [[
+ assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID))
+ ]]
+ end;
+ on_exit = function(code, signal)
+ eq(0, code, "exit code", fake_lsp_logfile)
+ eq(0, signal, "exit signal", fake_lsp_logfile)
+ end;
+ on_callback = function(err, method, params, client_id)
+ if method == 'start' then
+ exec_lua [[
+ vim.api.nvim_buf_set_lines(BUFFER, 1, 2, false, {
+ "boop";
+ })
+ ]]
+ client.notify('finish')
+ end
+ eq(table.remove(expected_callbacks), {err, method, params, client_id}, "expected callback")
+ if method == 'finish' then
+ client.stop()
+ end
+ end;
+ }
+ end)
+
+ -- TODO(askhan) we don't support full for now, so we can disable these tests.
+ pending('should check the body and didChange incremental', function()
+ local expected_callbacks = {
+ {NIL, "shutdown", {}, 1};
+ {NIL, "finish", {}, 1};
+ {NIL, "start", {}, 1};
+ }
+ local client
+ test_rpc_server {
+ test_name = "basic_check_buffer_open_and_change_incremental";
+ 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";
+ })
+ ]]
+ end;
+ on_init = function(_client)
+ client = _client
+ local sync_kind = exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Incremental")
+ eq(sync_kind, client.resolved_capabilities().text_document_did_change)
+ eq(true, client.resolved_capabilities().text_document_open_close)
+ exec_lua [[
+ assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID))
+ ]]
+ end;
+ on_exit = function(code, signal)
+ eq(0, code, "exit code", fake_lsp_logfile)
+ eq(0, signal, "exit signal", fake_lsp_logfile)
+ end;
+ on_callback = function(err, method, params, client_id)
+ if method == 'start' then
+ exec_lua [[
+ vim.api.nvim_buf_set_lines(BUFFER, 1, 2, false, {
+ "boop";
+ })
+ ]]
+ client.notify('finish')
+ end
+ eq(table.remove(expected_callbacks), {err, method, params, client_id}, "expected callback")
+ if method == 'finish' then
+ client.stop()
+ end
+ end;
+ }
+ end)
+
+ -- TODO(askhan) we don't support full for now, so we can disable these tests.
+ pending('should check the body and didChange incremental normal mode editing', function()
+ local expected_callbacks = {
+ {NIL, "shutdown", {}, 1};
+ {NIL, "finish", {}, 1};
+ {NIL, "start", {}, 1};
+ }
+ local client
+ test_rpc_server {
+ test_name = "basic_check_buffer_open_and_change_incremental_editing";
+ 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";
+ })
+ ]]
+ end;
+ on_init = function(_client)
+ client = _client
+ local sync_kind = exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Incremental")
+ eq(sync_kind, client.resolved_capabilities().text_document_did_change)
+ eq(true, client.resolved_capabilities().text_document_open_close)
+ exec_lua [[
+ assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID))
+ ]]
+ end;
+ on_exit = function(code, signal)
+ eq(0, code, "exit code", fake_lsp_logfile)
+ eq(0, signal, "exit signal", fake_lsp_logfile)
+ end;
+ on_callback = function(err, method, params, client_id)
+ if method == 'start' then
+ helpers.command("normal! 1Go")
+ client.notify('finish')
+ end
+ eq(table.remove(expected_callbacks), {err, method, params, client_id}, "expected callback")
+ if method == 'finish' then
+ client.stop()
+ end
+ end;
+ }
+ end)
+
+ it('should check the body and didChange full with 2 changes', function()
+ local expected_callbacks = {
+ {NIL, "shutdown", {}, 1};
+ {NIL, "finish", {}, 1};
+ {NIL, "start", {}, 1};
+ }
+ local client
+ test_rpc_server {
+ test_name = "basic_check_buffer_open_and_change_multi";
+ 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";
+ })
+ ]]
+ end;
+ on_init = function(_client)
+ client = _client
+ local sync_kind = exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Full")
+ eq(sync_kind, client.resolved_capabilities().text_document_did_change)
+ eq(true, client.resolved_capabilities().text_document_open_close)
+ exec_lua [[
+ assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID))
+ ]]
+ end;
+ on_exit = function(code, signal)
+ eq(0, code, "exit code", fake_lsp_logfile)
+ eq(0, signal, "exit signal", fake_lsp_logfile)
+ end;
+ on_callback = function(err, method, params, client_id)
+ if method == 'start' then
+ exec_lua [[
+ vim.api.nvim_buf_set_lines(BUFFER, 1, 2, false, {
+ "321";
+ })
+ vim.api.nvim_buf_set_lines(BUFFER, 1, 2, false, {
+ "boop";
+ })
+ ]]
+ client.notify('finish')
+ end
+ eq(table.remove(expected_callbacks), {err, method, params, client_id}, "expected callback")
+ if method == 'finish' then
+ client.stop()
+ end
+ end;
+ }
+ end)
+
+ it('should check the body and didChange full lifecycle', function()
+ local expected_callbacks = {
+ {NIL, "shutdown", {}, 1};
+ {NIL, "finish", {}, 1};
+ {NIL, "start", {}, 1};
+ }
+ local client
+ test_rpc_server {
+ test_name = "basic_check_buffer_open_and_change_multi_and_close";
+ 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";
+ })
+ ]]
+ end;
+ on_init = function(_client)
+ client = _client
+ local sync_kind = exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Full")
+ eq(sync_kind, client.resolved_capabilities().text_document_did_change)
+ eq(true, client.resolved_capabilities().text_document_open_close)
+ exec_lua [[
+ assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID))
+ ]]
+ end;
+ on_exit = function(code, signal)
+ eq(0, code, "exit code", fake_lsp_logfile)
+ eq(0, signal, "exit signal", fake_lsp_logfile)
+ end;
+ on_callback = function(err, method, params, client_id)
+ if method == 'start' then
+ exec_lua [[
+ vim.api.nvim_buf_set_lines(BUFFER, 1, 2, false, {
+ "321";
+ })
+ vim.api.nvim_buf_set_lines(BUFFER, 1, 2, false, {
+ "boop";
+ })
+ vim.api.nvim_command(BUFFER.."bwipeout")
+ ]]
+ client.notify('finish')
+ end
+ eq(table.remove(expected_callbacks), {err, method, params, client_id}, "expected callback")
+ if method == 'finish' then
+ client.stop()
+ end
+ end;
+ }
+ end)
+ end)
+
+ describe("parsing tests", function()
+ it('should handle invalid content-length correctly', function()
+ local expected_callbacks = {
+ {NIL, "shutdown", {}, 1};
+ {NIL, "finish", {}, 1};
+ {NIL, "start", {}, 1};
+ }
+ local client
+ test_rpc_server {
+ test_name = "invalid_header";
+ on_setup = function()
+ end;
+ on_init = function(_client)
+ client = _client
+ client.stop(true)
+ end;
+ on_exit = function(code, signal)
+ eq(0, code, "exit code", fake_lsp_logfile)
+ eq(0, signal, "exit signal", fake_lsp_logfile)
+ end;
+ on_callback = function(err, method, params, client_id)
+ eq(table.remove(expected_callbacks), {err, method, params, client_id}, "expected callback")
+ end;
+ }
+ end)
+ end)
+ describe('lsp._cmd_parts test', function()
+ local function _cmd_parts(input)
+ return exec_lua([[
+ lsp = require('vim.lsp')
+ return lsp._cmd_parts(...)
+ ]], input)
+ end
+ it('should valid cmd argument', function()
+ eq(true, pcall(_cmd_parts, {"nvim"}))
+ eq(true, pcall(_cmd_parts, {"nvim", "--head"}))
+ end)
+
+ it('should invalid cmd argument', function()
+ eq('Error executing lua: .../shared.lua: cmd: expected list, got nvim', pcall_err(_cmd_parts, "nvim"))
+ eq('Error executing lua: .../shared.lua: cmd argument: expected string, got number', pcall_err(_cmd_parts, {"nvim", 1}))
+ end)
+ end)
+end)
+
+describe('LSP', function()
+ before_each(function()
+ clear()
+ end)
+
+ local function make_edit(y_0, x_0, y_1, x_1, text)
+ return {
+ range = {
+ start = { line = y_0, character = x_0 };
+ ["end"] = { line = y_1, character = x_1 };
+ };
+ newText = type(text) == 'table' and table.concat(text, '\n') or (text or "");
+ }
+ end
+
+ it('highlight groups', function()
+ eq({'LspDiagnosticsError',
+ 'LspDiagnosticsErrorFloating',
+ 'LspDiagnosticsErrorSign',
+ 'LspDiagnosticsHint',
+ 'LspDiagnosticsHintFloating',
+ 'LspDiagnosticsHintSign',
+ 'LspDiagnosticsInformation',
+ 'LspDiagnosticsInformationFloating',
+ 'LspDiagnosticsInformationSign',
+ 'LspDiagnosticsUnderline',
+ 'LspDiagnosticsUnderlineError',
+ 'LspDiagnosticsUnderlineHint',
+ 'LspDiagnosticsUnderlineInformation',
+ 'LspDiagnosticsUnderlineWarning',
+ 'LspDiagnosticsWarning',
+ 'LspDiagnosticsWarningFloating',
+ 'LspDiagnosticsWarningSign',
+ },
+ exec_lua([[require'vim.lsp'; return vim.fn.getcompletion('Lsp', 'highlight')]]))
+ end)
+
+ describe('apply_text_edits', function()
+ before_each(function()
+ insert(dedent([[
+ First line of text
+ Second line of text
+ Third line of text
+ Fourth line of text
+ å å ɧ 汉语 ↥ 🤦 🦄]]))
+ end)
+ it('applies simple edits', function()
+ local edits = {
+ make_edit(0, 0, 0, 0, {"123"});
+ make_edit(1, 0, 1, 1, {"2"});
+ make_edit(2, 0, 2, 2, {"3"});
+ make_edit(3, 2, 3, 4, {""});
+ }
+ exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1)
+ eq({
+ '123First line of text';
+ '2econd line of text';
+ '3ird line of text';
+ 'Foth line of text';
+ 'å å ɧ 汉语 ↥ 🤦 🦄';
+ }, buf_lines(1))
+ end)
+ it('applies complex edits', function()
+ local edits = {
+ make_edit(0, 0, 0, 0, {"", "12"});
+ make_edit(0, 0, 0, 0, {"3", "foo"});
+ make_edit(0, 1, 0, 1, {"bar", "123"});
+ make_edit(0, #"First ", 0, #"First line of text", {"guy"});
+ make_edit(1, 0, 1, #'Second', {"baz"});
+ make_edit(2, #'Th', 2, #"Third", {"e next"});
+ make_edit(3, #'', 3, #"Fourth", {"another line of text", "before this"});
+ make_edit(3, #'Fourth', 3, #"Fourth line of text", {"!"});
+ }
+ exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1)
+ eq({
+ '';
+ '123';
+ 'fooFbar';
+ '123irst guy';
+ 'baz line of text';
+ 'The next line of text';
+ 'another line of text';
+ 'before this!';
+ 'å å ɧ 汉语 ↥ 🤦 🦄';
+ }, buf_lines(1))
+ end)
+ it('applies non-ASCII characters edits', function()
+ local edits = {
+ make_edit(4, 3, 4, 4, {"ä"});
+ }
+ exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1)
+ eq({
+ 'First line of text';
+ 'Second line of text';
+ 'Third line of text';
+ 'Fourth line of text';
+ 'å ä ɧ 汉语 ↥ 🤦 🦄';
+ }, buf_lines(1))
+ end)
+
+ describe('with LSP end line after what Vim considers to be the end line', function()
+ it('applies edits when the last linebreak is considered a new line', function()
+ local edits = {
+ make_edit(0, 0, 5, 0, {"All replaced"});
+ }
+ exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1)
+ eq({'All replaced'}, buf_lines(1))
+ end)
+ it('applies edits when the end line is 2 larger than vim\'s', function()
+ local edits = {
+ make_edit(0, 0, 6, 0, {"All replaced"});
+ }
+ exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1)
+ eq({'All replaced'}, buf_lines(1))
+ end)
+ it('applies edits with a column offset', function()
+ local edits = {
+ make_edit(0, 0, 5, 2, {"All replaced"});
+ }
+ exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1)
+ eq({'All replaced'}, buf_lines(1))
+ end)
+ end)
+ end)
+
+ describe('apply_text_document_edit', function()
+ local target_bufnr
+ local text_document_edit = function(editVersion)
+ return {
+ edits = {
+ make_edit(0, 0, 0, 3, "First ↥ 🤦 🦄")
+ },
+ textDocument = {
+ uri = "file://fake/uri";
+ version = editVersion
+ }
+ }
+ end
+ before_each(function()
+ target_bufnr = exec_lua [[
+ local bufnr = vim.uri_to_bufnr("file://fake/uri")
+ local lines = {"1st line of text", "2nd line of 语text"}
+ vim.api.nvim_buf_set_lines(bufnr, 0, 1, false, lines)
+ return bufnr
+ ]]
+ end)
+ it('correctly goes ahead with the edit if all is normal', function()
+ exec_lua('vim.lsp.util.apply_text_document_edit(...)', text_document_edit(5))
+ eq({
+ 'First ↥ 🤦 🦄 line of text';
+ '2nd line of 语text';
+ }, buf_lines(target_bufnr))
+ end)
+ it('correctly goes ahead with the edit if the version is vim.NIL', function()
+ -- we get vim.NIL when we decode json null value.
+ local json = exec_lua[[
+ return vim.fn.json_decode("{ \"a\": 1, \"b\": null }")
+ ]]
+ eq(json.b, exec_lua("return vim.NIL"))
+
+ exec_lua('vim.lsp.util.apply_text_document_edit(...)', text_document_edit(exec_lua("return vim.NIL")))
+ eq({
+ 'First ↥ 🤦 🦄 line of text';
+ '2nd line of 语text';
+ }, buf_lines(target_bufnr))
+ end)
+ it('skips the edit if the version of the edit is behind the local buffer ', function()
+ local apply_edit_mocking_current_version = function(edit, versionedBuf)
+ exec_lua([[
+ local args = {...}
+ local versionedBuf = args[2]
+ vim.lsp.util.buf_versions[versionedBuf.bufnr] = versionedBuf.currentVersion
+ vim.lsp.util.apply_text_document_edit(...)
+ ]], edit, versionedBuf)
+ end
+
+ local baseText = {
+ '1st line of text';
+ '2nd line of 语text';
+ }
+
+ eq(baseText, buf_lines(target_bufnr))
+
+ -- Apply an edit for an old version, should skip
+ apply_edit_mocking_current_version(text_document_edit(2), {currentVersion=7; bufnr=target_bufnr})
+ eq(baseText, buf_lines(target_bufnr)) -- no change
+
+ -- Sanity check that next version to current does apply change
+ apply_edit_mocking_current_version(text_document_edit(8), {currentVersion=7; bufnr=target_bufnr})
+ eq({
+ 'First ↥ 🤦 🦄 line of text';
+ '2nd line of 语text';
+ }, buf_lines(target_bufnr))
+ end)
+ end)
+ describe('workspace_apply_edit', function()
+ it('workspace/applyEdit returns ApplyWorkspaceEditResponse', function()
+ local expected = {
+ applied = true;
+ failureReason = nil;
+ }
+ eq(expected, exec_lua [[
+ local apply_edit = {
+ label = nil;
+ edit = {};
+ }
+ return vim.lsp.callbacks['workspace/applyEdit'](nil, nil, apply_edit)
+ ]])
+ 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' },
+ { label='foobar', textEdit={} },
+ -- resolves into insertText
+ { label='foocar', insertText='foobar' },
+ { label='foocar', insertText='foobar', textEdit={} },
+ -- resolves into textEdit.newText
+ { label='foocar', insertText='foodar', textEdit={newText='foobar'} },
+ { label='foocar', textEdit={newText='foobar'} },
+ -- real-world snippet text
+ { label='foocar', insertText='foodar', textEdit={newText='foobar(${1:place holder}, ${2:more ...holder{\\}})'} },
+ { label='foocar', insertText='foodar(${1:var1} typ1, ${2:var2} *typ2) {$0\\}', textEdit={} },
+ -- nested snippet tokens
+ { label='foocar', insertText='foodar(${1:var1 ${2|typ2,typ3|} ${3:tail}}) {$0\\}', textEdit={} },
+ -- plain text
+ { label='foocar', 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' } } } } },
+ { abbr = 'foobar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label='foobar', textEdit={} } } } } },
+ { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label='foocar', insertText='foobar' } } } } },
+ { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label='foocar', 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', 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', 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', insertText='foodar', 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', insertText='foodar(${1:var1} typ1, ${2:var2} *typ2) {$0\\}', textEdit={} } } } } },
+ { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foodar(var1 typ2,typ3 tail) {}', user_data = { nvim = { lsp = { completion_item = { label='foocar', insertText='foodar(${1:var1 ${2|typ2,typ3|} ${3:tail}}) {$0\\}', 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', 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))
+ end)
+ end)
+ describe('buf_diagnostics_save_positions', function()
+ it('stores the diagnostics in diagnostics_by_buf', function ()
+ local diagnostics = {
+ { range = {}; message = "diag1" },
+ { range = {}; message = "diag2" },
+ }
+ exec_lua([[
+ vim.lsp.util.buf_diagnostics_save_positions(...)]], 0, diagnostics)
+ eq(1, exec_lua [[ return #vim.lsp.util.diagnostics_by_buf ]])
+ eq(diagnostics, exec_lua [[
+ for _, diagnostics in pairs(vim.lsp.util.diagnostics_by_buf) do
+ return diagnostics
+ end
+ ]])
+ end)
+ end)
+ describe('lsp.util.show_line_diagnostics', function()
+ it('creates floating window and returns popup bufnr and winnr if current line contains diagnostics', function()
+ eq(3, exec_lua [[
+ local buffer = vim.api.nvim_create_buf(false, true)
+ vim.api.nvim_buf_set_lines(buffer, 0, -1, false, {
+ "testing";
+ "123";
+ })
+ local diagnostics = {
+ {
+ range = {
+ start = { line = 0; character = 1; };
+ ["end"] = { line = 0; character = 3; };
+ };
+ severity = vim.lsp.protocol.DiagnosticSeverity.Error;
+ message = "Syntax error";
+ },
+ }
+ vim.api.nvim_win_set_buf(0, buffer)
+ vim.lsp.util.buf_diagnostics_save_positions(vim.fn.bufnr(buffer), diagnostics)
+ local popup_bufnr, winnr = vim.lsp.util.show_line_diagnostics()
+ return popup_bufnr
+ ]])
+ end)
+ end)
+ describe('lsp.util.locations_to_items', function()
+ it('Convert Location[] to items', function()
+ local expected = {
+ {
+ filename = 'fake/uri',
+ lnum = 1,
+ col = 3,
+ text = 'testing'
+ },
+ }
+ local actual = exec_lua [[
+ local bufnr = vim.uri_to_bufnr("file://fake/uri")
+ local lines = {"testing", "123"}
+ vim.api.nvim_buf_set_lines(bufnr, 0, 1, false, lines)
+ local locations = {
+ {
+ uri = 'file://fake/uri',
+ range = {
+ start = { line = 0, character = 2 },
+ ['end'] = { line = 0, character = 3 },
+ }
+ },
+ }
+ return vim.lsp.util.locations_to_items(locations)
+ ]]
+ eq(expected, actual)
+ end)
+ it('Convert LocationLink[] to items', function()
+ local expected = {
+ {
+ filename = 'fake/uri',
+ lnum = 1,
+ col = 3,
+ text = 'testing'
+ },
+ }
+ local actual = exec_lua [[
+ local bufnr = vim.uri_to_bufnr("file://fake/uri")
+ local lines = {"testing", "123"}
+ vim.api.nvim_buf_set_lines(bufnr, 0, 1, false, lines)
+ local locations = {
+ {
+ targetUri = vim.uri_from_bufnr(bufnr),
+ targetRange = {
+ start = { line = 0, character = 2 },
+ ['end'] = { line = 0, character = 3 },
+ },
+ targetSelectionRange = {
+ start = { line = 0, character = 2 },
+ ['end'] = { line = 0, character = 3 },
+ }
+ },
+ }
+ return vim.lsp.util.locations_to_items(locations)
+ ]]
+ eq(expected, actual)
+ end)
+ end)
+ describe('lsp.util.symbols_to_items', function()
+ describe('convert DocumentSymbol[] to items', function()
+ it('DocumentSymbol has children', function()
+ local expected = {
+ {
+ col = 1,
+ filename = '',
+ kind = 'File',
+ lnum = 2,
+ text = '[File] TestA'
+ },
+ {
+ col = 1,
+ filename = '',
+ kind = 'Module',
+ lnum = 4,
+ text = '[Module] TestB'
+ },
+ {
+ col = 1,
+ filename = '',
+ kind = 'Namespace',
+ lnum = 6,
+ text = '[Namespace] TestC'
+ }
+ }
+ eq(expected, exec_lua [[
+ local doc_syms = {
+ {
+ deprecated = false,
+ detail = "A",
+ kind = 1,
+ name = "TestA",
+ range = {
+ start = {
+ character = 0,
+ line = 1
+ },
+ ["end"] = {
+ character = 0,
+ line = 2
+ }
+ },
+ selectionRange = {
+ start = {
+ character = 0,
+ line = 1
+ },
+ ["end"] = {
+ character = 4,
+ line = 1
+ }
+ },
+ children = {
+ {
+ children = {},
+ deprecated = false,
+ detail = "B",
+ kind = 2,
+ name = "TestB",
+ range = {
+ start = {
+ character = 0,
+ line = 3
+ },
+ ["end"] = {
+ character = 0,
+ line = 4
+ }
+ },
+ selectionRange = {
+ start = {
+ character = 0,
+ line = 3
+ },
+ ["end"] = {
+ character = 4,
+ line = 3
+ }
+ }
+ }
+ }
+ },
+ {
+ deprecated = false,
+ detail = "C",
+ kind = 3,
+ name = "TestC",
+ range = {
+ start = {
+ character = 0,
+ line = 5
+ },
+ ["end"] = {
+ character = 0,
+ line = 6
+ }
+ },
+ selectionRange = {
+ start = {
+ character = 0,
+ line = 5
+ },
+ ["end"] = {
+ character = 4,
+ line = 5
+ }
+ }
+ }
+ }
+ return vim.lsp.util.symbols_to_items(doc_syms, nil)
+ ]])
+ end)
+ it('DocumentSymbol has no children', function()
+ local expected = {
+ {
+ col = 1,
+ filename = '',
+ kind = 'File',
+ lnum = 2,
+ text = '[File] TestA'
+ },
+ {
+ col = 1,
+ filename = '',
+ kind = 'Namespace',
+ lnum = 6,
+ text = '[Namespace] TestC'
+ }
+ }
+ eq(expected, exec_lua [[
+ local doc_syms = {
+ {
+ deprecated = false,
+ detail = "A",
+ kind = 1,
+ name = "TestA",
+ range = {
+ start = {
+ character = 0,
+ line = 1
+ },
+ ["end"] = {
+ character = 0,
+ line = 2
+ }
+ },
+ selectionRange = {
+ start = {
+ character = 0,
+ line = 1
+ },
+ ["end"] = {
+ character = 4,
+ line = 1
+ }
+ },
+ },
+ {
+ deprecated = false,
+ detail = "C",
+ kind = 3,
+ name = "TestC",
+ range = {
+ start = {
+ character = 0,
+ line = 5
+ },
+ ["end"] = {
+ character = 0,
+ line = 6
+ }
+ },
+ selectionRange = {
+ start = {
+ character = 0,
+ line = 5
+ },
+ ["end"] = {
+ character = 4,
+ line = 5
+ }
+ }
+ }
+ }
+ return vim.lsp.util.symbols_to_items(doc_syms, nil)
+ ]])
+ end)
+ end)
+ it('convert SymbolInformation[] to items', function()
+ local expected = {
+ {
+ col = 1,
+ filename = 'test_a',
+ kind = 'File',
+ lnum = 2,
+ text = '[File] TestA'
+ },
+ {
+ col = 1,
+ filename = 'test_b',
+ kind = 'Module',
+ lnum = 4,
+ text = '[Module] TestB'
+ }
+ }
+ eq(expected, exec_lua [[
+ local sym_info = {
+ {
+ deprecated = false,
+ kind = 1,
+ name = "TestA",
+ location = {
+ range = {
+ start = {
+ character = 0,
+ line = 1
+ },
+ ["end"] = {
+ character = 0,
+ line = 2
+ }
+ },
+ uri = "file://test_a"
+ },
+ contanerName = "TestAContainer"
+ },
+ {
+ deprecated = false,
+ kind = 2,
+ name = "TestB",
+ location = {
+ range = {
+ start = {
+ character = 0,
+ line = 3
+ },
+ ["end"] = {
+ character = 0,
+ line = 4
+ }
+ },
+ uri = "file://test_b"
+ },
+ contanerName = "TestBContainer"
+ }
+ }
+ return vim.lsp.util.symbols_to_items(sym_info, nil)
+ ]])
+ 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)"))
+ eq("TypeParameter", exec_lua("return vim.lsp.util._get_symbol_kind_name(26)"))
+ end)
+ it('returns the name not specified by protocol', function()
+ eq("Unknown", exec_lua("return vim.lsp.util._get_symbol_kind_name(nil)"))
+ eq("Unknown", exec_lua("return vim.lsp.util._get_symbol_kind_name(vim.NIL)"))
+ eq("Unknown", exec_lua("return vim.lsp.util._get_symbol_kind_name(1000)"))
+ end)
+ end)
+
+ describe('lsp.util.jump_to_location', function()
+ local target_bufnr
+
+ before_each(function()
+ target_bufnr = exec_lua [[
+ local bufnr = vim.uri_to_bufnr("file://fake/uri")
+ local lines = {"1st line of text", "å å ɧ 汉语 ↥ 🤦 🦄"}
+ vim.api.nvim_buf_set_lines(bufnr, 0, 1, false, lines)
+ return bufnr
+ ]]
+ end)
+
+ local location = function(start_line, start_char, end_line, end_char)
+ return {
+ uri = "file://fake/uri",
+ range = {
+ start = { line = start_line, character = start_char },
+ ["end"] = { line = end_line, character = end_char },
+ },
+ }
+ end
+
+ local jump = function(msg)
+ eq(true, exec_lua('return vim.lsp.util.jump_to_location(...)', msg))
+ eq(target_bufnr, exec_lua[[return vim.fn.bufnr('%')]])
+ return {
+ line = exec_lua[[return vim.fn.line('.')]],
+ col = exec_lua[[return vim.fn.col('.')]],
+ }
+ end
+
+ it('jumps to a Location', function()
+ local pos = jump(location(0, 9, 0, 9))
+ eq(1, pos.line)
+ eq(10, pos.col)
+ end)
+
+ it('jumps to a LocationLink', function()
+ local pos = jump({
+ targetUri = "file://fake/uri",
+ targetSelectionRange = {
+ start = { line = 0, character = 4 },
+ ["end"] = { line = 0, character = 4 },
+ },
+ targetRange = {
+ start = { line = 1, character = 5 },
+ ["end"] = { line = 1, character = 5 },
+ },
+ })
+ eq(1, pos.line)
+ eq(5, pos.col)
+ end)
+
+ it('jumps to the correct multibyte column', function()
+ local pos = jump(location(1, 2, 1, 2))
+ eq(2, pos.line)
+ eq(4, pos.col)
+ eq('Ã¥', exec_lua[[return vim.fn.expand('<cword>')]])
+ end)
+ end)
+
+ describe('lsp.util._make_floating_popup_size', function()
+ before_each(function()
+ exec_lua [[ contents =
+ {"text tαxt txtα tex",
+ "text tααt tααt text",
+ "text tαxt tαxt"}
+ ]]
+ end)
+
+ it('calculates size correctly', function()
+ eq({19,3}, exec_lua[[ return {vim.lsp.util._make_floating_popup_size(contents)} ]])
+ end)
+
+ 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)
+ end)
+
+ describe('lsp.util.get_effective_tabstop', function()
+ local function test_tabstop(tabsize, softtabstop)
+ exec_lua(string.format([[
+ vim.api.nvim_buf_set_option(0, 'softtabstop', %d)
+ vim.api.nvim_buf_set_option(0, 'tabstop', 2)
+ vim.api.nvim_buf_set_option(0, 'shiftwidth', 3)
+ ]], softtabstop))
+ eq(tabsize, exec_lua('return vim.lsp.util.get_effective_tabstop()'))
+ end
+
+ it('with softtabstop = 1', function() test_tabstop(1, 1) end)
+ it('with softtabstop = 0', function() test_tabstop(2, 0) end)
+ it('with softtabstop = -1', function() test_tabstop(3, -1) end)
+ end)
+
+ describe('vim.lsp.buf.outgoing_calls', function()
+ it('does nothing for an empty response', function()
+ local qflist_count = exec_lua([=[
+ require'vim.lsp.callbacks'['callHierarchy/outgoingCalls']()
+ return #vim.fn.getqflist()
+ ]=])
+ eq(0, qflist_count)
+ end)
+
+ it('opens the quickfix list with the right caller', function()
+ local qflist = exec_lua([=[
+ local rust_analyzer_response = { {
+ fromRanges = { {
+ ['end'] = {
+ character = 7,
+ line = 3
+ },
+ start = {
+ character = 4,
+ line = 3
+ }
+ } },
+ to = {
+ detail = "fn foo()",
+ kind = 12,
+ name = "foo",
+ range = {
+ ['end'] = {
+ character = 11,
+ line = 0
+ },
+ start = {
+ character = 0,
+ line = 0
+ }
+ },
+ selectionRange = {
+ ['end'] = {
+ character = 6,
+ line = 0
+ },
+ start = {
+ character = 3,
+ line = 0
+ }
+ },
+ uri = "file:///src/main.rs"
+ }
+ } }
+ local callback = require'vim.lsp.callbacks'['callHierarchy/outgoingCalls']
+ callback(nil, nil, rust_analyzer_response)
+ return vim.fn.getqflist()
+ ]=])
+
+ local expected = { {
+ bufnr = 2,
+ col = 5,
+ lnum = 4,
+ module = "",
+ nr = 0,
+ pattern = "",
+ text = "foo",
+ type = "",
+ valid = 1,
+ vcol = 0
+ } }
+
+ eq(expected, qflist)
+ end)
+ end)
+
+ describe('vim.lsp.buf.incoming_calls', function()
+ it('does nothing for an empty response', function()
+ local qflist_count = exec_lua([=[
+ require'vim.lsp.callbacks'['callHierarchy/incomingCalls']()
+ return #vim.fn.getqflist()
+ ]=])
+ eq(0, qflist_count)
+ end)
+
+ it('opens the quickfix list with the right callee', function()
+ local qflist = exec_lua([=[
+ local rust_analyzer_response = { {
+ from = {
+ detail = "fn main()",
+ kind = 12,
+ name = "main",
+ range = {
+ ['end'] = {
+ character = 1,
+ line = 4
+ },
+ start = {
+ character = 0,
+ line = 2
+ }
+ },
+ selectionRange = {
+ ['end'] = {
+ character = 7,
+ line = 2
+ },
+ start = {
+ character = 3,
+ line = 2
+ }
+ },
+ uri = "file:///src/main.rs"
+ },
+ fromRanges = { {
+ ['end'] = {
+ character = 7,
+ line = 3
+ },
+ start = {
+ character = 4,
+ line = 3
+ }
+ } }
+ } }
+
+ local callback = require'vim.lsp.callbacks'['callHierarchy/incomingCalls']
+ callback(nil, nil, rust_analyzer_response)
+ return vim.fn.getqflist()
+ ]=])
+
+ local expected = { {
+ bufnr = 2,
+ col = 5,
+ lnum = 4,
+ module = "",
+ nr = 0,
+ pattern = "",
+ text = "main",
+ type = "",
+ valid = 1,
+ vcol = 0
+ } }
+
+ eq(expected, qflist)
+ end)
+ end)
+end)
diff --git a/test/functional/plugin/man_spec.lua b/test/functional/plugin/man_spec.lua
index d95995797e..e5b2e7dc1f 100644
--- a/test/functional/plugin/man_spec.lua
+++ b/test/functional/plugin/man_spec.lua
@@ -19,38 +19,32 @@ describe(':Man', function()
u = { underline = true },
bi = { bold = true, italic = true },
biu = { bold = true, italic = true, underline = true },
- })
- screen:set_default_attr_ignore({
- { foreground = Screen.colors.Blue }, -- control chars
- { bold = true, foreground = Screen.colors.Blue } -- empty line '~'s
+ c = { foreground = Screen.colors.Blue }, -- control chars
+ eob = { bold = true, foreground = Screen.colors.Blue } -- empty line '~'s
})
screen:attach()
end)
- after_each(function()
- screen:detach()
- end)
-
it('clears backspaces from text and adds highlights', function()
rawfeed([[
ithis i<C-v><C-h>is<C-v><C-h>s a<C-v><C-h>a test
with _<C-v><C-h>o_<C-v><C-h>v_<C-v><C-h>e_<C-v><C-h>r_<C-v><C-h>s_<C-v><C-h>t_<C-v><C-h>r_<C-v><C-h>u_<C-v><C-h>c_<C-v><C-h>k text<ESC>]])
- screen:expect([[
- this i^His^Hs a^Ha test |
- with _^Ho_^Hv_^He_^Hr_^Hs_^Ht_^Hr_^Hu_^Hc_^Hk tex^t |
- ~ |
- ~ |
- |
- ]])
+ screen:expect{grid=[[
+ this i{c:^H}is{c:^H}s a{c:^H}a test |
+ with _{c:^H}o_{c:^H}v_{c:^H}e_{c:^H}r_{c:^H}s_{c:^H}t_{c:^H}r_{c:^H}u_{c:^H}c_{c:^H}k tex^t |
+ {eob:~ }|
+ {eob:~ }|
+ |
+ ]]}
eval('man#init_pager()')
screen:expect([[
^this {b:is} {b:a} test |
with {u:overstruck} text |
- ~ |
- ~ |
+ {eob:~ }|
+ {eob:~ }|
|
]])
end)
@@ -60,21 +54,21 @@ describe(':Man', function()
ithis <C-v><ESC>[1mis <C-v><ESC>[3ma <C-v><ESC>[4mtest<C-v><ESC>[0m
<C-v><ESC>[4mwith<C-v><ESC>[24m <C-v><ESC>[4mescaped<C-v><ESC>[24m <C-v><ESC>[4mtext<C-v><ESC>[24m<ESC>]])
- screen:expect([=[
- this ^[[1mis ^[[3ma ^[[4mtest^[[0m |
- ^[[4mwith^[[24m ^[[4mescaped^[[24m ^[[4mtext^[[24^m |
- ~ |
- ~ |
- |
- ]=])
+ screen:expect{grid=[=[
+ this {c:^[}[1mis {c:^[}[3ma {c:^[}[4mtest{c:^[}[0m |
+ {c:^[}[4mwith{c:^[}[24m {c:^[}[4mescaped{c:^[}[24m {c:^[}[4mtext{c:^[}[24^m |
+ {eob:~ }|
+ {eob:~ }|
+ |
+ ]=]}
eval('man#init_pager()')
screen:expect([[
^this {b:is }{bi:a }{biu:test} |
{u:with} {u:escaped} {u:text} |
- ~ |
- ~ |
+ {eob:~ }|
+ {eob:~ }|
|
]])
end)
@@ -88,8 +82,8 @@ describe(':Man', function()
screen:expect([[
^this {b:is} {b:ã‚} test |
with {u:överstrũck} te{i:xt¶} |
- ~ |
- ~ |
+ {eob:~ }|
+ {eob:~ }|
|
]])
end)
@@ -105,7 +99,7 @@ describe(':Man', function()
{b:^_begins} |
{b:mid_dle} |
{u:mid_dle} |
- ~ |
+ {eob:~ }|
|
]])
end)
@@ -121,7 +115,7 @@ describe(':Man', function()
^· {b:·} |
{b:·} |
{b:·} double |
- ~ |
+ {eob:~ }|
|
]])
end)
diff --git a/test/functional/preload.lua b/test/functional/preload.lua
index 1107b45d54..24a3977e6b 100644
--- a/test/functional/preload.lua
+++ b/test/functional/preload.lua
@@ -2,3 +2,13 @@
-- Busted started doing this to help provide more isolation. See issue #62
-- for more information about this.
local helpers = require('test.functional.helpers')(nil)
+local iswin = helpers.iswin
+
+if iswin() then
+ local ffi = require('ffi')
+ ffi.cdef[[
+ typedef int errno_t;
+ errno_t _set_fmode(int mode);
+ ]]
+ ffi.C._set_fmode(0x8000)
+end
diff --git a/test/functional/provider/clipboard_spec.lua b/test/functional/provider/clipboard_spec.lua
index b2d75db745..da9dd09129 100644
--- a/test/functional/provider/clipboard_spec.lua
+++ b/test/functional/provider/clipboard_spec.lua
@@ -88,6 +88,11 @@ describe('clipboard', function()
before_each(function()
clear()
screen = Screen.new(72, 4)
+ screen:set_default_attr_ids({
+ [0] = {bold = true, foreground = Screen.colors.Blue},
+ [1] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red},
+ [2] = {bold = true, foreground = Screen.colors.SeaGreen4},
+ })
screen:attach()
command("set display-=msgsep")
end)
@@ -103,22 +108,22 @@ describe('clipboard', function()
feed_command('redir @+> | :silent echo system("cat CONTRIBUTING.md") | redir END')
screen:expect([[
^ |
- ~ |
- ~ |
+ {0:~ }|
+ {0:~ }|
clipboard: No provider. Try ":checkhealth" or ":h clipboard". |
- ]], nil, {{bold = true, foreground = Screen.colors.Blue}})
+ ]])
end)
it('`:redir @+>|bogus_cmd|redir END` + invalid g:clipboard must not recurse #7184',
function()
command("let g:clipboard = 'bogus'")
feed_command('redir @+> | bogus_cmd | redir END')
- screen:expect([[
- ~ |
+ screen:expect{grid=[[
+ {0:~ }|
clipboard: No provider. Try ":checkhealth" or ":h clipboard". |
- E492: Not an editor command: bogus_cmd | redir END |
- Press ENTER or type command to continue^ |
- ]], nil, {{bold = true, foreground = Screen.colors.Blue}})
+ {1:E492: Not an editor command: bogus_cmd | redir END} |
+ {2:Press ENTER or type command to continue}^ |
+ ]]}
end)
it('invalid g:clipboard shows hint if :redir is not active', function()
@@ -131,10 +136,10 @@ describe('clipboard', function()
feed_command('let @+="foo"')
screen:expect([[
^ |
- ~ |
- ~ |
+ {0:~ }|
+ {0:~ }|
clipboard: No provider. Try ":checkhealth" or ":h clipboard". |
- ]], nil, {{bold = true, foreground = Screen.colors.Blue}})
+ ]])
end)
it('valid g:clipboard', function()
@@ -266,13 +271,17 @@ describe('clipboard (with fake clipboard.vim)', function()
function()
local screen = Screen.new(72, 4)
screen:attach()
+ screen:set_default_attr_ids({
+ [0] = {bold = true, foreground = Screen.colors.Blue},
+ [1] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red},
+ })
feed_command('redir @+> | bogus_cmd | redir END')
screen:expect([[
^ |
- ~ |
- ~ |
- E492: Not an editor command: bogus_cmd | redir END |
- ]], nil, {{bold = true, foreground = Screen.colors.Blue}})
+ {0:~ }|
+ {0:~ }|
+ {1:E492: Not an editor command: bogus_cmd | redir END} |
+ ]])
end)
it('has independent "* and unnamed registers by default', function()
@@ -637,6 +646,9 @@ describe('clipboard (with fake clipboard.vim)', function()
feed_command('set mouse=a')
local screen = Screen.new(30, 5)
+ screen:set_default_attr_ids({
+ [0] = {bold = true, foreground = Screen.colors.Blue},
+ })
screen:attach()
insert([[
the source
@@ -646,10 +658,10 @@ describe('clipboard (with fake clipboard.vim)', function()
screen:expect([[
the ^source |
a target |
- ~ |
- ~ |
+ {0:~ }|
+ {0:~ }|
|
- ]], nil, {{bold = true, foreground = Screen.colors.Blue}})
+ ]])
feed('<MiddleMouse><0,1>')
expect([[
diff --git a/test/functional/provider/define_spec.lua b/test/functional/provider/define_spec.lua
index 51a8831274..1d50ce0a56 100644
--- a/test/functional/provider/define_spec.lua
+++ b/test/functional/provider/define_spec.lua
@@ -89,6 +89,21 @@ local function command_specs_for(fn, sync, first_arg_factory, init)
runx(sync, handler, on_setup)
end)
+ it('with nargs/double-quote', function()
+ call(fn, args..', {"nargs": "*"}')
+ local function on_setup()
+ command('RpcCommand "arg1" "arg2" "arg3"')
+ end
+
+ local function handler(method, arguments)
+ eq('test-handler', method)
+ eq({'"arg1"', '"arg2"', '"arg3"'}, arguments[1])
+ return ''
+ end
+
+ runx(sync, handler, on_setup)
+ end)
+
it('with range', function()
call(fn,args..', {"range": ""}')
local function on_setup()
diff --git a/test/functional/provider/nodejs_spec.lua b/test/functional/provider/nodejs_spec.lua
index 07a00f8a8c..661a6f4f94 100644
--- a/test/functional/provider/nodejs_spec.lua
+++ b/test/functional/provider/nodejs_spec.lua
@@ -8,8 +8,9 @@ local retry = helpers.retry
do
clear()
- if missing_provider('node') then
- pending("Missing nodejs host, or nodejs version is too old.", function()end)
+ local reason = missing_provider('node')
+ if reason then
+ pending(string.format("Missing nodejs host, or nodejs version is too old (%s)", reason), function() end)
return
end
end
diff --git a/test/functional/provider/perl_spec.lua b/test/functional/provider/perl_spec.lua
new file mode 100644
index 0000000000..7b446e4ab3
--- /dev/null
+++ b/test/functional/provider/perl_spec.lua
@@ -0,0 +1,80 @@
+local helpers = require('test.functional.helpers')(after_each)
+local eq, clear = helpers.eq, helpers.clear
+local missing_provider = helpers.missing_provider
+local command = helpers.command
+local write_file = helpers.write_file
+local eval = helpers.eval
+local retry = helpers.retry
+
+do
+ clear()
+ local reason = missing_provider('perl')
+ if reason then
+ pending(string.format("Missing perl host, or perl version is too old (%s)", reason), function() end)
+ return
+ end
+end
+
+before_each(function()
+ clear()
+end)
+
+describe('perl host', function()
+ if helpers.pending_win32(pending) then return end
+ teardown(function ()
+ os.remove('Xtest-perl-hello.pl')
+ os.remove('Xtest-perl-hello-plugin.pl')
+ end)
+
+ it('works', function()
+ local fname = 'Xtest-perl-hello.pl'
+ write_file(fname, [[
+ package main;
+ use strict;
+ use warnings;
+ use Neovim::Ext;
+ use Neovim::Ext::MsgPack::RPC;
+
+ my $session = Neovim::Ext::MsgPack::RPC::socket_session($ENV{NVIM_LISTEN_ADDRESS});
+ my $nvim = Neovim::Ext::from_session($session);
+ $nvim->command('let g:job_out = "hello"');
+ 1;
+ ]])
+ command('let g:job_id = jobstart(["perl", "'..fname..'"])')
+ retry(nil, 3000, function() eq('hello', eval('g:job_out')) end)
+ end)
+
+ it('plugin works', function()
+ local fname = 'Xtest-perl-hello-plugin.pl'
+ write_file(fname, [[
+ package TestPlugin;
+ use strict;
+ use warnings;
+ use parent qw(Neovim::Ext::Plugin);
+
+ __PACKAGE__->register;
+
+ @{TestPlugin::commands} = ();
+ @{TestPlugin::specs} = ();
+ sub test_command :nvim_command('TestCommand')
+ {
+ my ($this) = @_;
+ $this->nvim->command('let g:job_out = "hello-plugin"');
+ }
+
+ package main;
+ use strict;
+ use warnings;
+ use Neovim::Ext;
+ use Neovim::Ext::MsgPack::RPC;
+
+ my $session = Neovim::Ext::MsgPack::RPC::socket_session($ENV{NVIM_LISTEN_ADDRESS});
+ my $nvim = Neovim::Ext::from_session($session);
+ my $plugin = TestPlugin->new($nvim);
+ $plugin->test_command();
+ 1;
+ ]])
+ command('let g:job_id = jobstart(["perl", "'..fname..'"])')
+ retry(nil, 3000, function() eq('hello-plugin', eval('g:job_out')) end)
+ end)
+end)
diff --git a/test/functional/provider/python3_spec.lua b/test/functional/provider/python3_spec.lua
index b319d5e948..d254edc7d5 100644
--- a/test/functional/provider/python3_spec.lua
+++ b/test/functional/provider/python3_spec.lua
@@ -10,13 +10,14 @@ local pcall_err = helpers.pcall_err
do
clear()
- if missing_provider('python3') then
+ local reason = missing_provider('python3')
+ if reason then
it(':python3 reports E319 if provider is missing', function()
local expected = [[Vim%(py3.*%):E319: No "python3" provider found.*]]
matches(expected, pcall_err(command, 'py3 print("foo")'))
matches(expected, pcall_err(command, 'py3file foo'))
end)
- pending('Python 3 (or the pynvim module) is broken/missing', function() end)
+ pending(string.format('Python 3 (or the pynvim module) is broken/missing (%s)', reason), function() end)
return
end
end
diff --git a/test/functional/provider/python_spec.lua b/test/functional/provider/python_spec.lua
index 986f10b2e9..d60d8d1001 100644
--- a/test/functional/provider/python_spec.lua
+++ b/test/functional/provider/python_spec.lua
@@ -18,13 +18,14 @@ local pcall_err = helpers.pcall_err
do
clear()
- if missing_provider('python') then
+ local reason = missing_provider('python')
+ if reason then
it(':python reports E319 if provider is missing', function()
local expected = [[Vim%(py.*%):E319: No "python" provider found.*]]
matches(expected, pcall_err(command, 'py print("foo")'))
matches(expected, pcall_err(command, 'pyfile foo'))
end)
- pending('Python 2 (or the pynvim module) is broken/missing', function() end)
+ pending(string.format('Python 2 (or the pynvim module) is broken/missing (%s)', reason), function() end)
return
end
end
diff --git a/test/functional/provider/ruby_spec.lua b/test/functional/provider/ruby_spec.lua
index d20adde2ef..bb7d23ede6 100644
--- a/test/functional/provider/ruby_spec.lua
+++ b/test/functional/provider/ruby_spec.lua
@@ -18,13 +18,14 @@ local pcall_err = helpers.pcall_err
do
clear()
- if missing_provider('ruby') then
+ local reason = missing_provider('ruby')
+ if reason then
it(':ruby reports E319 if provider is missing', function()
local expected = [[Vim%(ruby.*%):E319: No "ruby" provider found.*]]
matches(expected, pcall_err(command, 'ruby puts "foo"'))
matches(expected, pcall_err(command, 'rubyfile foo'))
end)
- pending("Missing neovim RubyGem.", function() end)
+ pending(string.format('Missing neovim RubyGem (%s)', reason), function() end)
return
end
end
diff --git a/test/functional/shada/errors_spec.lua b/test/functional/shada/errors_spec.lua
index 66c8c4ad2f..77a41caec7 100644
--- a/test/functional/shada/errors_spec.lua
+++ b/test/functional/shada/errors_spec.lua
@@ -1,7 +1,7 @@
-- ShaDa errors handling support
local helpers = require('test.functional.helpers')(after_each)
-local nvim_command, eq, exc_exec, redir_exec =
- helpers.command, helpers.eq, helpers.exc_exec, helpers.redir_exec
+local nvim_command, eq, exc_exec =
+ helpers.command, helpers.eq, helpers.exc_exec
local shada_helpers = require('test.functional.shada.helpers')
local reset, clear, get_shada_rw =
@@ -494,23 +494,6 @@ $
eq(0, exc_exec('wshada! ' .. shada_fname))
end)
- it('errors when a funcref is stored in a variable', function()
- nvim_command('let F = function("tr")')
- nvim_command('set shada+=!')
- eq('\nE5004: Error while dumping variable g:F, itself: attempt to dump function reference'
- .. '\nE574: Failed to write variable F',
- redir_exec('wshada'))
- end)
-
- it('errors when a self-referencing list is stored in a variable', function()
- nvim_command('let L = []')
- nvim_command('call add(L, L)')
- nvim_command('set shada+=!')
- eq('\nE5005: Unable to dump variable g:L: container references itself in index 0'
- .. '\nE574: Failed to write variable L',
- redir_exec('wshada'))
- end)
-
it('errors with too large items', function()
wshada({
1, 206, 70, 90, 31, 179, 86, 133, 169, 103, 101, 110, 101, 114, 97,
diff --git a/test/functional/shada/history_spec.lua b/test/functional/shada/history_spec.lua
index 78b5c77857..9291f5e100 100644
--- a/test/functional/shada/history_spec.lua
+++ b/test/functional/shada/history_spec.lua
@@ -2,6 +2,7 @@
local helpers = require('test.functional.helpers')(after_each)
local nvim_command, funcs, meths, nvim_feed, eq =
helpers.command, helpers.funcs, helpers.meths, helpers.feed, helpers.eq
+local eval = helpers.eval
local shada_helpers = require('test.functional.shada.helpers')
local reset, clear = shada_helpers.reset, shada_helpers.clear
@@ -237,4 +238,13 @@ describe('ShaDa support code', function()
nvim_command('wshada')
end)
+ it('does not crash when number of history save to zero (#11497)', function()
+ nvim_command('set shada=\'10')
+ nvim_feed(':" Test\n')
+ nvim_command('wshada')
+ nvim_command('set shada=\'10,:0')
+ nvim_command('wshada')
+ eq(2, eval('1+1')) -- check nvim still running
+ end)
+
end)
diff --git a/test/functional/shada/variables_spec.lua b/test/functional/shada/variables_spec.lua
index 74bbceddcc..cc0e7fa537 100644
--- a/test/functional/shada/variables_spec.lua
+++ b/test/functional/shada/variables_spec.lua
@@ -1,7 +1,7 @@
-- ShaDa variables saving/reading support
local helpers = require('test.functional.helpers')(after_each)
-local meths, funcs, nvim_command, eq, exc_exec =
- helpers.meths, helpers.funcs, helpers.command, helpers.eq, helpers.exc_exec
+local meths, funcs, nvim_command, eq =
+ helpers.meths, helpers.funcs, helpers.command, helpers.eq
local shada_helpers = require('test.functional.shada.helpers')
local reset, clear = shada_helpers.reset, shada_helpers.clear
@@ -121,28 +121,39 @@ describe('ShaDa support code', function()
meths.get_var('NESTEDVAR'))
end)
- it('errors and writes when a funcref is stored in a variable',
+ it('ignore when a funcref is stored in a variable',
function()
nvim_command('let F = function("tr")')
meths.set_var('U', '10')
nvim_command('set shada+=!')
- eq('Vim(wshada):E5004: Error while dumping variable g:F, itself: attempt to dump function reference',
- exc_exec('wshada'))
- meths.set_option('shada', '')
- reset('set shada+=!')
+ nvim_command('wshada')
+ reset()
+ nvim_command('set shada+=!')
+ nvim_command('rshada')
eq('10', meths.get_var('U'))
end)
- it('errors and writes when a self-referencing list is stored in a variable',
+ it('ignore when a partial is stored in a variable',
+ function()
+ nvim_command('let P = { -> 1 }')
+ meths.set_var('U', '10')
+ nvim_command('set shada+=!')
+ nvim_command('wshada')
+ reset()
+ nvim_command('set shada+=!')
+ nvim_command('rshada')
+ eq('10', meths.get_var('U'))
+ end)
+
+ it('ignore when a self-referencing list is stored in a variable',
function()
meths.set_var('L', {})
nvim_command('call add(L, L)')
meths.set_var('U', '10')
nvim_command('set shada+=!')
- eq('Vim(wshada):E5005: Unable to dump variable g:L: container references itself in index 0',
- exc_exec('wshada'))
- meths.set_option('shada', '')
- reset('set shada+=!')
+ nvim_command('wshada')
+ reset()
+ nvim_command('rshada')
eq('10', meths.get_var('U'))
end)
end)
diff --git a/test/functional/terminal/buffer_spec.lua b/test/functional/terminal/buffer_spec.lua
index 1763574bf9..6372cd935e 100644
--- a/test/functional/terminal/buffer_spec.lua
+++ b/test/functional/terminal/buffer_spec.lua
@@ -5,6 +5,7 @@ local wait = helpers.wait
local eval, feed_command, source = helpers.eval, helpers.feed_command, helpers.source
local eq, neq = helpers.eq, helpers.neq
local write_file = helpers.write_file
+local command= helpers.command
describe(':terminal buffer', function()
local screen
@@ -16,6 +17,18 @@ describe(':terminal buffer', function()
screen = thelpers.screen_setup()
end)
+ it('terminal-mode forces various options', function()
+ feed([[<C-\><C-N>]])
+ command('setlocal cursorline cursorcolumn scrolloff=4 sidescrolloff=7')
+ eq({ 1, 1, 4, 7 }, eval('[&l:cursorline, &l:cursorcolumn, &l:scrolloff, &l:sidescrolloff]'))
+ eq('n', eval('mode()'))
+
+ -- Enter terminal-mode ("insert" mode in :terminal).
+ feed('i')
+ eq('t', eval('mode()'))
+ eq({ 0, 0, 0, 0 }, eval('[&l:cursorline, &l:cursorcolumn, &l:scrolloff, &l:sidescrolloff]'))
+ end)
+
describe('when a new file is edited', function()
before_each(function()
feed('<c-\\><c-n>:set bufhidden=wipe<cr>:enew<cr>')
@@ -59,7 +72,7 @@ describe(':terminal buffer', function()
end)
it('does not create swap files', function()
- local swapfile = nvim('command_output', 'swapname'):gsub('\n', '')
+ local swapfile = nvim('exec', 'swapname', true):gsub('\n', '')
eq(nil, io.open(swapfile))
end)
@@ -208,22 +221,38 @@ describe(':terminal buffer', function()
feed_command('terminal')
feed('<c-\\><c-n>')
feed_command('confirm bdelete')
- screen:expect{any='Close "term://', attr_ignore=true}
+ screen:expect{any='Close "term://'}
end)
it('with &confirm', function()
feed_command('terminal')
feed('<c-\\><c-n>')
feed_command('bdelete')
- screen:expect{any='E89', attr_ignore=true}
+ screen:expect{any='E89'}
feed('<cr>')
eq('terminal', eval('&buftype'))
feed_command('set confirm | bdelete')
- screen:expect{any='Close "term://', attr_ignore=true}
+ screen:expect{any='Close "term://'}
feed('y')
neq('terminal', eval('&buftype'))
end)
end)
+
+ it('it works with set rightleft #11438', function()
+ local columns = eval('&columns')
+ feed(string.rep('a', columns))
+ command('set rightleft')
+ screen:expect([[
+ ydaer ytt|
+ {1:a}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
+ |
+ |
+ |
+ |
+ {3:-- TERMINAL --} |
+ ]])
+ command('bdelete!')
+ end)
end)
describe('No heap-buffer-overflow when using', function()
@@ -247,3 +276,12 @@ describe('No heap-buffer-overflow when using', function()
feed_command('bdelete!')
end)
end)
+
+describe('No heap-buffer-overflow when', function()
+ it('set nowrap and send long line #11548', function()
+ feed_command('set nowrap')
+ feed_command('autocmd TermOpen * startinsert')
+ feed_command('call feedkeys("4000ai\\<esc>:terminal!\\<cr>")')
+ eq(2, eval('1+1'))
+ end)
+end)
diff --git a/test/functional/terminal/edit_spec.lua b/test/functional/terminal/edit_spec.lua
index d213bae7b3..fabc5524ed 100644
--- a/test/functional/terminal/edit_spec.lua
+++ b/test/functional/terminal/edit_spec.lua
@@ -5,9 +5,12 @@ local curbufmeths = helpers.curbufmeths
local curwinmeths = helpers.curwinmeths
local nvim_dir = helpers.nvim_dir
local command = helpers.command
+local funcs = helpers.funcs
local meths = helpers.meths
local clear = helpers.clear
local eq = helpers.eq
+local matches = helpers.matches
+local pesc = helpers.pesc
describe(':edit term://*', function()
local get_screen = function(columns, lines)
@@ -28,7 +31,8 @@ describe(':edit term://*', function()
command('edit term://')
local termopen_runs = meths.get_var('termopen_runs')
eq(1, #termopen_runs)
- eq(termopen_runs[1], termopen_runs[1]:match('^term://.//%d+:$'))
+ local cwd = funcs.fnamemodify('.', ':p:~'):gsub([[[\/]*$]], '')
+ matches('^term://'..pesc(cwd)..'//%d+:$', termopen_runs[1])
end)
it("runs TermOpen early enough to set buffer-local 'scrollback'", function()
diff --git a/test/functional/terminal/ex_terminal_spec.lua b/test/functional/terminal/ex_terminal_spec.lua
index b0019d2d37..138befd978 100644
--- a/test/functional/terminal/ex_terminal_spec.lua
+++ b/test/functional/terminal/ex_terminal_spec.lua
@@ -245,12 +245,14 @@ describe(':terminal (with fake shell)', function()
it('works with gf', function()
command('set shellxquote=') -- win: avoid extra quotes
terminal_with_fake_shell([[echo "scripts/shadacat.py"]])
+ retry(nil, 4 * screen.timeout, function()
screen:expect([[
^ready $ echo "scripts/shadacat.py" |
|
[Process exited 0] |
:terminal echo "scripts/shadacat.py" |
]])
+ end)
feed([[<C-\><C-N>]])
eq('term://', string.match(eval('bufname("%")'), "^term://"))
feed([[ggf"lgf]])
diff --git a/test/functional/terminal/helpers.lua b/test/functional/terminal/helpers.lua
index f6cab6bd1e..d909888613 100644
--- a/test/functional/terminal/helpers.lua
+++ b/test/functional/terminal/helpers.lua
@@ -52,7 +52,7 @@ local function screen_setup(extra_rows, command, cols, opts)
[3] = {bold = true},
[4] = {foreground = 12},
[5] = {bold = true, reverse = true},
- [6] = {background = 11},
+ -- 6 was a duplicate item
[7] = {foreground = 130},
[8] = {foreground = 15, background = 1}, -- error message
[9] = {foreground = 4},
diff --git a/test/functional/terminal/highlight_spec.lua b/test/functional/terminal/highlight_spec.lua
index 06a6fd6f2b..8d3f0218af 100644
--- a/test/functional/terminal/highlight_spec.lua
+++ b/test/functional/terminal/highlight_spec.lua
@@ -121,13 +121,12 @@ it(':terminal highlight has lower precedence than editor #9964', function()
local screen = Screen.new(30, 4)
screen:set_default_attr_ids({
-- "Normal" highlight emitted by the child nvim process.
- N_child = {foreground = tonumber('0x4040ff'), background = tonumber('0xffff40')},
- -- "Search" highlight emitted by the child nvim process.
- S_child = {background = tonumber('0xffff40'), italic = true, foreground = tonumber('0x4040ff')},
+ N_child = {foreground = tonumber('0x4040ff'), background = tonumber('0xffff40'), fg_indexed=true, bg_indexed=true},
-- "Search" highlight in the parent nvim process.
S = {background = Screen.colors.Green, italic = true, foreground = Screen.colors.Red},
-- "Question" highlight in the parent nvim process.
- Q = {background = tonumber('0xffff40'), bold = true, foreground = Screen.colors.SeaGreen4},
+ -- note: bg is indexed as it comes from the (cterm) child, while fg isn't as it comes from (rgb) parent
+ Q = {background = tonumber('0xffff40'), bold = true, foreground = Screen.colors.SeaGreen4, bg_indexed=true},
})
screen:attach({rgb=true})
-- Child nvim process in :terminal (with cterm colors).
@@ -160,6 +159,54 @@ it(':terminal highlight has lower precedence than editor #9964', function()
]])
end)
+describe(':terminal highlight forwarding', function()
+ local screen
+
+ before_each(function()
+ clear()
+ screen = Screen.new(50, 7)
+ screen:set_rgb_cterm(true)
+ screen:set_default_attr_ids({
+ [1] = {{reverse = true}, {reverse = true}},
+ [2] = {{bold = true}, {bold = true}},
+ [3] = {{fg_indexed = true, foreground = tonumber('0xe0e000')}, {foreground = 3}},
+ [4] = {{foreground = tonumber('0xff8000')}, {}},
+ })
+ screen:attach()
+ command('enew | call termopen(["'..nvim_dir..'/tty-test"])')
+ feed('i')
+ screen:expect([[
+ tty ready |
+ {1: } |
+ |
+ |
+ |
+ |
+ {2:-- TERMINAL --} |
+ ]])
+ end)
+
+ it('will handle cterm and rgb attributes', function()
+ if helpers.pending_win32(pending) then return end
+ thelpers.set_fg(3)
+ thelpers.feed_data('text')
+ thelpers.feed_termcode('[38:2:255:128:0m')
+ thelpers.feed_data('color')
+ thelpers.clear_attrs()
+ thelpers.feed_data('text')
+ screen:expect{grid=[[
+ tty ready |
+ {3:text}{4:color}text{1: } |
+ |
+ |
+ |
+ |
+ {2:-- TERMINAL --} |
+ ]]}
+ end)
+end)
+
+
describe(':terminal highlight with custom palette', function()
local screen
@@ -167,7 +214,7 @@ describe(':terminal highlight with custom palette', function()
clear()
screen = Screen.new(50, 7)
screen:set_default_attr_ids({
- [1] = {foreground = tonumber('0x123456')},
+ [1] = {foreground = tonumber('0x123456')}, -- no fg_indexed when overriden
[2] = {foreground = 12},
[3] = {bold = true, reverse = true},
[5] = {background = 11},
diff --git a/test/functional/terminal/mouse_spec.lua b/test/functional/terminal/mouse_spec.lua
index 64f437f206..0eb5901b3b 100644
--- a/test/functional/terminal/mouse_spec.lua
+++ b/test/functional/terminal/mouse_spec.lua
@@ -31,10 +31,6 @@ describe(':terminal mouse', function()
]])
end)
- after_each(function()
- screen:detach()
- end)
-
describe('when the terminal has focus', function()
it('will exit focus on mouse-scroll', function()
eq('t', eval('mode()'))
@@ -91,6 +87,36 @@ describe(':terminal mouse', function()
{3:-- TERMINAL --} |
]])
end)
+
+ it('will forward mouse clicks to the program with the correct even if set nu', function()
+ if helpers.pending_win32(pending) then return end
+ nvim('command', 'set number')
+ -- When the display area such as a number is clicked, it returns to the
+ -- normal mode.
+ feed('<LeftMouse><3,0>')
+ eq('n', eval('mode()'))
+ screen:expect([[
+ {7: 11 }^line28 |
+ {7: 12 }line29 |
+ {7: 13 }line30 |
+ {7: 14 }mouse enabled |
+ {7: 15 }rows: 6, cols: 46 |
+ {7: 16 }{2: } |
+ |
+ ]])
+ -- If click on the coordinate (0,1) of the region of the terminal
+ -- (i.e. the coordinate (4,1) of vim), 'CSI !"' is sent to the terminal.
+ feed('i<LeftMouse><4,1>')
+ screen:expect([[
+ {7: 11 }line28 |
+ {7: 12 }line29 |
+ {7: 13 }line30 |
+ {7: 14 }mouse enabled |
+ {7: 15 }rows: 6, cols: 46 |
+ {7: 16 } !"{1: } |
+ {3:-- TERMINAL --} |
+ ]])
+ end)
end)
describe('with a split window and other buffer', function()
@@ -152,7 +178,7 @@ describe(':terminal mouse', function()
end)
it('wont lose focus if another window is scrolled', function()
- feed('<ScrollWheelUp><0,0><ScrollWheelUp><0,0>')
+ feed('<ScrollWheelUp><4,0><ScrollWheelUp><4,0>')
screen:expect([[
{7: 21 }line │line30 |
{7: 22 }line │rows: 5, cols: 25 |
@@ -162,7 +188,7 @@ describe(':terminal mouse', function()
========== ========== |
{3:-- TERMINAL --} |
]])
- feed('<S-ScrollWheelDown><0,0>')
+ feed('<S-ScrollWheelDown><4,0>')
screen:expect([[
{7: 26 }line │line30 |
{7: 27 }line │rows: 5, cols: 25 |
diff --git a/test/functional/terminal/scrollback_spec.lua b/test/functional/terminal/scrollback_spec.lua
index 7413081510..1df8df6f6e 100644
--- a/test/functional/terminal/scrollback_spec.lua
+++ b/test/functional/terminal/scrollback_spec.lua
@@ -21,10 +21,6 @@ describe(':terminal scrollback', function()
screen = thelpers.screen_setup(nil, nil, 30)
end)
- after_each(function()
- screen:detach()
- end)
-
describe('when the limit is exceeded', function()
before_each(function()
local lines = {}
@@ -406,8 +402,6 @@ describe("'scrollback' option", function()
feed_data(nvim_dir..'/shell-test REP 31 line'..(iswin() and '\r' or '\n'))
screen:expect{any='30: line '}
retry(nil, nil, function() expect_lines(7) end)
-
- screen:detach()
end)
it('deletes lines (only) if necessary', function()
@@ -438,14 +432,32 @@ describe("'scrollback' option", function()
command('sleep 100m')
feed_data(nvim_dir.."/shell-test REP 41 line"..(iswin() and '\r' or '\n'))
- screen:expect{any='40: line '}
+ if iswin() then
+ screen:expect{grid=[[
+ 37: line |
+ 38: line |
+ 39: line |
+ 40: line |
+ |
+ ${1: } |
+ {3:-- TERMINAL --} |
+ ]]}
+ else
+ screen:expect{grid=[[
+ 36: line |
+ 37: line |
+ 38: line |
+ 39: line |
+ 40: line |
+ {MATCH:.*}|
+ {3:-- TERMINAL --} |
+ ]]}
+ end
+ expect_lines(58)
- retry(nil, nil, function() expect_lines(58) end)
-- Verify off-screen state
eq((iswin() and '36: line' or '35: line'), eval("getline(line('w0') - 1)"))
eq((iswin() and '27: line' or '26: line'), eval("getline(line('w0') - 10)"))
-
- screen:detach()
end)
it('defaults to 10000 in :terminal buffers', function()
diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua
index c55093cb0f..c0578c08e1 100644
--- a/test/functional/terminal/tui_spec.lua
+++ b/test/functional/terminal/tui_spec.lua
@@ -45,10 +45,6 @@ describe('TUI', function()
child_session = helpers.connect(child_server)
end)
- after_each(function()
- screen:detach()
- end)
-
-- Wait for mode in the child Nvim (avoid "typeahead race" #10826).
local function wait_for_mode(mode)
retry(nil, nil, function()
@@ -303,9 +299,54 @@ describe('TUI', function()
feed_data('u')
expect_child_buf_lines({'"pasted from terminal"'})
feed_data('u')
+ expect_child_buf_lines({'""'})
+ feed_data('u')
expect_child_buf_lines({''})
end)
+ it('paste: select-mode', function()
+ feed_data('ithis is line 1\nthis is line 2\nline 3 is here\n\027')
+ wait_for_mode('n')
+ screen:expect{grid=[[
+ this is line 1 |
+ this is line 2 |
+ line 3 is here |
+ {1: } |
+ {5:[No Name] [+] }|
+ |
+ {3:-- TERMINAL --} |
+ ]]}
+ -- Select-mode. Use <C-n> to move down.
+ feed_data('gg04lgh\14\14')
+ wait_for_mode('s')
+ feed_data('\027[200~')
+ feed_data('just paste itâ„¢')
+ feed_data('\027[201~')
+ screen:expect{grid=[[
+ thisjust paste itâ„¢{1:3} is here |
+ |
+ {4:~ }|
+ {4:~ }|
+ {5:[No Name] [+] }|
+ |
+ {3:-- TERMINAL --} |
+ ]]}
+ -- Undo.
+ feed_data('u')
+ expect_child_buf_lines{
+ 'this is line 1',
+ '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()
feed_data(':set statusline=^^^^^^^\n')
feed_data(':terminal '..nvim_dir..'/tty-test\n')
@@ -447,7 +488,7 @@ describe('TUI', function()
end)
it('paste: recovers from vim.paste() failure', function()
- child_session:request('nvim_execute_lua', [[
+ child_session:request('nvim_exec_lua', [[
_G.save_paste_fn = vim.paste
vim.paste = function(lines, phase) error("fake fail") end
]], {})
@@ -505,7 +546,7 @@ describe('TUI', function()
{3:-- TERMINAL --} |
]]}
-- Paste works if vim.paste() succeeds.
- child_session:request('nvim_execute_lua', [[
+ child_session:request('nvim_exec_lua', [[
vim.paste = _G.save_paste_fn
]], {})
feed_data('\027[200~line A\nline B\n\027[201~')
@@ -524,7 +565,7 @@ describe('TUI', function()
it('paste: vim.paste() cancel (retval=false) #10865', function()
-- This test only exercises the "cancel" case. Use-case would be "dangling
-- paste", but that is not implemented yet. #10865
- child_session:request('nvim_execute_lua', [[
+ child_session:request('nvim_exec_lua', [[
vim.paste = function(lines, phase) return false end
]], {})
feed_data('\027[200~line A\nline B\n\027[201~')
@@ -539,7 +580,7 @@ describe('TUI', function()
|
{4:~ }|
{5: }|
- {8:paste: Error executing lua: vim.lua:197: Vim:E21: }|
+ {MATCH:paste: Error executing lua: vim.lua:%d+: Vim:E21: }|
{8:Cannot make changes, 'modifiable' is off} |
{10:Press ENTER or type command to continue}{1: } |
{3:-- TERMINAL --} |
@@ -564,9 +605,10 @@ describe('TUI', function()
wait_for_mode('i')
-- "bracketed paste"
feed_data('\027[200~'..expected..'\027[201~')
+ -- FIXME: Data race between the two feeds
+ if uname() == 'freebsd' then screen:sleep(1) end
feed_data(' end')
expected = expected..' end'
- expect_child_buf_lines({expected})
screen:expect([[
zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz|
zzzzzzzzzzzzzz end{1: } |
@@ -576,6 +618,24 @@ describe('TUI', function()
{3:-- INSERT --} |
{3:-- TERMINAL --} |
]])
+ expect_child_buf_lines({expected})
+ end)
+
+ it('paste: less-than sign in cmdline #11088', function()
+ local expected = '<'
+ feed_data(':')
+ wait_for_mode('c')
+ -- "bracketed paste"
+ feed_data('\027[200~'..expected..'\027[201~')
+ screen:expect{grid=[[
+ |
+ {4:~ }|
+ {4:~ }|
+ {4:~ }|
+ {5:[No Name] }|
+ :<{1: } |
+ {3:-- TERMINAL --} |
+ ]]}
end)
it('paste: big burst of input', function()
@@ -667,11 +727,11 @@ describe('TUI', function()
screen:set_option('rgb', true)
screen:set_default_attr_ids({
[1] = {reverse = true},
- [2] = {foreground = tonumber('0x4040ff')},
+ [2] = {foreground = tonumber('0x4040ff'), fg_indexed=true},
[3] = {bold = true, reverse = true},
[4] = {bold = true},
- [5] = {reverse = true, foreground = tonumber('0xe0e000')},
- [6] = {foreground = tonumber('0xe0e000')},
+ [5] = {reverse = true, foreground = tonumber('0xe0e000'), fg_indexed=true},
+ [6] = {foreground = tonumber('0xe0e000'), fg_indexed=true},
[7] = {reverse = true, foreground = Screen.colors.SeaGreen4},
[8] = {foreground = Screen.colors.SeaGreen4},
[9] = {bold = true, foreground = Screen.colors.Blue1},
@@ -715,6 +775,54 @@ describe('TUI', function()
]])
end)
+ it('forwards :term palette colors with termguicolors', function()
+ screen:set_rgb_cterm(true)
+ screen:set_default_attr_ids({
+ [1] = {{reverse = true}, {reverse = true}},
+ [2] = {{bold = true, reverse = true}, {bold = true, reverse = true}},
+ [3] = {{bold = true}, {bold = true}},
+ [4] = {{fg_indexed = true, foreground = tonumber('0xe0e000')}, {foreground = 3}},
+ [5] = {{foreground = tonumber('0xff8000')}, {}},
+ })
+
+ feed_data(':set statusline=^^^^^^^\n')
+ feed_data(':set termguicolors\n')
+ feed_data(':terminal '..nvim_dir..'/tty-test\n')
+ -- Depending on platform the above might or might not fit in the cmdline
+ -- so clear it for consistent behavior.
+ feed_data(':\027')
+ screen:expect{grid=[[
+ {1:t}ty ready |
+ |
+ |
+ |
+ {2:^^^^^^^ }|
+ |
+ {3:-- TERMINAL --} |
+ ]]}
+ feed_data(':call chansend(&channel, "\\033[38;5;3mtext\\033[38:2:255:128:0mcolor\\033[0;10mtext")\n')
+ screen:expect{grid=[[
+ {1:t}ty ready |
+ {4:text}{5:color}text |
+ |
+ |
+ {2:^^^^^^^ }|
+ |
+ {3:-- TERMINAL --} |
+ ]]}
+
+ feed_data(':set notermguicolors\n')
+ screen:expect{grid=[[
+ {1:t}ty ready |
+ {4:text}colortext |
+ |
+ |
+ {2:^^^^^^^ }|
+ :set notermguicolors |
+ {3:-- TERMINAL --} |
+ ]]}
+ 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([=[
@@ -911,7 +1019,15 @@ describe('TUI FocusGained/FocusLost', function()
feed_data(':terminal\n')
-- Wait for terminal to be ready.
- screen:expect{any='-- TERMINAL --'}
+ screen:expect{grid=[[
+ {1:r}eady $ |
+ [Process exited 0] |
+ |
+ |
+ |
+ :terminal |
+ {3:-- TERMINAL --} |
+ ]]}
feed_data('\027[I')
screen:expect{grid=[[
@@ -922,7 +1038,7 @@ describe('TUI FocusGained/FocusLost', function()
|
gained |
{3:-- TERMINAL --} |
- ]], timeout=(3 * screen.timeout)}
+ ]], timeout=(4 * screen.timeout)}
feed_data('\027[O')
screen:expect([[
@@ -1266,7 +1382,7 @@ describe("TUI 'term' option", function()
elseif is_macos then
local status, _ = pcall(assert_term, "xterm", "xterm")
if not status then
- pending("macOS: unibilium could not find terminfo", function() end)
+ pending("macOS: unibilium could not find terminfo")
end
else
assert_term("xterm", "xterm")
@@ -1328,125 +1444,26 @@ describe("TUI", function()
end)
-describe('TUI background color', function()
- local screen
-
- before_each(function()
- clear()
- screen = thelpers.screen_setup(0, '["'..nvim_prog
- ..'", "-u", "NONE", "-i", "NONE", "--cmd", "set noswapfile"]')
- end)
-
- it("triggers OptionSet event on terminal-response", function()
- feed_data('\027:autocmd OptionSet background echo "did OptionSet, yay!"\n')
-
- -- Wait for the child Nvim to register the OptionSet handler.
- feed_data('\027:autocmd OptionSet\n')
- screen:expect({any='--- Autocommands ---'})
-
- feed_data('\012') -- CTRL-L: clear the screen
- screen:expect([[
- {1: } |
- {4:~ }|
- {4:~ }|
- {4:~ }|
- {5:[No Name] 0,0-1 All}|
- |
- {3:-- TERMINAL --} |
- ]])
- feed_data('\027]11;rgb:ffff/ffff/ffff\007')
- screen:expect{any='did OptionSet, yay!'}
- end)
-
- it("handles deferred background color", function()
- local last_bg = 'dark'
- local function wait_for_bg(bg)
- -- Retry until the terminal response is handled.
- retry(100, nil, function()
- feed_data(':echo &background\n')
- screen:expect({
- timeout=40,
- grid=string.format([[
- {1: } |
- {4:~ }|
- {4:~ }|
- {4:~ }|
- {5:[No Name] 0,0-1 All}|
- %-5s |
- {3:-- TERMINAL --} |
- ]], bg)
- })
- end)
- last_bg = bg
- end
-
- local function assert_bg(colorspace, color, bg)
- -- Ensure the opposite of the expected bg is active.
- local other_bg = (bg == 'dark' and 'light' or 'dark')
- if last_bg ~= other_bg then
- feed_data(other_bg == 'light' and '\027]11;rgb:f/f/f\007'
- or '\027]11;rgb:0/0/0\007')
- wait_for_bg(other_bg)
- end
+it('TUI bg color triggers OptionSet event on terminal-response', function()
+ -- Only single integration test.
+ -- See test/unit/tui_spec.lua for unit tests.
+ clear()
+ local screen = thelpers.screen_setup(0, '["'..nvim_prog
+ ..'", "-u", "NONE", "-i", "NONE", "--cmd", "set noswapfile", '
+ ..'"-c", "autocmd OptionSet background echo \\"did OptionSet, yay!\\""]')
- feed_data('\027]11;'..colorspace..':'..color..'\007')
- wait_for_bg(bg)
- end
+ screen:expect([[
+ {1: } |
+ {4:~ }|
+ {4:~ }|
+ {4:~ }|
+ {5:[No Name] 0,0-1 All}|
+ |
+ {3:-- TERMINAL --} |
+ ]])
+ feed_data('\027]11;rgb:ffff/ffff/ffff\007')
+ screen:expect{any='did OptionSet, yay!'}
- 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')
- end)
+ feed_data(':echo "new_bg=".&background\n')
+ screen:expect{any='new_bg=light'}
end)
diff --git a/test/functional/terminal/window_split_tab_spec.lua b/test/functional/terminal/window_split_tab_spec.lua
index 7b49a38e77..03bd336aec 100644
--- a/test/functional/terminal/window_split_tab_spec.lua
+++ b/test/functional/terminal/window_split_tab_spec.lua
@@ -103,4 +103,14 @@ describe(':terminal', function()
|
]])
end)
+
+ it('stays in terminal mode with <Cmd>wincmd', function()
+ command('terminal')
+ command('split')
+ command('terminal')
+ feed('a<Cmd>wincmd j<CR>')
+ eq(2, eval("winnr()"))
+ eq('t', eval('mode()'))
+ end)
+
end)
diff --git a/test/functional/ui/bufhl_spec.lua b/test/functional/ui/bufhl_spec.lua
index bcccef84b6..3cb592c714 100644
--- a/test/functional/ui/bufhl_spec.lua
+++ b/test/functional/ui/bufhl_spec.lua
@@ -5,6 +5,7 @@ local clear, feed, insert = helpers.clear, helpers.feed, helpers.insert
local command, neq = helpers.command, helpers.neq
local meths = helpers.meths
local curbufmeths, eq = helpers.curbufmeths, helpers.eq
+local pcall_err = helpers.pcall_err
describe('Buffer highlighting', function()
local screen
@@ -31,13 +32,13 @@ describe('Buffer highlighting', function()
[14] = {background = Screen.colors.Gray90},
[15] = {background = Screen.colors.Gray90, bold = true, foreground = Screen.colors.Brown},
[16] = {foreground = Screen.colors.Magenta, background = Screen.colors.Gray90},
+ [17] = {foreground = Screen.colors.Magenta, background = Screen.colors.LightRed},
+ [18] = {background = Screen.colors.LightRed},
+ [19] = {foreground = Screen.colors.Blue1, background = Screen.colors.LightRed},
+ [20] = {underline = true, bold = true, foreground = Screen.colors.Cyan4},
})
end)
- after_each(function()
- screen:detach()
- end)
-
local add_highlight = curbufmeths.add_highlight
local clear_namespace = curbufmeths.clear_namespace
@@ -206,21 +207,272 @@ describe('Buffer highlighting', function()
|
]])
- command(':3move 4')
- screen:expect([[
+ -- TODO(bfedl): this behaves a bit weirdly due to the highlight on
+ -- the deleted line wrapping around. we should invalidate
+ -- highlights when they are completely inside deleted text
+ command('3move 4')
+ screen:expect{grid=[[
a {5:longer} example |
|
+ {8:from different sources} |
+ {8:^in }{20:order}{8: to demonstrate} |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+ --screen:expect([[
+ -- a {5:longer} example |
+ -- |
+ -- {9:from }{8:diff}{7:erent} sources |
+ -- ^in {6:order} to {7:de}{5:monstr}{7:ate} |
+ -- {1:~ }|
+ -- {1:~ }|
+ -- {1:~ }|
+ -- |
+ --]])
+
+ command('undo')
+ screen:expect{grid=[[
+ a {5:longer} example |
+ ^ |
+ in {6:order} to {7:de}{5:monstr}{7:ate} |
+ {9:from }{8:diff}{7:erent} sources |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ 1 change; before #4 {MATCH:.*}|
+ ]]}
+
+ command('undo')
+ screen:expect{grid=[[
+ ^a {5:longer} example |
+ in {6:order} to {7:de}{5:monstr}{7:ate} |
+ {9:from }{8:diff}{7:erent} sources |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ 1 line less; before #3 {MATCH:.*}|
+ ]]}
+
+ command('undo')
+ screen:expect{grid=[[
+ a {5:longer} example |
+ in {6:order} to {7:de}{5:monstr}{7:ate} |
+ {7:^combin}{8:ing}{9: hi}ghlights |
{9:from }{8:diff}{7:erent} sources |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ 1 more line; before #2 {MATCH:.*}|
+ ]]}
+ end)
+
+ it('and moving lines around', function()
+ command('2move 3')
+ screen:expect{grid=[[
+ a {5:longer} example |
+ {7:combin}{8:ing}{9: hi}ghlights |
^in {6:order} to {7:de}{5:monstr}{7:ate} |
+ {9:from }{8:diff}{7:erent} sources |
{1:~ }|
{1:~ }|
{1:~ }|
|
- ]])
+ ]]}
+
+ command('1,2move 4')
+ screen:expect{grid=[[
+ in {6:order} to {7:de}{5:monstr}{7:ate} |
+ {9:from }{8:diff}{7:erent} sources |
+ a {5:longer} example |
+ {7:^combin}{8:ing}{9: hi}ghlights |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+
+ command('undo')
+ screen:expect{grid=[[
+ a {5:longer} example |
+ {7:combin}{8:ing}{9: hi}ghlights |
+ ^in {6:order} to {7:de}{5:monstr}{7:ate} |
+ {9:from }{8:diff}{7:erent} sources |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ 2 change3; before #3 {MATCH:.*}|
+ ]]}
+
+ command('undo')
+ screen:expect{grid=[[
+ a {5:longer} example |
+ ^in {6:order} to {7:de}{5:monstr}{7:ate} |
+ {7:combin}{8:ing}{9: hi}ghlights |
+ {9:from }{8:diff}{7:erent} sources |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ 1 change; before #2 {MATCH:.*}|
+ ]]}
+ end)
+
+ it('and adjusting columns', function()
+ -- insert before
+ feed('ggiquite <esc>')
+ screen:expect{grid=[[
+ quite^ a {5:longer} example |
+ in {6:order} to {7:de}{5:monstr}{7:ate} |
+ {7:combin}{8:ing}{9: hi}ghlights |
+ {9:from }{8:diff}{7:erent} sources |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+
+ feed('u')
+ screen:expect{grid=[[
+ ^a {5:longer} example |
+ in {6:order} to {7:de}{5:monstr}{7:ate} |
+ {7:combin}{8:ing}{9: hi}ghlights |
+ {9:from }{8:diff}{7:erent} sources |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ 1 change; before #2 {MATCH:.*}|
+ ]]}
+
+ -- change/insert in the middle
+ feed('+fesAAAA')
+ screen:expect{grid=[[
+ a {5:longer} example |
+ in {6:ordAAAA^r} to {7:de}{5:monstr}{7:ate} |
+ {7:combin}{8:ing}{9: hi}ghlights |
+ {9:from }{8:diff}{7:erent} sources |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {7:-- INSERT --} |
+ ]]}
+
+ feed('<esc>tdD')
+ screen:expect{grid=[[
+ a {5:longer} example |
+ in {6:ordAAAAr} t^o |
+ {7:combin}{8:ing}{9: hi}ghlights |
+ {9:from }{8:diff}{7:erent} sources |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+
+ feed('u')
+ screen:expect{grid=[[
+ a {5:longer} example |
+ in {6:ordAAAAr} to^ {7:de}{5:monstr}{7:ate} |
+ {7:combin}{8:ing}{9: hi}ghlights |
+ {9:from }{8:diff}{7:erent} sources |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ 1 change; before #4 {MATCH:.*}|
+ ]]}
+
+ feed('u')
+ screen:expect{grid=[[
+ a {5:longer} example |
+ in {6:ord^er} to {7:de}{5:monstr}{7:ate} |
+ {7:combin}{8:ing}{9: hi}ghlights |
+ {9:from }{8:diff}{7:erent} sources |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ 1 change; before #3 {MATCH:.*}|
+ ]]}
+ end)
+
+ it('and joining lines', function()
+ feed('ggJJJ')
+ screen:expect{grid=[[
+ a {5:longer} example in {6:order} to {7:de}{5:monstr}{7:ate}|
+ {7:combin}{8:ing}{9: hi}ghlights^ {9:from }{8:diff}{7:erent} sou|
+ rces |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+
+ feed('uuu')
+ screen:expect{grid=[[
+ ^a {5:longer} example |
+ in {6:order} to {7:de}{5:monstr}{7:ate} |
+ {7:combin}{8:ing}{9: hi}ghlights |
+ {9:from }{8:diff}{7:erent} sources |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ 1 more line; before #2 {MATCH:.*}|
+ ]]}
+ end)
+
+ it('and splitting lines', function()
+ feed('2Gtti<cr>')
+ screen:expect{grid=[[
+ a {5:longer} example |
+ in {6:order} |
+ ^ to {7:de}{5:monstr}{7:ate} |
+ {7:combin}{8:ing}{9: hi}ghlights |
+ {9:from }{8:diff}{7:erent} sources |
+ {1:~ }|
+ {1:~ }|
+ {7:-- INSERT --} |
+ ]]}
+
+ feed('<esc>tsi<cr>')
+ screen:expect{grid=[[
+ a {5:longer} example |
+ in {6:order} |
+ to {7:de}{5:mo} |
+ {5:^nstr}{7:ate} |
+ {7:combin}{8:ing}{9: hi}ghlights |
+ {9:from }{8:diff}{7:erent} sources |
+ {1:~ }|
+ {7:-- INSERT --} |
+ ]]}
+
+ feed('<esc>u')
+ screen:expect{grid=[[
+ a {5:longer} example |
+ in {6:order} |
+ to {7:de}{5:mo^nstr}{7:ate} |
+ {7:combin}{8:ing}{9: hi}ghlights |
+ {9:from }{8:diff}{7:erent} sources |
+ {1:~ }|
+ {1:~ }|
+ 1 line less; before #3 {MATCH:.*}|
+ ]]}
+
+ feed('<esc>u')
+ screen:expect{grid=[[
+ a {5:longer} example |
+ in {6:order}^ to {7:de}{5:monstr}{7:ate} |
+ {7:combin}{8:ing}{9: hi}ghlights |
+ {9:from }{8:diff}{7:erent} sources |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ 1 line less; before #2 {MATCH:.*}|
+ ]]}
end)
end)
- it('prioritizes latest added highlight', function()
+ pending('prioritizes latest added highlight', function()
insert([[
three overlapping colors]])
add_highlight(0, "Identifier", 0, 6, 17)
@@ -251,6 +503,37 @@ describe('Buffer highlighting', function()
]])
end)
+ it('prioritizes earlier highlight groups (TEMP)', function()
+ insert([[
+ three overlapping colors]])
+ add_highlight(0, "Identifier", 0, 6, 17)
+ add_highlight(0, "String", 0, 14, 23)
+ local id = add_highlight(0, "Special", 0, 0, 9)
+
+ screen:expect{grid=[[
+ {4:three }{6:overlapp}{2:ing color}^s |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+
+ clear_namespace(id, 0, 1)
+ screen:expect{grid=[[
+ three {6:overlapp}{2:ing color}^s |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+ end)
+
it('works with multibyte text', function()
insert([[
Ta båten över sjön!]])
@@ -297,7 +580,7 @@ describe('Buffer highlighting', function()
]])
end)
- describe('virtual text annotations', function()
+ describe('virtual text decorations', function()
local set_virtual_text = curbufmeths.set_virtual_text
local id1, id2
before_each(function()
@@ -375,16 +658,53 @@ describe('Buffer highlighting', function()
]])
feed("2Gdd")
- screen:expect([[
+ -- TODO(bfredl): currently decorations get moved from a deleted line
+ -- to the next one. We might want to add "invalidation" when deleting
+ -- over a decoration.
+ screen:expect{grid=[[
1 + 2 |
^5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5|
- , 5, 5, 5, 5, 5, 5, Lorem ipsum dolor s|
+ , 5, 5, 5, 5, 5, 5, {12:æš—x事zz速野谷質çµè‚²}|
x = 4 |
{1:~ }|
{1:~ }|
{1:~ }|
|
- ]])
+ ]]}
+ --screen:expect([[
+ -- 1 + 2 |
+ -- ^5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5|
+ -- , 5, 5, 5, 5, 5, 5, Lorem ipsum dolor s|
+ -- x = 4 |
+ -- {1:~ }|
+ -- {1:~ }|
+ -- {1:~ }|
+ -- |
+ --]])
+ end)
+
+ 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"}, {}))
+ end)
+
+ it('can be retrieved', function()
+ local get_virtual_text = curbufmeths.get_virtual_text
+ local line_count = curbufmeths.line_count
+
+ local s1 = {{'Köttbullar', 'Comment'}, {'Kräuterbutter'}}
+ local s2 = {{'ã“ã‚“ã«ã¡ã¯', 'Comment'}}
+
+ -- TODO: only a virtual text from the same ns curretly overrides
+ -- an existing virtual text. We might add a prioritation system.
+ set_virtual_text(id1, 0, s1, {})
+ eq(s1, get_virtual_text(0))
+
+ set_virtual_text(-1, line_count(), s2, {})
+ eq(s2, get_virtual_text(line_count()))
+
+ eq({}, get_virtual_text(line_count() + 9000))
end)
it('is not highlighted by visual selection', function()
@@ -516,6 +836,32 @@ describe('Buffer highlighting', function()
|
]])
end)
+
+ it('works with color column', function()
+ eq(-1, set_virtual_text(-1, 3, {{"暗x事", "Comment"}}, {}))
+ screen:expect{grid=[[
+ ^1 + 2 {3:=}{2: 3} |
+ 3 + {11:ERROR:} invalid syntax |
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5|
+ , 5, 5, 5, 5, 5, 5, Lorem ipsum dolor s|
+ x = 4 {12:暗x事} |
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+
+ command("set colorcolumn=9")
+ screen:expect{grid=[[
+ ^1 + 2 {3:=}{2: }{17:3} |
+ 3 + {11:ERROR:} invalid syntax |
+ 5, 5, 5,{18: }5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5|
+ , 5, 5, 5, 5, 5, 5, Lorem ipsum dolor s|
+ x = 4 {12:暗}{19:x}{12:事} |
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+ end)
end)
it('and virtual text use the same namespace counter', function()
diff --git a/test/functional/ui/cmdline_highlight_spec.lua b/test/functional/ui/cmdline_highlight_spec.lua
index 052414a43d..9c746b99bd 100644
--- a/test/functional/ui/cmdline_highlight_spec.lua
+++ b/test/functional/ui/cmdline_highlight_spec.lua
@@ -362,7 +362,7 @@ describe('Command-line coloring', function()
{EOB:~ }|
:e^ |
]])
- eq('', meths.command_output('messages'))
+ eq('', meths.exec('messages', true))
end)
it('silences :echon', function()
set_color_cb('Echoning')
@@ -377,7 +377,7 @@ describe('Command-line coloring', function()
{EOB:~ }|
:e^ |
]])
- eq('', meths.command_output('messages'))
+ eq('', meths.exec('messages', true))
end)
it('silences :echomsg', function()
set_color_cb('Echomsging')
@@ -392,7 +392,7 @@ describe('Command-line coloring', function()
{EOB:~ }|
:e^ |
]])
- eq('', meths.command_output('messages'))
+ eq('', meths.exec('messages', true))
end)
it('does the right thing when throwing', function()
set_color_cb('Throwing')
@@ -857,7 +857,7 @@ describe('Ex commands coloring', function()
]])
feed('<CR>')
eq('Error detected while processing :\nE605: Exception not caught: 42\nE749: empty buffer',
- meths.command_output('messages'))
+ meths.exec('messages', true))
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 f9769c706f..21c01b3458 100644
--- a/test/functional/ui/cmdline_spec.lua
+++ b/test/functional/ui/cmdline_spec.lua
@@ -25,10 +25,6 @@ local function test_cmdline(linegrid)
screen = new_screen({rgb=true, ext_cmdline=true, ext_linegrid=linegrid})
end)
- after_each(function()
- screen:detach()
- end)
-
it('works', function()
feed(':')
screen:expect{grid=[[
@@ -779,7 +775,7 @@ local function test_cmdline(linegrid)
}}}
-- This used to send an invalid event where pos where larger than the total
- -- lenght of content. Checked in _handle_cmdline_show.
+ -- length of content. Checked in _handle_cmdline_show.
feed('<esc>')
screen:expect([[
^ |
@@ -804,10 +800,6 @@ describe('cmdline redraw', function()
screen = new_screen({rgb=true})
end)
- after_each(function()
- screen:detach()
- end)
-
it('with timer', function()
feed(':012345678901234567890123456789')
screen:expect{grid=[[
@@ -829,8 +821,7 @@ describe('cmdline redraw', function()
it('with <Cmd>', function()
if 'openbsd' == helpers.uname() then
- pending('FIXME #10804', function() end)
- return
+ pending('FIXME #10804')
end
command('cmap a <Cmd>call sin(0)<CR>') -- no-op
feed(':012345678901234567890123456789')
diff --git a/test/functional/ui/cursor_spec.lua b/test/functional/ui/cursor_spec.lua
index 67aba919b0..6c913124ac 100644
--- a/test/functional/ui/cursor_spec.lua
+++ b/test/functional/ui/cursor_spec.lua
@@ -13,10 +13,6 @@ describe('ui/cursor', function()
screen:attach()
end)
- after_each(function()
- screen:detach()
- end)
-
it("'guicursor' is published as a UI event", function()
local expected_mode_info = {
[1] = {
@@ -249,6 +245,25 @@ describe('ui/cursor', function()
eq('normal', screen.mode)
end)
+ -- update the highlight again to hide cursor
+ helpers.command('hi Cursor blend=100')
+
+ for _, m in ipairs(expected_mode_info) do
+ if m.hl_id then
+ m.attr = {background = Screen.colors.Red, blend = 100}
+ end
+ end
+ screen:expect{grid=[[
+ ^ |
+ ~ |
+ ~ |
+ ~ |
+ test |
+ ]], condition=function()
+ eq(expected_mode_info, screen._mode_info)
+ end
+ }
+
-- 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')
diff --git a/test/functional/ui/diff_spec.lua b/test/functional/ui/diff_spec.lua
index 8eb2bbf779..69b6ab8cf0 100644
--- a/test/functional/ui/diff_spec.lua
+++ b/test/functional/ui/diff_spec.lua
@@ -3,7 +3,10 @@ local Screen = require('test.functional.ui.screen')
local feed = helpers.feed
local clear = helpers.clear
+local command = helpers.command
+local insert = helpers.insert
local write_file = helpers.write_file
+local source = helpers.source
describe('Diff mode screen', function()
local fname = 'Xtest-functional-diff-screen-1'
@@ -957,3 +960,151 @@ int main(int argc, char **argv)
end)
end)
end)
+
+it('win_update redraws lines properly', function()
+ local screen
+ clear()
+ screen = Screen.new(50, 10)
+ screen:attach()
+ screen:set_default_attr_ids({
+ [1] = {bold = true, foreground = Screen.colors.Blue1},
+ [2] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red},
+ [3] = {background = Screen.colors.Red, foreground = Screen.colors.Grey100, special = Screen.colors.Yellow},
+ [4] = {bold = true, foreground = Screen.colors.SeaGreen4},
+ [5] = {special = Screen.colors.Yellow},
+ [6] = {special = Screen.colors.Yellow, bold = true, foreground = Screen.colors.SeaGreen4},
+ [7] = {foreground = Screen.colors.Grey0, background = Screen.colors.Grey100},
+ [8] = {foreground = Screen.colors.Gray90, background = Screen.colors.Grey100},
+ [9] = {foreground = tonumber('0x00000c'), background = Screen.colors.Grey100},
+ [10] = {background = Screen.colors.Grey100, bold = true, foreground = tonumber('0xe5e5ff')},
+ [11] = {background = Screen.colors.Grey100, bold = true, foreground = tonumber('0x2b8452')},
+ [12] = {bold = true, reverse = true},
+ [13] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.WebGray},
+ [14] = {reverse = true},
+ [15] = {background = Screen.colors.LightBlue},
+ [16] = {background = Screen.colors.LightCyan1, bold = true, foreground = Screen.colors.Blue1},
+ [17] = {bold = true, background = Screen.colors.Red},
+ [18] = {background = Screen.colors.LightMagenta},
+ })
+
+ insert([[
+ 1
+
+
+ 2
+ 1a
+ ]])
+ command("vnew left")
+ insert([[
+ 2
+ 2a
+ 2b
+ ]])
+ command("windo diffthis")
+ command("windo 1")
+ screen:expect{grid=[[
+ {13: }{16:-----------------------}{14:│}{13: }{15:^1 }|
+ {13: }{16:-----------------------}{14:│}{13: }{15: }|
+ {13: }{16:-----------------------}{14:│}{13: }{15: }|
+ {13: }2 {14:│}{13: }2 |
+ {13: }{17:2}{18:a }{14:│}{13: }{17:1}{18:a }|
+ {13: }{15:2b }{14:│}{13: }{16:----------------------}|
+ {13: } {14:│}{13: } |
+ {1:~ }{14:│}{1:~ }|
+ {14:left [+] }{12:[No Name] [+] }|
+ |
+ ]]}
+ feed('<C-e>')
+ feed('<C-e>')
+ feed('<C-y>')
+ feed('<C-y>')
+ feed('<C-y>')
+ screen:expect{grid=[[
+ {13: }{16:-----------------------}{14:│}{13: }{15:1 }|
+ {13: }{16:-----------------------}{14:│}{13: }{15: }|
+ {13: }{16:-----------------------}{14:│}{13: }{15:^ }|
+ {13: }2 {14:│}{13: }2 |
+ {13: }{17:2}{18:a }{14:│}{13: }{17:1}{18:a }|
+ {13: }{15:2b }{14:│}{13: }{16:----------------------}|
+ {13: } {14:│}{13: } |
+ {1:~ }{14:│}{1:~ }|
+ {14:left [+] }{12:[No Name] [+] }|
+ |
+ ]]}
+end)
+
+it('diff updates line numbers below filler lines', function()
+ clear()
+ local screen = Screen.new(40, 14)
+ screen:attach()
+ screen:set_default_attr_ids({
+ [1] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.WebGray},
+ [2] = {background = Screen.colors.LightCyan1, bold = true, foreground = Screen.colors.Blue1},
+ [3] = {reverse = true},
+ [4] = {background = Screen.colors.LightBlue},
+ [5] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.LightGrey},
+ [6] = {bold = true, foreground = Screen.colors.Blue1},
+ [7] = {bold = true, reverse = true},
+ [8] = {bold = true, background = Screen.colors.Red},
+ [9] = {background = Screen.colors.LightMagenta},
+ [10] = {bold = true, foreground = Screen.colors.Brown},
+ [11] = {foreground = Screen.colors.Brown},
+ })
+ source([[
+ call setline(1, ['a', 'a', 'a', 'y', 'b', 'b', 'b', 'b', 'b'])
+ vnew
+ call setline(1, ['a', 'a', 'a', 'x', 'x', 'x', 'b', 'b', 'b', 'b', 'b'])
+ windo diffthis
+ setlocal number rnu foldcolumn=0
+ ]])
+ screen:expect([[
+ {1: }a {3:│}{10:1 }^a |
+ {1: }a {3:│}{11: 1 }a |
+ {1: }a {3:│}{11: 2 }a |
+ {1: }{8:x}{9: }{3:│}{11: 3 }{8:y}{9: }|
+ {1: }{4:x }{3:│}{11: }{2:----------------}|
+ {1: }{4:x }{3:│}{11: }{2:----------------}|
+ {1: }b {3:│}{11: 4 }b |
+ {1: }b {3:│}{11: 5 }b |
+ {1: }b {3:│}{11: 6 }b |
+ {1: }b {3:│}{11: 7 }b |
+ {1: }b {3:│}{11: 8 }b |
+ {6:~ }{3:│}{6:~ }|
+ {3:[No Name] [+] }{7:[No Name] [+] }|
+ |
+ ]])
+ feed('j')
+ screen:expect([[
+ {1: }a {3:│}{11: 1 }a |
+ {1: }a {3:│}{10:2 }^a |
+ {1: }a {3:│}{11: 1 }a |
+ {1: }{8:x}{9: }{3:│}{11: 2 }{8:y}{9: }|
+ {1: }{4:x }{3:│}{11: }{2:----------------}|
+ {1: }{4:x }{3:│}{11: }{2:----------------}|
+ {1: }b {3:│}{11: 3 }b |
+ {1: }b {3:│}{11: 4 }b |
+ {1: }b {3:│}{11: 5 }b |
+ {1: }b {3:│}{11: 6 }b |
+ {1: }b {3:│}{11: 7 }b |
+ {6:~ }{3:│}{6:~ }|
+ {3:[No Name] [+] }{7:[No Name] [+] }|
+ |
+ ]])
+ feed('j')
+ screen:expect([[
+ {1: }a {3:│}{11: 2 }a |
+ {1: }a {3:│}{11: 1 }a |
+ {1: }a {3:│}{10:3 }^a |
+ {1: }{8:x}{9: }{3:│}{11: 1 }{8:y}{9: }|
+ {1: }{4:x }{3:│}{11: }{2:----------------}|
+ {1: }{4:x }{3:│}{11: }{2:----------------}|
+ {1: }b {3:│}{11: 2 }b |
+ {1: }b {3:│}{11: 3 }b |
+ {1: }b {3:│}{11: 4 }b |
+ {1: }b {3:│}{11: 5 }b |
+ {1: }b {3:│}{11: 6 }b |
+ {6:~ }{3:│}{6:~ }|
+ {3:[No Name] [+] }{7:[No Name] [+] }|
+ |
+ ]])
+end)
diff --git a/test/functional/ui/embed_spec.lua b/test/functional/ui/embed_spec.lua
index f3cd223f53..8218c8e12d 100644
--- a/test/functional/ui/embed_spec.lua
+++ b/test/functional/ui/embed_spec.lua
@@ -50,8 +50,7 @@ local function test_embed(ext_linegrid)
it("doesn't erase output when setting color scheme", function()
if 'openbsd' == helpers.uname() then
- pending('FIXME #10804', function() end)
- return
+ pending('FIXME #10804')
end
startup('--cmd', 'echoerr "foo"', '--cmd', 'color default', '--cmd', 'echoerr "bar"')
screen:expect([[
diff --git a/test/functional/ui/float_spec.lua b/test/functional/ui/float_spec.lua
index dbaf6f802b..11fe861de8 100644
--- a/test/functional/ui/float_spec.lua
+++ b/test/functional/ui/float_spec.lua
@@ -2,9 +2,11 @@ local helpers = require('test.functional.helpers')(after_each)
local Screen = require('test.functional.ui.screen')
local os = require('os')
local clear, feed = helpers.clear, helpers.feed
+local assert_alive = helpers.assert_alive
local command, feed_command = helpers.command, helpers.feed_command
local eval = helpers.eval
local eq = helpers.eq
+local exec_lua = helpers.exec_lua
local insert = helpers.insert
local meths = helpers.meths
local curbufmeths = helpers.curbufmeths
@@ -12,7 +14,7 @@ local funcs = helpers.funcs
local run = helpers.run
local pcall_err = helpers.pcall_err
-describe('floating windows', function()
+describe('floatwin', function()
before_each(function()
clear()
end)
@@ -39,6 +41,7 @@ describe('floating windows', function()
[19] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.WebGray},
[20] = {bold = true, foreground = Screen.colors.Brown},
[21] = {background = Screen.colors.Gray90},
+ [22] = {background = Screen.colors.LightRed},
}
it('behavior', function()
@@ -55,6 +58,31 @@ describe('floating windows', function()
eq(1000, funcs.win_getid())
end)
+ it('closed immediately by autocmd #11383', function()
+ eq('Error executing lua: [string "<nvim>"]:4: Window was closed immediately',
+ pcall_err(exec_lua, [[
+ local a = vim.api
+ local function crashes(contents)
+ local buf = a.nvim_create_buf(false, true)
+ local floatwin = a.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)
+ local winnr = vim.fn.win_id2win(floatwin)
+ a.nvim_command('wincmd p')
+ a.nvim_command('autocmd CursorMoved * ++once '..winnr..'wincmd c')
+ return buf, floatwin
+ end
+ crashes{'foo'}
+ crashes{'bar'}
+ ]]))
+ assert_alive()
+ end)
+
local function with_ext_multigrid(multigrid)
local screen
before_each(function()
@@ -398,6 +426,7 @@ describe('floating windows', function()
it("can use 'minimal' style", function()
command('set number')
command('set signcolumn=yes')
+ command('set colorcolumn=1')
command('set cursorline')
command('set foldcolumn=1')
command('hi NormalFloat guibg=#333333')
@@ -414,9 +443,9 @@ describe('floating windows', function()
[2:----------------------------------------]|
[3:----------------------------------------]|
## grid 2
- {19: }{20: 1 }{21:^x }|
- {19: }{14: 2 }y |
- {19: }{14: 3 } |
+ {19: }{20: 1 }{22:^x}{21: }|
+ {19: }{14: 2 }{22:y} |
+ {19: }{14: 3 }{22: } |
{0:~ }|
{0:~ }|
{0:~ }|
@@ -430,9 +459,9 @@ describe('floating windows', function()
]], float_pos={[4] = {{id = 1001}, "NW", 1, 4, 10, true}}}
else
screen:expect{grid=[[
- {19: }{20: 1 }{21:^x }|
- {19: }{14: 2 }y |
- {19: }{14: 3 } {15:x } |
+ {19: }{20: 1 }{22:^x}{21: }|
+ {19: }{14: 2 }{22:y} |
+ {19: }{14: 3 }{22: } {15:x } |
{0:~ }{15:y }{0: }|
{0:~ }{15: }{0: }|
{0:~ }{15: }{0: }|
@@ -454,9 +483,9 @@ describe('floating windows', function()
[2:----------------------------------------]|
[3:----------------------------------------]|
## grid 2
- {19: }{17:ðŒ¢Ì€Ì̂̃̅̄ðŒ¢Ì€Ì̂̃̅̄}{20: 1 }{21:^x }|
- {19: }{14: 2 }y |
- {19: }{14: 3 } |
+ {19: }{17:ðŒ¢Ì€Ì̂̃̅̄ðŒ¢Ì€Ì̂̃̅̄}{20: 1 }{22:^x}{21: }|
+ {19: }{14: 2 }{22:y} |
+ {19: }{14: 3 }{22: } |
{0:~ }|
{0:~ }|
{0:~ }|
@@ -471,9 +500,9 @@ describe('floating windows', function()
else
screen:expect([[
- {19: }{17:ðŒ¢Ì€Ì̂̃̅̄ðŒ¢Ì€Ì̂̃̅̄}{20: 1 }{21:^x }|
- {19: }{14: 2 }y |
- {19: }{14: 3 } {17:ðŒ¢Ì€Ì̂̃̅̄ðŒ¢Ì€Ì̂̃̅̄}{15:x } |
+ {19: }{17:ðŒ¢Ì€Ì̂̃̅̄ðŒ¢Ì€Ì̂̃̅̄}{20: 1 }{22:^x}{21: }|
+ {19: }{14: 2 }{22:y} |
+ {19: }{14: 3 }{22: } {17:ðŒ¢Ì€Ì̂̃̅̄ðŒ¢Ì€Ì̂̃̅̄}{15:x } |
{0:~ }{19: }{15:y }{0: }|
{0:~ }{19: }{15: }{0: }|
{0:~ }{15: }{0: }|
@@ -495,9 +524,9 @@ describe('floating windows', function()
[2:----------------------------------------]|
[3:----------------------------------------]|
## grid 2
- {19: }{20: 1 }{21:^x }|
- {19: }{14: 2 }y |
- {19: }{14: 3 } |
+ {19: }{20: 1 }{22:^x}{21: }|
+ {19: }{14: 2 }{22:y} |
+ {19: }{14: 3 }{22: } |
{0:~ }|
{0:~ }|
{0:~ }|
@@ -511,9 +540,9 @@ describe('floating windows', function()
]], float_pos={[4] = {{id = 1001}, "NW", 1, 4, 10, true}}}
else
screen:expect([[
- {19: }{20: 1 }{21:^x }|
- {19: }{14: 2 }y |
- {19: }{14: 3 } {15: } |
+ {19: }{20: 1 }{22:^x}{21: }|
+ {19: }{14: 2 }{22:y} |
+ {19: }{14: 3 }{22: } {15: } |
{0:~ }{15: }{0: }|
{0:~ }{15: }{0: }|
{0:~ }{15: }{0: }|
@@ -947,6 +976,28 @@ describe('floating windows', function()
{2:~ }|
]], float_pos={
[5] = {{id = 1002}, "NE", 4, 0, 50, true}
+ }, win_viewport = {
+ [2] = {
+ topline = 0,
+ botline = 3,
+ curline = 0,
+ curcol = 3,
+ win = { id = 1000 }
+ },
+ [4] = {
+ topline = 0,
+ botline = 3,
+ curline = 0,
+ curcol = 3,
+ win = { id = 1001 }
+ },
+ [5] = {
+ topline = 0,
+ botline = 2,
+ curline = 0,
+ curcol = 0,
+ win = { id = 1002 }
+ }
}}
else
screen:expect([[
@@ -2003,10 +2054,10 @@ describe('floating windows', function()
screen:expect{grid=[[
## grid 1
[2:----------------------------------------]|
- [2:----------------------------------------]|
- [2:----------------------------------------]|
- [2:----------------------------------------]|
- [2:----------------------------------------]|
+ {5:[No Name] }|
+ [5:----------------------------------------]|
+ [5:----------------------------------------]|
+ [5:----------------------------------------]|
{5:[Preview] }|
[3:----------------------------------------]|
## grid 2
@@ -2017,6 +2068,10 @@ describe('floating windows', function()
{17:f}{1:oo }|
{17:b}{1:ar }|
{1: }|
+ ## grid 5
+ |1| {17:f}oo |
+ |2| {17:b}ar |
+ {0:~ }|
]], float_pos=expected_pos}
else
screen:expect([[
diff --git a/test/functional/ui/fold_spec.lua b/test/functional/ui/fold_spec.lua
index c5ef718883..6ec45064da 100644
--- a/test/functional/ui/fold_spec.lua
+++ b/test/functional/ui/fold_spec.lua
@@ -24,10 +24,6 @@ describe("folded lines", function()
})
end)
- after_each(function()
- screen:detach()
- end)
-
it("work with more than one signcolumn", function()
command("set signcolumn=yes:9")
feed("i<cr><esc>")
@@ -64,6 +60,57 @@ describe("folded lines", function()
]])
end)
+ it("works with multibyte fillchars", function()
+ insert([[
+ aa
+ bb
+ cc
+ dd
+ ee
+ ff]])
+ command("set fillchars+=foldopen:▾,foldsep:│,foldclose:▸")
+ feed_command('1')
+ command("set foldcolumn=2")
+ feed('zf4j')
+ feed('zf2j')
+ feed('zO')
+ screen:expect{grid=[[
+ {7:▾▾}^aa |
+ {7:││}bb |
+ {7:││}cc |
+ {7:││}dd |
+ {7:││}ee |
+ {7:│ }ff |
+ {1:~ }|
+ :1 |
+ ]]}
+
+ feed_command("set rightleft")
+ screen:expect{grid=[[
+ a^a{7:▾▾}|
+ bb{7:││}|
+ cc{7:││}|
+ dd{7:││}|
+ ee{7:││}|
+ ff{7: │}|
+ {1: ~}|
+ :set rightleft |
+ ]]}
+
+ feed_command("set norightleft")
+ meths.input_mouse('left', 'press', '', 0, 0, 1)
+ screen:expect{grid=[[
+ {7:▾▸}{5:^+--- 5 lines: aa··························}|
+ {7:│ }ff |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ :set norightleft |
+ ]]}
+ end)
+
it("works with multibyte text", function()
-- Currently the only allowed value of 'maxcombine'
eq(6, meths.get_option('maxcombine'))
@@ -248,4 +295,64 @@ describe("folded lines", function()
]])
end)
+
+ it("work with autoresize", function()
+
+ funcs.setline(1, 'line 1')
+ funcs.setline(2, 'line 2')
+ funcs.setline(3, 'line 3')
+ funcs.setline(4, 'line 4')
+
+ feed("zfj")
+ command("set foldcolumn=0")
+ screen:expect{grid=[[
+ {5:^+-- 2 lines: line 1·························}|
+ line 3 |
+ line 4 |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+ -- should adapt to the current nesting of folds (e.g., 1)
+ command("set foldcolumn=auto:1")
+ screen:expect{grid=[[
+ {7:+}{5:^+-- 2 lines: line 1························}|
+ {7: }line 3 |
+ {7: }line 4 |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+ -- fdc should not change with a new fold as the maximum is 1
+ feed("zf3j")
+
+ screen:expect{grid=[[
+ {7:+}{5:^+-- 4 lines: line 1························}|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+
+ -- relax the maximum fdc thus fdc should expand to
+ -- accomodate the current number of folds
+ command("set foldcolumn=auto:4")
+ screen:expect{grid=[[
+ {7:+ }{5:^+-- 4 lines: line 1·······················}|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+ end)
end)
diff --git a/test/functional/ui/highlight_spec.lua b/test/functional/ui/highlight_spec.lua
index f40f658275..28e4e88326 100644
--- a/test/functional/ui/highlight_spec.lua
+++ b/test/functional/ui/highlight_spec.lua
@@ -35,7 +35,6 @@ describe('highlight: `:syntax manual`', function()
end)
after_each(function()
- screen:detach()
os.remove('Xtest-functional-ui-highlight.tmp.vim')
end)
@@ -97,10 +96,6 @@ describe('highlight defaults', function()
command("set display-=msgsep")
end)
- after_each(function()
- screen:detach()
- end)
-
it('window status bar', function()
screen:set_default_attr_ids({
[0] = {bold=true, foreground=Screen.colors.Blue},
@@ -346,17 +341,10 @@ describe('highlight defaults', function()
end)
describe('highlight', function()
- local screen
-
- before_each(function()
- clear()
- screen = Screen.new(25,10)
- screen:attach()
- end)
+ before_each(clear)
it('visual', function()
- screen:detach()
- screen = Screen.new(20,4)
+ local screen = Screen.new(20,4)
screen:attach()
screen:set_default_attr_ids({
[1] = {background = Screen.colors.LightGrey},
@@ -389,8 +377,7 @@ describe('highlight', function()
end)
it('cterm=standout gui=standout', function()
- screen:detach()
- screen = Screen.new(20,5)
+ local screen = Screen.new(20,5)
screen:attach()
screen:set_default_attr_ids({
[1] = {bold = true, foreground = Screen.colors.Blue1},
@@ -413,8 +400,7 @@ describe('highlight', function()
end)
it('strikethrough', function()
- screen:detach()
- screen = Screen.new(25,6)
+ local screen = Screen.new(25,6)
screen:attach()
feed_command('syntax on')
feed_command('syn keyword TmpKeyword foo')
@@ -438,7 +424,56 @@ describe('highlight', function()
})
end)
+ it('nocombine', function()
+ local screen = Screen.new(25,6)
+ screen:set_default_attr_ids{
+ [1] = {foreground = Screen.colors.SlateBlue, underline = true},
+ [2] = {bold = true, foreground = Screen.colors.Blue1},
+ [3] = {underline = true, reverse = true, foreground = Screen.colors.SlateBlue},
+ [4] = {background = Screen.colors.Yellow, reverse = true, foreground = Screen.colors.SlateBlue},
+ [5] = {foreground = Screen.colors.Red},
+ }
+ screen:attach()
+ feed_command('syntax on')
+ feed_command('hi! Underlined cterm=underline gui=underline')
+ feed_command('syn keyword Underlined foobar')
+ feed_command('hi Search cterm=inverse,nocombine gui=inverse,nocombine')
+ insert([[
+ foobar
+ foobar
+ ]])
+ screen:expect{grid=[[
+ {1:foobar} |
+ {1:foobar} |
+ ^ |
+ {2:~ }|
+ {2:~ }|
+ |
+ ]]}
+
+ feed('/foo')
+ screen:expect{grid=[[
+ {3:foo}{1:bar} |
+ {4:foo}{1:bar} |
+ |
+ {2:~ }|
+ {2:~ }|
+ /foo^ |
+ ]]}
+ feed('<cr>')
+ screen:expect{grid=[[
+ {4:^foo}{1:bar} |
+ {4:foo}{1:bar} |
+ |
+ {2:~ }|
+ {2:~ }|
+ {5:search hit...uing at TOP} |
+ ]]}
+ end)
+
it('guisp (special/undercurl)', function()
+ local screen = Screen.new(25,10)
+ screen:attach()
feed_command('syntax on')
feed_command('syn keyword TmpKeyword neovim')
feed_command('syn keyword TmpKeyword1 special')
@@ -494,10 +529,6 @@ describe("'listchars' highlight", function()
screen:attach()
end)
- after_each(function()
- screen:detach()
- end)
-
it("'cursorline' and 'cursorcolumn'", function()
screen:set_default_attr_ids({
[0] = {bold=true, foreground=Screen.colors.Blue},
@@ -657,6 +688,30 @@ describe("'listchars' highlight", function()
]])
end)
+ it("'listchar' with wrap", function()
+ screen:set_default_attr_ids({
+ [0] = {bold=true, foreground=Screen.colors.Blue},
+ })
+ feed_command('set wrap')
+ feed_command('set listchars=eol:¬,precedes:< list')
+ feed('90ia<esc>')
+ screen:expect([[
+ {0:<}aaaaaaaaaaaaaaaaaaa|
+ aaaaaaaaaaaaaaaaaaaa|
+ aaaaaaaaaaaaaaaaaaaa|
+ aaaaaaaaa^a{0:¬} |
+ |
+ ]])
+ feed('0')
+ screen:expect([[
+ ^aaaaaaaaaaaaaaaaaaaa|
+ aaaaaaaaaaaaaaaaaaaa|
+ aaaaaaaaaaaaaaaaaaaa|
+ aaaaaaaaaaaaaaaaaaaa|
+ |
+ ]])
+ end)
+
it("'listchar' in visual mode", function()
screen:set_default_attr_ids({
[1] = {background=Screen.colors.Grey90},
@@ -1131,6 +1186,7 @@ describe("'winhighlight' highlight", function()
[25] = {bold = true, foreground = Screen.colors.Green1},
[26] = {background = Screen.colors.Red},
[27] = {background = Screen.colors.DarkBlue, bold = true, foreground = Screen.colors.Green1},
+ [28] = {bold = true, foreground = Screen.colors.Brown},
})
command("hi Background1 guibg=DarkBlue")
command("hi Background2 guibg=DarkGreen")
@@ -1543,4 +1599,45 @@ describe("'winhighlight' highlight", function()
{21:-- }{22:match 1 of 3} |
]])
end)
+
+ it('can override CursorLine and CursorLineNr', function()
+ -- CursorLine used to be parsed as CursorLineNr, because strncmp
+ command('set cursorline number')
+ command('split')
+ command('set winhl=CursorLine:Background1')
+ screen:expect{grid=[[
+ {28: 1 }{1:^ }|
+ {0:~ }|
+ {0:~ }|
+ {3:[No Name] }|
+ {28: 1 }{18: }|
+ {0:~ }|
+ {4:[No Name] }|
+ |
+ ]]}
+
+ command('set winhl=CursorLineNr:Background2,CursorLine:Background1')
+ screen:expect{grid=[[
+ {5: 1 }{1:^ }|
+ {0:~ }|
+ {0:~ }|
+ {3:[No Name] }|
+ {28: 1 }{18: }|
+ {0:~ }|
+ {4:[No Name] }|
+ |
+ ]]}
+
+ feed('<c-w>w')
+ screen:expect{grid=[[
+ {5: 1 }{1: }|
+ {0:~ }|
+ {0:~ }|
+ {4:[No Name] }|
+ {28: 1 }{18:^ }|
+ {0:~ }|
+ {3:[No Name] }|
+ |
+ ]]}
+ end)
end)
diff --git a/test/functional/ui/hlstate_spec.lua b/test/functional/ui/hlstate_spec.lua
index d1c115587e..2a567b28ee 100644
--- a/test/functional/ui/hlstate_spec.lua
+++ b/test/functional/ui/hlstate_spec.lua
@@ -181,11 +181,11 @@ describe('ext_hlstate detailed highlights', function()
it("work with :terminal", function()
screen:set_default_attr_ids({
[1] = {{}, {{hi_name = "TermCursorNC", ui_name = "TermCursorNC", kind = "ui"}}},
- [2] = {{foreground = 52479}, {{kind = "term"}}},
- [3] = {{bold = true, foreground = 52479}, {{kind = "term"}}},
- [4] = {{foreground = 52479}, {2, 1}},
- [5] = {{foreground = 4259839}, {{kind = "term"}}},
- [6] = {{foreground = 4259839}, {5, 1}},
+ [2] = {{foreground = tonumber('0x00ccff'), fg_indexed=true}, {{kind = "term"}}},
+ [3] = {{bold = true, foreground = tonumber('0x00ccff'), fg_indexed=true}, {{kind = "term"}}},
+ [4] = {{foreground = tonumber('0x00ccff'), fg_indexed=true}, {2, 1}},
+ [5] = {{foreground = tonumber('0x40ffff'), fg_indexed=true}, {{kind = "term"}}},
+ [6] = {{foreground = tonumber('0x40ffff'), fg_indexed=true}, {5, 1}},
[7] = {{}, {{hi_name = "MsgArea", ui_name = "MsgArea", kind = "ui"}}},
})
command('enew | call termopen(["'..nvim_dir..'/tty-test"])')
@@ -259,7 +259,7 @@ describe('ext_hlstate detailed highlights', function()
it("can use independent cterm and rgb colors", function()
-- tell test module to save all attributes (doesn't change nvim options)
- screen:set_hlstate_cterm(true)
+ screen:set_rgb_cterm(true)
screen:set_default_attr_ids({
[1] = {{bold = true, foreground = Screen.colors.Blue1}, {foreground = 12}, {{hi_name = "NonText", ui_name = "EndOfBuffer", kind = "ui"}}},
diff --git a/test/functional/ui/inccommand_spec.lua b/test/functional/ui/inccommand_spec.lua
index 351c4b4bcf..afb0c9cfa6 100644
--- a/test/functional/ui/inccommand_spec.lua
+++ b/test/functional/ui/inccommand_spec.lua
@@ -18,6 +18,7 @@ local wait = helpers.wait
local nvim = helpers.nvim
local sleep = helpers.sleep
local nvim_dir = helpers.nvim_dir
+local assert_alive = helpers.assert_alive
local default_text = [[
Inc substitution on
@@ -84,18 +85,19 @@ local function common_setup(screen, inccommand, text)
[14] = {foreground = Screen.colors.White, background = Screen.colors.Red},
[15] = {bold=true, foreground=Screen.colors.Blue},
[16] = {background=Screen.colors.Grey90}, -- cursorline
+ [17] = {foreground = Screen.colors.Blue1},
vis = {background=Screen.colors.LightGrey}
})
end
- command("set inccommand=" .. (inccommand and inccommand or ""))
+ command("set inccommand=" .. (inccommand or ""))
if text then
insert(text)
end
end
-describe(":substitute, inccommand=split", function()
+describe(":substitute, inccommand=split interactivity", function()
before_each(function()
clear()
common_setup(nil, "split", default_text)
@@ -556,7 +558,6 @@ describe(":substitute, 'inccommand' preserves undo", function()
]])
end
end
- screen:detach()
end)
it('with undolevels=2', function()
@@ -647,7 +648,6 @@ describe(":substitute, 'inccommand' preserves undo", function()
Already ...t change |
]])
end
- screen:detach()
end
end)
@@ -713,7 +713,6 @@ describe(":substitute, 'inccommand' preserves undo", function()
Already ...t change |
]])
end
- screen:detach()
end)
end)
@@ -726,19 +725,15 @@ describe(":substitute, inccommand=split", function()
common_setup(screen, "split", default_text .. default_text)
end)
- after_each(function()
- screen:detach()
- end)
-
it("preserves 'modified' buffer flag", function()
feed_command("set nomodified")
feed(":%s/tw")
screen:expect([[
Inc substitution on |
{12:tw}o lines |
+ Inc substitution on |
+ {12:tw}o lines |
|
- {15:~ }|
- {15:~ }|
{11:[No Name] }|
|2| {12:tw}o lines |
|4| {12:tw}o lines |
@@ -786,6 +781,59 @@ describe(":substitute, inccommand=split", function()
{15:~ }|
:silent tabedit %s/tw/to^ |
]])
+ feed('<Esc>')
+
+ -- leading colons
+ feed(':::%s/tw/to')
+ screen:expect{any=[[{12:to}o lines]]}
+ feed('<Esc>')
+ screen:expect{any=[[two lines]]}
+ end)
+
+ it("ignores new-window modifiers when splitting the preview window", function()
+ -- one modifier
+ feed(':topleft %s/tw/to')
+ screen:expect([[
+ Inc substitution on |
+ {12:to}o lines |
+ Inc substitution on |
+ {12:to}o lines |
+ |
+ {11:[No Name] [+] }|
+ |2| {12:to}o lines |
+ |4| {12:to}o lines |
+ {15:~ }|
+ {15:~ }|
+ {15:~ }|
+ {15:~ }|
+ {15:~ }|
+ {10:[Preview] }|
+ :topleft %s/tw/to^ |
+ ]])
+ feed('<Esc>')
+ screen:expect{any=[[two lines]]}
+
+ -- multiple modifiers
+ feed(':topleft vert %s/tw/to')
+ screen:expect([[
+ Inc substitution on |
+ {12:to}o lines |
+ Inc substitution on |
+ {12:to}o lines |
+ |
+ {11:[No Name] [+] }|
+ |2| {12:to}o lines |
+ |4| {12:to}o lines |
+ {15:~ }|
+ {15:~ }|
+ {15:~ }|
+ {15:~ }|
+ {15:~ }|
+ {10:[Preview] }|
+ :topleft vert %s/tw/to^ |
+ ]])
+ feed('<Esc>')
+ screen:expect{any=[[two lines]]}
end)
it('shows split window when typing the pattern', function()
@@ -793,9 +841,9 @@ describe(":substitute, inccommand=split", function()
screen:expect([[
Inc substitution on |
{12:tw}o lines |
+ Inc substitution on |
+ {12:tw}o lines |
|
- {15:~ }|
- {15:~ }|
{11:[No Name] [+] }|
|2| {12:tw}o lines |
|4| {12:tw}o lines |
@@ -814,9 +862,9 @@ describe(":substitute, inccommand=split", function()
screen:expect([[
Inc substitution on |
o lines |
+ Inc substitution on |
+ o lines |
|
- {15:~ }|
- {15:~ }|
{11:[No Name] [+] }|
|2| o lines |
|4| o lines |
@@ -833,9 +881,9 @@ describe(":substitute, inccommand=split", function()
screen:expect([[
Inc substitution on |
{12:x}o lines |
+ Inc substitution on |
+ {12:x}o lines |
|
- {15:~ }|
- {15:~ }|
{11:[No Name] [+] }|
|2| {12:x}o lines |
|4| {12:x}o lines |
@@ -852,9 +900,9 @@ describe(":substitute, inccommand=split", function()
screen:expect([[
Inc substitution on |
o lines |
+ Inc substitution on |
+ o lines |
|
- {15:~ }|
- {15:~ }|
{11:[No Name] [+] }|
|2| o lines |
|4| o lines |
@@ -874,9 +922,9 @@ describe(":substitute, inccommand=split", function()
screen:expect([[
Inc substitution on |
{12:XX}o lines |
+ Inc substitution on |
+ {12:XX}o lines |
|
- {15:~ }|
- {15:~ }|
{11:[No Name] [+] }|
|2| {12:XX}o lines |
|4| {12:XX}o lines |
@@ -938,11 +986,11 @@ describe(":substitute, inccommand=split", function()
feed(":%s/tw")
-- 'cursorline' is NOT active during preview.
screen:expect([[
+ Inc substitution on |
{12:tw}o lines |
Inc substitution on |
{12:tw}o lines |
|
- {15:~ }|
{11:[No Name] [+] }|
|2| {12:tw}o lines |
|4| {12:tw}o lines |
@@ -1241,10 +1289,6 @@ describe("inccommand=nosplit", function()
common_setup(screen, "nosplit", default_text .. default_text)
end)
- after_each(function()
- if screen then screen:detach() end
- end)
-
it("works with :smagic, :snomagic", function()
feed_command("set hlsearch")
insert("Line *.3.* here")
@@ -1719,10 +1763,6 @@ describe("'inccommand' split windows", function()
common_setup(screen, "split", default_text)
end
- after_each(function()
- screen:detach()
- end)
-
it('work after more splits', function()
refresh()
@@ -2205,11 +2245,11 @@ describe(":substitute", function()
feed("/KKK")
screen:expect([[
+ T T123 T T123 T2T TT T23423424|
+ x |
afa {12:KKK}adf la;lkd {12:KKK}alx |
|
{15:~ }|
- {15:~ }|
- {15:~ }|
{11:[No Name] [+] }|
|3| afa {12:KKK}adf la;lkd {12:KKK}alx |
{15:~ }|
@@ -2253,6 +2293,76 @@ 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)
+ common_setup(screen, "split", string.rep(text,10))
+ feed(":%s/\\n\\n/<c-v><c-m>/g")
+ screen:expect{grid=[[
+ CCC CC |
+ AAA AA |
+ BBB BB |
+ CCC CC |
+ |
+ {11:[No Name] [+] }|
+ | 1| AAA AA |
+ | 2|{12: }BBB BB |
+ | 3|{12: }CCC CC |
+ | 4|{12: }AAA AA |
+ | 5|{12: }BBB BB |
+ | 6|{12: }CCC CC |
+ | 7|{12: }AAA AA |
+ {10:[Preview] }|
+ :%s/\n\n/{17:^M}/g^ |
+ ]]}
+ assert_alive()
+ 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")
+ screen:expect{grid=[[
+ CCC CC |
+ AAA AA |
+ BBB BB |
+ CCC CC |
+ AAA AA |
+ BBB BB |
+ CCC CC |
+ AAA AA |
+ BBB BB |
+ CCC CC |
+ AAA AA |
+ BBB BB |
+ CCC CC |
+ |
+ :%s/\n\n/{17:^M}/g^ |
+ ]]}
+ assert_alive()
+ end)
+
it("inccommand=split, multibyte text", function()
common_setup(screen, "split", multibyte_text)
feed(":%s/£.*ѫ/X¥¥")
@@ -2485,11 +2595,11 @@ describe(":substitute", function()
wait()
feed([[:%s/\(some\)\@<lt>!thing/one/]])
screen:expect([[
+ something |
every{12:one} |
someone |
{15:~ }|
{15:~ }|
- {15:~ }|
{11:[No Name] [+] }|
|2| every{12:one} |
{15:~ }|
@@ -2527,11 +2637,11 @@ describe(":substitute", function()
wait()
feed([[:%s/some\(thing\)\@!/every/]])
screen:expect([[
+ something |
+ everything |
{12:every}one |
{15:~ }|
{15:~ }|
- {15:~ }|
- {15:~ }|
{11:[No Name] [+] }|
|3| {12:every}one |
{15:~ }|
@@ -2544,6 +2654,49 @@ describe(":substitute", function()
:%s/some\(thing\)\@!/every/^ |
]])
end)
+
+ it("doesn't prompt to swap cmd range", function()
+ screen = Screen.new(50, 8) -- wide to avoid hit-enter prompt
+ common_setup(screen, "split", default_text)
+ feed(':2,1s/tw/MO/g')
+
+ -- substitution preview should have been made, without prompting
+ screen:expect([[
+ {12:MO}o lines |
+ {11:[No Name] [+] }|
+ |2| {12:MO}o lines |
+ {15:~ }|
+ {15:~ }|
+ {15:~ }|
+ {10:[Preview] }|
+ :2,1s/tw/MO/g^ |
+ ]])
+
+ -- but should be prompted on hitting enter
+ feed('<CR>')
+ screen:expect([[
+ {12:MO}o lines |
+ {11:[No Name] [+] }|
+ |2| {12:MO}o lines |
+ {15:~ }|
+ {15:~ }|
+ {15:~ }|
+ {10:[Preview] }|
+ {13:Backwards range given, OK to swap (y/n)?}^ |
+ ]])
+
+ feed('y')
+ screen:expect([[
+ Inc substitution on |
+ ^MOo lines |
+ |
+ {15:~ }|
+ {15:~ }|
+ {15:~ }|
+ {15:~ }|
+ {13:Backwards range given, OK to swap (y/n)?}y |
+ ]])
+ end)
end)
it(':substitute with inccommand during :terminal activity', function()
diff --git a/test/functional/ui/messages_spec.lua b/test/functional/ui/messages_spec.lua
index 875e4092a6..efc02db159 100644
--- a/test/functional/ui/messages_spec.lua
+++ b/test/functional/ui/messages_spec.lua
@@ -122,7 +122,7 @@ describe('ui/ext_messages', function()
feed('G$x')
screen:expect{grid=[[
line 1 |
- {IGNORE}|
+ {MATCH:.*}|
{1:~ }|
{1:~ }|
{1:~ }|
@@ -747,7 +747,7 @@ describe('ui/ext_messages', function()
{1:~ }|
{1:~ }|
]], messages={{
- content = {{'E5105: Error while calling lua chunk: [string "<VimL compiled string>"]:1: such\nmultiline\nerror', 2}},
+ content = {{'E5108: Error executing lua [string ":lua"]:1: such\nmultiline\nerror', 2}},
kind = "lua_error"
}}}
end)
@@ -810,6 +810,9 @@ describe('ui/builtin messages', function()
[4] = {bold = true, foreground = Screen.colors.SeaGreen4},
[5] = {foreground = Screen.colors.Blue1},
[6] = {bold = true, foreground = Screen.colors.Magenta},
+ [7] = {background = Screen.colors.Grey20},
+ [8] = {reverse = true},
+ [9] = {background = Screen.colors.LightRed}
})
end)
@@ -860,7 +863,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.command_output("hi ErrorMsg"))
+ meths.exec("hi ErrorMsg", true))
end)
it(':syntax list langGroup output', function()
@@ -899,9 +902,153 @@ 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.command_output('syntax list vimComment'))
+ meths.exec('syntax list vimComment', true))
-- luacheck: pop
end)
+
+ it('supports ruler with laststatus=0', function()
+ command("set ruler laststatus=0")
+ screen:expect{grid=[[
+ ^ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ 0,0-1 All |
+ ]]}
+
+ command("hi MsgArea guibg=#333333")
+ screen:expect{grid=[[
+ ^ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {7: 0,0-1 All }|
+ ]]}
+
+ command("set rulerformat=%15(%c%V\\ %p%%%)")
+ screen:expect{grid=[[
+ ^ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {7: 0,0-1 100% }|
+ ]]}
+ end)
+
+ it('supports echo with CRLF line separators', function()
+ feed(':echo "line 1\\r\\nline 2"<cr>')
+ screen:expect{grid=[[
+ |
+ {1:~ }|
+ {1:~ }|
+ {3: }|
+ line 1 |
+ line 2 |
+ {4:Press ENTER or type command to continue}^ |
+ ]]}
+
+ feed('<cr>:echo "abc\\rz"<cr>')
+ screen:expect{grid=[[
+ ^ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ zbc |
+ ]]}
+ end)
+
+ it('redraws NOT_VALID correctly after message', function()
+ -- edge case: only one window was set NOT_VALID. Orginal report
+ -- used :make, but fake it using one command to set the current
+ -- window NOT_VALID and another to show a long message.
+ command("set more")
+ feed(':new<cr><c-w><c-w>')
+ screen:expect{grid=[[
+ |
+ {1:~ }|
+ {8:[No Name] }|
+ ^ |
+ {1:~ }|
+ {3:[No Name] }|
+ :new |
+ ]]}
+
+ feed(':set colorcolumn=10 | digraphs<cr>')
+ screen:expect{grid=[[
+ :set colorcolumn=10 | digraphs |
+ NU {5:^@} 10 SH {5:^A} 1 SX {5:^B} 2 EX {5:^C} 3 |
+ ET {5:^D} 4 EQ {5:^E} 5 AK {5:^F} 6 BL {5:^G} 7 |
+ BS {5:^H} 8 HT {5:^I} 9 LF {5:^@} 10 VT {5:^K} 11 |
+ FF {5:^L} 12 CR {5:^M} 13 SO {5:^N} 14 SI {5:^O} 15 |
+ DL {5:^P} 16 D1 {5:^Q} 17 D2 {5:^R} 18 D3 {5:^S} 19 |
+ {4:-- More --}^ |
+ ]]}
+
+ feed('q')
+ screen:expect{grid=[[
+ |
+ {1:~ }|
+ {8:[No Name] }|
+ ^ {9: } |
+ {1:~ }|
+ {3:[No Name] }|
+ |
+ ]]}
+
+ -- edge case: just covers statusline
+ feed(':set colorcolumn=5 | lua error("x\\n\\nx")<cr>')
+ screen:expect{grid=[[
+ |
+ {1:~ }|
+ {3: }|
+ {2:E5108: Error executing lua [string ":lua"]:1: x} |
+ |
+ {2:x} |
+ {4:Press ENTER or type command to continue}^ |
+ ]]}
+
+ feed('<cr>')
+ screen:expect{grid=[[
+ |
+ {1:~ }|
+ {8:[No Name] }|
+ ^ {9: } |
+ {1:~ }|
+ {3:[No Name] }|
+ |
+ ]]}
+
+ -- edge case: just covers lowest window line
+ feed(':set colorcolumn=5 | lua error("x\\n\\n\\nx")<cr>')
+ screen:expect{grid=[[
+ |
+ {3: }|
+ {2:E5108: Error executing lua [string ":lua"]:1: x} |
+ |
+ |
+ {2:x} |
+ {4:Press ENTER or type command to continue}^ |
+ ]]}
+
+ feed('<cr>')
+ screen:expect{grid=[[
+ |
+ {1:~ }|
+ {8:[No Name] }|
+ ^ {9: } |
+ {1:~ }|
+ {3:[No Name] }|
+ |
+ ]]}
+ end)
end)
describe('ui/ext_messages', function()
@@ -930,7 +1077,7 @@ describe('ui/ext_messages', function()
{1:~ }|
{1:~ }|
{1:~ }|
- {IGNORE}|
+ {MATCH:.*}|
{1:~ }|
{1:~ }Nvim is open source and freely distributable{1: }|
{1:~ }https://neovim.io/#chat{1: }|
@@ -940,8 +1087,8 @@ describe('ui/ext_messages', function()
{1:~ }type :q{5:<Enter>} to exit {1: }|
{1:~ }type :help{5:<Enter>} for help {1: }|
{1:~ }|
- {IGNORE}|
- {IGNORE}|
+ {MATCH:.*}|
+ {MATCH:.*}|
{1:~ }|
{1:~ }|
{1:~ }|
@@ -986,7 +1133,7 @@ describe('ui/ext_messages', function()
|
|
|
- {IGNORE}|
+ {MATCH:.*}|
|
Nvim is open source and freely distributable |
https://neovim.io/#chat |
@@ -996,8 +1143,8 @@ describe('ui/ext_messages', function()
type :q{5:<Enter>} to exit |
type :help{5:<Enter>} for help |
|
- {IGNORE}|
- {IGNORE}|
+ {MATCH:.*}|
+ {MATCH:.*}|
|
|
|
@@ -1089,7 +1236,7 @@ aliquip ex ea commodo consequat.]])
it('can be quit', function()
screen:try_resize(25,5)
- feed(':echon join(map(range(0, &lines*2), "v:val"), "\\n")<cr>')
+ feed(':echon join(map(range(0, &lines*10), "v:val"), "\\n")<cr>')
screen:expect{grid=[[
0 |
1 |
@@ -1110,97 +1257,96 @@ aliquip ex ea commodo consequat.]])
it('handles wrapped lines with line scroll', function()
feed(':lua error(_G.x)<cr>')
screen:expect{grid=[[
- {2:E5105: Error while calling lua chun}|
- {2:k: [string "<VimL compiled string>"}|
- {2:]:1: Lorem ipsum dolor sit amet, co}|
- {2:nsectetur} |
+ {2:E5108: Error executing lua [string }|
+ {2:":lua"]:1: Lorem ipsum dolor sit am}|
+ {2:et, consectetur} |
{2:adipisicing elit, sed do eiusmod te}|
{2:mpor} |
{2:incididunt ut labore et dolore magn}|
+ {2:a aliqua.} |
{4:-- More --}^ |
]]}
feed('j')
screen:expect{grid=[[
- {2:k: [string "<VimL compiled string>"}|
- {2:]:1: Lorem ipsum dolor sit amet, co}|
- {2:nsectetur} |
+ {2:":lua"]:1: Lorem ipsum dolor sit am}|
+ {2:et, consectetur} |
{2:adipisicing elit, sed do eiusmod te}|
{2:mpor} |
{2:incididunt ut labore et dolore magn}|
{2:a aliqua.} |
+ {2:Ut enim ad minim veniam, quis nostr}|
{4:-- More --}^ |
]]}
feed('k')
screen:expect{grid=[[
- {2:E5105: Error while calling lua chun}|
- {2:k: [string "<VimL compiled string>"}|
- {2:]:1: Lorem ipsum dolor sit amet, co}|
- {2:nsectetur} |
+ {2:E5108: Error executing lua [string }|
+ {2:":lua"]:1: Lorem ipsum dolor sit am}|
+ {2:et, consectetur} |
{2:adipisicing elit, sed do eiusmod te}|
{2:mpor} |
{2:incididunt ut labore et dolore magn}|
+ {2:a aliqua.} |
{4:-- More --}^ |
]]}
feed('j')
screen:expect{grid=[[
- {2:k: [string "<VimL compiled string>"}|
- {2:]:1: Lorem ipsum dolor sit amet, co}|
- {2:nsectetur} |
+ {2:":lua"]:1: Lorem ipsum dolor sit am}|
+ {2:et, consectetur} |
{2:adipisicing elit, sed do eiusmod te}|
{2:mpor} |
{2:incididunt ut labore et dolore magn}|
{2:a aliqua.} |
+ {2:Ut enim ad minim veniam, quis nostr}|
{4:-- More --}^ |
]]}
-
end)
it('handles wrapped lines with page scroll', function()
feed(':lua error(_G.x)<cr>')
screen:expect{grid=[[
- {2:E5105: Error while calling lua chun}|
- {2:k: [string "<VimL compiled string>"}|
- {2:]:1: Lorem ipsum dolor sit amet, co}|
- {2:nsectetur} |
+ {2:E5108: Error executing lua [string }|
+ {2:":lua"]:1: Lorem ipsum dolor sit am}|
+ {2:et, consectetur} |
{2:adipisicing elit, sed do eiusmod te}|
{2:mpor} |
{2:incididunt ut labore et dolore magn}|
+ {2:a aliqua.} |
{4:-- More --}^ |
]]}
feed('d')
screen:expect{grid=[[
- {2:adipisicing elit, sed do eiusmod te}|
- {2:mpor} |
{2:incididunt ut labore et dolore magn}|
{2:a aliqua.} |
{2:Ut enim ad minim veniam, quis nostr}|
{2:ud xercitation} |
{2:ullamco laboris nisi ut} |
- {4:-- More --}^ |
+ {2:aliquip ex ea commodo consequat.} |
+ {4:Press ENTER or type command to cont}|
+ {4:inue}^ |
]]}
feed('u')
screen:expect{grid=[[
- {2:E5105: Error while calling lua chun}|
- {2:k: [string "<VimL compiled string>"}|
- {2:]:1: Lorem ipsum dolor sit amet, co}|
- {2:nsectetur} |
+ {2:E5108: Error executing lua [string }|
+ {2:":lua"]:1: Lorem ipsum dolor sit am}|
+ {2:et, consectetur} |
{2:adipisicing elit, sed do eiusmod te}|
{2:mpor} |
{2:incididunt ut labore et dolore magn}|
+ {2:a aliqua.} |
{4:-- More --}^ |
]]}
feed('d')
screen:expect{grid=[[
- {2:adipisicing elit, sed do eiusmod te}|
{2:mpor} |
{2:incididunt ut labore et dolore magn}|
{2:a aliqua.} |
{2:Ut enim ad minim veniam, quis nostr}|
{2:ud xercitation} |
{2:ullamco laboris nisi ut} |
+ {2:aliquip ex ea commodo consequat.} |
{4:-- More --}^ |
]]}
end)
@@ -1210,49 +1356,49 @@ aliquip ex ea commodo consequat.]])
feed(':lua error(_G.x)<cr>')
screen:expect{grid=[[
- {3:E5105: Error while calling lua chun}|
- {3:k: [string "<VimL compiled string>"}|
- {3:]:1: Lorem ipsum dolor sit amet, co}|
- {3:nsectetur}{5: }|
+ {3:E5108: Error executing lua [string }|
+ {3:":lua"]:1: Lorem ipsum dolor sit am}|
+ {3:et, consectetur}{5: }|
{3:adipisicing elit, sed do eiusmod te}|
{3:mpor}{5: }|
{3:incididunt ut labore et dolore magn}|
+ {3:a aliqua.}{5: }|
{6:-- More --}{5:^ }|
]]}
feed('j')
screen:expect{grid=[[
- {3:k: [string "<VimL compiled string>"}|
- {3:]:1: Lorem ipsum dolor sit amet, co}|
- {3:nsectetur}{5: }|
+ {3:":lua"]:1: Lorem ipsum dolor sit am}|
+ {3:et, consectetur}{5: }|
{3:adipisicing elit, sed do eiusmod te}|
{3:mpor}{5: }|
{3:incididunt ut labore et dolore magn}|
{3:a aliqua.}{5: }|
+ {3:Ut enim ad minim veniam, quis nostr}|
{6:-- More --}{5:^ }|
]]}
feed('k')
screen:expect{grid=[[
- {3:E5105: Error while calling lua chun}|
- {3:k: [string "<VimL compiled string>"}|
- {3:]:1: Lorem ipsum dolor sit amet, co}|
- {3:nsectetur}{5: }|
+ {3:E5108: Error executing lua [string }|
+ {3:":lua"]:1: Lorem ipsum dolor sit am}|
+ {3:et, consectetur}{5: }|
{3:adipisicing elit, sed do eiusmod te}|
{3:mpor}{5: }|
{3:incididunt ut labore et dolore magn}|
+ {3:a aliqua.}{5: }|
{6:-- More --}{5:^ }|
]]}
feed('j')
screen:expect{grid=[[
- {3:k: [string "<VimL compiled string>"}|
- {3:]:1: Lorem ipsum dolor sit amet, co}|
- {3:nsectetur}{5: }|
+ {3:":lua"]:1: Lorem ipsum dolor sit am}|
+ {3:et, consectetur}{5: }|
{3:adipisicing elit, sed do eiusmod te}|
{3:mpor}{5: }|
{3:incididunt ut labore et dolore magn}|
{3:a aliqua.}{5: }|
+ {3:Ut enim ad minim veniam, quis nostr}|
{6:-- More --}{5:^ }|
]]}
end)
@@ -1261,46 +1407,46 @@ aliquip ex ea commodo consequat.]])
command("hi MsgArea guisp=Yellow")
feed(':lua error(_G.x)<cr>')
screen:expect{grid=[[
- {3:E5105: Error while calling lua chun}|
- {3:k: [string "<VimL compiled string>"}|
- {3:]:1: Lorem ipsum dolor sit amet, co}|
- {3:nsectetur}{5: }|
+ {3:E5108: Error executing lua [string }|
+ {3:":lua"]:1: Lorem ipsum dolor sit am}|
+ {3:et, consectetur}{5: }|
{3:adipisicing elit, sed do eiusmod te}|
{3:mpor}{5: }|
{3:incididunt ut labore et dolore magn}|
+ {3:a aliqua.}{5: }|
{6:-- More --}{5:^ }|
]]}
feed('d')
screen:expect{grid=[[
- {3:adipisicing elit, sed do eiusmod te}|
- {3:mpor}{5: }|
{3:incididunt ut labore et dolore magn}|
{3:a aliqua.}{5: }|
{3:Ut enim ad minim veniam, quis nostr}|
{3:ud xercitation}{5: }|
{3:ullamco laboris nisi ut}{5: }|
- {6:-- More --}{5:^ }|
+ {3:aliquip ex ea commodo consequat.}{5: }|
+ {6:Press ENTER or type command to cont}|
+ {6:inue}{5:^ }|
]]}
feed('u')
screen:expect{grid=[[
- {3:E5105: Error while calling lua chun}|
- {3:k: [string "<VimL compiled string>"}|
- {3:]:1: Lorem ipsum dolor sit amet, co}|
- {3:nsectetur}{5: }|
+ {3:E5108: Error executing lua [string }|
+ {3:":lua"]:1: Lorem ipsum dolor sit am}|
+ {3:et, consectetur}{5: }|
{3:adipisicing elit, sed do eiusmod te}|
{3:mpor}{5: }|
{3:incididunt ut labore et dolore magn}|
+ {3:a aliqua.}{5: }|
{6:-- More --}{5:^ }|
]]}
feed('d')
screen:expect{grid=[[
- {3:adipisicing elit, sed do eiusmod te}|
{3:mpor}{5: }|
{3:incididunt ut labore et dolore magn}|
{3:a aliqua.}{5: }|
{3:Ut enim ad minim veniam, quis nostr}|
{3:ud xercitation}{5: }|
{3:ullamco laboris nisi ut}{5: }|
+ {3:aliquip ex ea commodo consequat.}{5: }|
{6:-- More --}{5:^ }|
]]}
end)
@@ -1437,23 +1583,23 @@ aliquip ex ea commodo consequat.]])
it('can be resized', function()
feed(':lua error(_G.x)<cr>')
screen:expect{grid=[[
- {2:E5105: Error while calling lua chun}|
- {2:k: [string "<VimL compiled string>"}|
- {2:]:1: Lorem ipsum dolor sit amet, co}|
- {2:nsectetur} |
+ {2:E5108: Error executing lua [string }|
+ {2:":lua"]:1: Lorem ipsum dolor sit am}|
+ {2:et, consectetur} |
{2:adipisicing elit, sed do eiusmod te}|
{2:mpor} |
{2:incididunt ut labore et dolore magn}|
+ {2:a aliqua.} |
{4:-- More --}^ |
]]}
-- responds to resize, but text is not reflown
screen:try_resize(45, 5)
screen:expect{grid=[[
- {2:nsectetur} |
{2:adipisicing elit, sed do eiusmod te} |
{2:mpor} |
{2:incididunt ut labore et dolore magn} |
+ {2:a aliqua.} |
{4:-- More --}^ |
]]}
@@ -1461,14 +1607,14 @@ aliquip ex ea commodo consequat.]])
-- text is not reflown; existing lines get cut
screen:try_resize(30, 12)
screen:expect{grid=[[
- {2:E5105: Error while calling lua}|
- {2:k: [string "<VimL compiled str}|
- {2:]:1: Lorem ipsum dolor sit ame}|
- {2:nsectetur} |
+ {2:E5108: Error executing lua [st}|
+ {2:":lua"]:1: Lorem ipsum dolor s}|
+ {2:et, consectetur} |
{2:adipisicing elit, sed do eiusm}|
{2:mpore} |
{2:incididunt ut labore et dolore}|
- {2: magn} |
+ {2:a aliqua.} |
+ |
|
|
|
@@ -1479,18 +1625,18 @@ aliquip ex ea commodo consequat.]])
-- wrapped at the new screen size.
feed('<cr>')
screen:expect{grid=[[
- {2:k: [string "<VimL compiled str}|
- {2:]:1: Lorem ipsum dolor sit ame}|
- {2:nsectetur} |
+ {2:et, consectetur} |
{2:adipisicing elit, sed do eiusm}|
{2:mpore} |
{2:incididunt ut labore et dolore}|
- {2: magna aliqua.} |
+ {2:a aliqua.} |
{2:Ut enim ad minim veniam, quis }|
{2:nostrud xercitation} |
{2:ullamco laboris nisi ut} |
{2:aliquip ex ea commodo consequa}|
- {4:-- More --}^ |
+ {2:t.} |
+ {4:Press ENTER or type command to}|
+ {4: continue}^ |
]]}
feed('q')
diff --git a/test/functional/ui/mode_spec.lua b/test/functional/ui/mode_spec.lua
index 200f6eecdb..9390f268b3 100644
--- a/test/functional/ui/mode_spec.lua
+++ b/test/functional/ui/mode_spec.lua
@@ -3,6 +3,7 @@ local Screen = require('test.functional.ui.screen')
local clear, feed, insert = helpers.clear, helpers.feed, helpers.insert
local command = helpers.command
+local retry = helpers.retry
describe('ui mode_change event', function()
local screen
@@ -61,30 +62,36 @@ describe('ui mode_change event', function()
|
]], mode="normal"}
+ local matchtime = 0
command("set showmatch")
- command("set matchtime=2") -- tenths of seconds
- feed('a(stuff')
- screen:expect{grid=[[
- word(stuff^ |
- {0:~ }|
- {0:~ }|
- {2:-- INSERT --} |
- ]], mode="insert"}
-
- feed(')')
- screen:expect{grid=[[
- word^(stuff) |
- {0:~ }|
- {0:~ }|
- {2:-- INSERT --} |
- ]], mode="showmatch"}
-
- screen:expect{grid=[[
- word(stuff)^ |
- {0:~ }|
- {0:~ }|
- {2:-- INSERT --} |
- ]], mode="insert"}
+ retry(nil, nil, function()
+ matchtime = matchtime + 1
+ local screen_timeout = 1000 * matchtime -- fail faster for retry.
+
+ command("set matchtime=" .. matchtime) -- tenths of seconds
+ feed('a(stuff')
+ screen:expect{grid=[[
+ word(stuff^ |
+ {0:~ }|
+ {0:~ }|
+ {2:-- INSERT --} |
+ ]], mode="insert", timeout=screen_timeout}
+
+ feed(')')
+ screen:expect{grid=[[
+ word^(stuff) |
+ {0:~ }|
+ {0:~ }|
+ {2:-- INSERT --} |
+ ]], mode="showmatch", timeout=screen_timeout}
+
+ screen:expect{grid=[[
+ word(stuff)^ |
+ {0:~ }|
+ {0:~ }|
+ {2:-- INSERT --} |
+ ]], mode="insert", timeout=screen_timeout}
+ end)
end)
it('works in replace mode', function()
diff --git a/test/functional/ui/mouse_spec.lua b/test/functional/ui/mouse_spec.lua
index 3bd6b81ff1..d857b57a31 100644
--- a/test/functional/ui/mouse_spec.lua
+++ b/test/functional/ui/mouse_spec.lua
@@ -12,7 +12,10 @@ describe('ui/mouse/input', function()
clear()
meths.set_option('mouse', 'a')
meths.set_option('list', true)
- meths.set_option('listchars', 'eol:$')
+ -- NB: this is weird, but mostly irrelevant to the test
+ -- So I didn't bother to change it
+ command('set listchars=eol:$')
+ command('setl listchars=nbsp:x')
screen = Screen.new(25, 5)
screen:attach()
screen:set_default_attr_ids({
@@ -26,6 +29,8 @@ describe('ui/mouse/input', function()
},
[4] = {reverse = true},
[5] = {bold = true, reverse = true},
+ [6] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red},
+ [7] = {bold = true, foreground = Screen.colors.SeaGreen4},
})
command("set display-=msgsep")
feed('itesting<cr>mouse<cr>support and selection<esc>')
@@ -38,10 +43,6 @@ describe('ui/mouse/input', function()
]])
end)
- after_each(function()
- screen:detach()
- end)
-
it('single left click moves cursor', function()
feed('<LeftMouse><2,1>')
screen:expect([[
@@ -419,9 +420,9 @@ describe('ui/mouse/input', function()
meths.set_option('showtabline', 2)
screen:expect([[
{fill:test-test2 }|
+ testing |
mouse |
support and selectio^n |
- {0:~ }|
|
]])
meths.set_var('reply', {})
@@ -539,9 +540,9 @@ describe('ui/mouse/input', function()
feed_command('tabprevious') -- go to first tab
screen:expect([[
{sel: + foo }{tab: + bar }{fill: }{tab:X}|
+ testing |
mouse |
support and selectio^n |
- {0:~ }|
:tabprevious |
]])
feed('<LeftMouse><10,0><LeftRelease>') -- go to second tab
@@ -620,12 +621,12 @@ describe('ui/mouse/input', function()
meths.set_option('tags', './non-existent-tags-file')
feed('<C-LeftMouse><0,0>')
screen:expect([[
- E433: No tags file |
- E426: tag not found: test|
- ing |
- Press ENTER or type comma|
- nd to continue^ |
- ]],nil,true)
+ {6:E433: No tags file} |
+ {6:E426: tag not found: test}|
+ {6:ing} |
+ {7:Press ENTER or type comma}|
+ {7:nd to continue}^ |
+ ]])
feed('<cr>')
end)
@@ -814,7 +815,8 @@ describe('ui/mouse/input', function()
feed_command('set concealcursor=ni')
feed_command('set nowrap')
- feed_command('set shiftwidth=2 tabstop=4 list listchars=tab:>-')
+ 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=>')
diff --git a/test/functional/ui/multibyte_spec.lua b/test/functional/ui/multibyte_spec.lua
index 3e63353ad2..e6a79feadc 100644
--- a/test/functional/ui/multibyte_spec.lua
+++ b/test/functional/ui/multibyte_spec.lua
@@ -21,10 +21,6 @@ describe("multibyte rendering", function()
})
end)
- after_each(function()
- screen:detach()
- end)
-
it("works with composed char at start of line", function()
insert([[
ÌŠ
@@ -127,20 +123,20 @@ describe('multibyte rendering: statusline', function()
before_each(function()
clear()
screen = Screen.new(40, 4)
+ screen:set_default_attr_ids({
+ [1] = {bold = true, foreground = Screen.colors.Blue1},
+ [2] = {bold = true, reverse = true},
+ })
screen:attach()
command('set laststatus=2')
end)
- after_each(function()
- screen:detach()
- end)
-
it('last char shows (multibyte)', function()
command('set statusline=你好')
screen:expect([[
^ |
- ~ |
- 你好 |
+ {1:~ }|
+ {2:你好 }|
|
]])
end)
@@ -148,8 +144,8 @@ describe('multibyte rendering: statusline', function()
command('set statusline=abc')
screen:expect([[
^ |
- ~ |
- abc |
+ {1:~ }|
+ {2:abc }|
|
]])
end)
@@ -157,8 +153,8 @@ describe('multibyte rendering: statusline', function()
command('set statusline=Ÿ')
screen:expect([[
^ |
- ~ |
- <9f> |
+ {1:~ }|
+ {2:<9f> }|
|
]])
end)
@@ -167,8 +163,8 @@ describe('multibyte rendering: statusline', function()
-- o + U+1DF0 + U+20EF + U+0338 + U+20D0 + U+20E7 + U+20DD
screen:expect([[
^ |
- ~ |
- o̸⃯ᷰâƒâƒ§âƒ |
+ {1:~ }|
+ {2:o̸⃯ᷰâƒâƒ§âƒ }|
|
]])
end)
@@ -177,9 +173,19 @@ describe('multibyte rendering: statusline', function()
-- U+9F + U+1DF0 + U+20EF + U+0338 + U+20D0 + U+20E7 + U+20DD
screen:expect([[
^ |
- ~ |
- <9f><1df0><20ef><0338><20d0><20e7><20dd>|
+ {1:~ }|
+ {2:<9f><1df0><20ef><0338><20d0><20e7><20dd>}|
|
]])
end)
+
+ it('hidden group %( %) does not cause invalid unicode', function()
+ command("let &statusline = '%#StatColorHi2#%(✓%#StatColorHi2#%) Q≡'")
+ screen:expect{grid=[[
+ ^ |
+ {1:~ }|
+ {2: Q≡ }|
+ |
+ ]]}
+ end)
end)
diff --git a/test/functional/ui/multigrid_spec.lua b/test/functional/ui/multigrid_spec.lua
index 30a5b63d89..e4d1187dea 100644
--- a/test/functional/ui/multigrid_spec.lua
+++ b/test/functional/ui/multigrid_spec.lua
@@ -37,10 +37,6 @@ describe('ext_multigrid', function()
})
end)
- after_each(function()
- screen:detach()
- end)
-
it('default initial screen', function()
screen:expect{grid=[[
## grid 1
@@ -1966,4 +1962,191 @@ describe('ext_multigrid', function()
{1:~ }|
]]}
end)
+
+ it('has viewport information', function()
+ screen:try_resize(48, 8)
+ screen:expect{grid=[[
+ ## grid 1
+ [2:------------------------------------------------]|
+ [2:------------------------------------------------]|
+ [2:------------------------------------------------]|
+ [2:------------------------------------------------]|
+ [2:------------------------------------------------]|
+ [2:------------------------------------------------]|
+ {11:[No Name] }|
+ [3:------------------------------------------------]|
+ ## grid 2
+ ^ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ## grid 3
+ |
+ ]], win_viewport={
+ [2] = {win = { id = 1000 }, topline = 0, botline = 2, curline = 0, curcol = 0}
+ }}
+ 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{grid=[[
+ ## grid 1
+ [2:------------------------------------------------]|
+ [2:------------------------------------------------]|
+ [2:------------------------------------------------]|
+ [2:------------------------------------------------]|
+ [2:------------------------------------------------]|
+ [2:------------------------------------------------]|
+ {11:[No Name] [+] }|
+ [3:------------------------------------------------]|
+ ## grid 2
+ 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
+ |
+ ]], win_viewport={
+ [2] = {win = {id = 1000}, topline = 5, botline = 11, curline = 10, curcol = 7},
+ }}
+
+
+ feed('<c-u>')
+ screen:expect{grid=[[
+ ## grid 1
+ [2:------------------------------------------------]|
+ [2:------------------------------------------------]|
+ [2:------------------------------------------------]|
+ [2:------------------------------------------------]|
+ [2:------------------------------------------------]|
+ [2:------------------------------------------------]|
+ {11:[No Name] [+] }|
+ [3:------------------------------------------------]|
+ ## grid 2
+ 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 |
+ ## grid 3
+ |
+ ]], win_viewport={
+ [2] = {win = {id = 1000}, topline = 2, botline = 9, curline = 7, curcol = 0},
+ }}
+
+ command("split")
+ screen:expect{grid=[[
+ ## grid 1
+ [4:------------------------------------------------]|
+ [4:------------------------------------------------]|
+ [4:------------------------------------------------]|
+ {11:[No Name] [+] }|
+ [2:------------------------------------------------]|
+ [2:------------------------------------------------]|
+ {12:[No Name] [+] }|
+ [3:------------------------------------------------]|
+ ## grid 2
+ reprehenderit in voluptate velit esse cillum |
+ dolore eu fugiat nulla pariatur. Excepteur sint |
+ ## grid 3
+ |
+ ## grid 4
+ ea commodo consequat. Duis aute irure dolor in |
+ 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},
+ [4] = {win = {id = 1001}, topline = 5, botline = 9, curline = 7, curcol = 0},
+ }}
+
+ feed("b")
+ screen:expect{grid=[[
+ ## grid 1
+ [4:------------------------------------------------]|
+ [4:------------------------------------------------]|
+ [4:------------------------------------------------]|
+ {11:[No Name] [+] }|
+ [2:------------------------------------------------]|
+ [2:------------------------------------------------]|
+ {12:[No Name] [+] }|
+ [3:------------------------------------------------]|
+ ## grid 2
+ reprehenderit in voluptate velit esse cillum |
+ dolore eu fugiat nulla pariatur. Excepteur sint |
+ ## grid 3
+ |
+ ## grid 4
+ ea commodo consequat. Duis aute irure dolor in |
+ 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},
+ [4] = {win = {id = 1001}, topline = 5, botline = 9, curline = 6, curcol = 38},
+ }}
+
+ feed("2k")
+ screen:expect{grid=[[
+ ## grid 1
+ [4:------------------------------------------------]|
+ [4:------------------------------------------------]|
+ [4:------------------------------------------------]|
+ {11:[No Name] [+] }|
+ [2:------------------------------------------------]|
+ [2:------------------------------------------------]|
+ {12:[No Name] [+] }|
+ [3:------------------------------------------------]|
+ ## grid 2
+ reprehenderit in voluptate velit esse cillum |
+ dolore eu fugiat nulla pariatur. Excepteur sint |
+ ## grid 3
+ |
+ ## grid 4
+ exercitation ullamco laboris nisi ut a^liquip ex |
+ 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},
+ [4] = {win = {id = 1001}, topline = 4, botline = 8, curline = 4, curcol = 38},
+ }}
+
+ -- handles non-current window
+ meths.win_set_cursor(1000, {1, 10})
+ 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
+ exercitation ullamco laboris nisi ut a^liquip ex |
+ 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},
+ [4] = {win = {id = 1001}, topline = 4, botline = 8, curline = 4, curcol = 38},
+ }}
+ end)
end)
diff --git a/test/functional/ui/options_spec.lua b/test/functional/ui/options_spec.lua
index 93192934c7..9646c3fdad 100644
--- a/test/functional/ui/options_spec.lua
+++ b/test/functional/ui/options_spec.lua
@@ -5,7 +5,7 @@ local command = helpers.command
local eq = helpers.eq
local shallowcopy = helpers.shallowcopy
-describe('ui receives option updates', function()
+describe('UI receives option updates', function()
local screen
local function reset(opts, ...)
@@ -20,6 +20,8 @@ describe('ui receives option updates', function()
pumblend=0,
showtabline=1,
termguicolors=false,
+ ttimeout=true,
+ ttimeoutlen=50,
ext_cmdline=false,
ext_popupmenu=false,
ext_tabline=false,
@@ -40,10 +42,6 @@ describe('ui receives option updates', function()
return defaults
end
- after_each(function()
- screen:detach()
- end)
-
it("for defaults", function()
local expected = reset()
screen:expect(function()
@@ -51,6 +49,33 @@ describe('ui receives option updates', function()
end)
end)
+ it('on attach #11372', function()
+ clear()
+ local evs = {}
+ screen = Screen.new(20,5)
+ -- Override mouse_on/mouse_off handlers.
+ function screen:_handle_mouse_on()
+ table.insert(evs, 'mouse_on')
+ end
+ function screen:_handle_mouse_off()
+ table.insert(evs, 'mouse_off')
+ end
+ screen:attach()
+ screen:expect(function()
+ eq({'mouse_off'}, evs)
+ end)
+ command("set mouse=nvi")
+ screen:expect(function()
+ eq({'mouse_off','mouse_on'}, evs)
+ end)
+ screen:detach()
+ eq({'mouse_off','mouse_on'}, evs)
+ screen:attach()
+ screen:expect(function()
+ eq({'mouse_off','mouse_on','mouse_on'}, evs)
+ end)
+ end)
+
it("when setting options", function()
local expected = reset()
local defaults = shallowcopy(expected)
@@ -85,6 +110,18 @@ describe('ui receives option updates', function()
eq(expected, screen.options)
end)
+ command("set nottimeout")
+ expected.ttimeout = false
+ screen:expect(function()
+ eq(expected, screen.options)
+ end)
+
+ command("set ttimeoutlen=100")
+ expected.ttimeoutlen = 100
+ screen:expect(function()
+ eq(expected, screen.options)
+ end)
+
command("set all&")
screen:expect(function()
eq(defaults, screen.options)
diff --git a/test/functional/ui/output_spec.lua b/test/functional/ui/output_spec.lua
index c028f44b44..d7dde6345f 100644
--- a/test/functional/ui/output_spec.lua
+++ b/test/functional/ui/output_spec.lua
@@ -10,6 +10,8 @@ local iswin = helpers.iswin
local clear = helpers.clear
local command = helpers.command
local nvim_dir = helpers.nvim_dir
+local has_powershell = helpers.has_powershell
+local set_shell_powershell = helpers.set_shell_powershell
describe("shell command :!", function()
local screen
@@ -30,7 +32,6 @@ describe("shell command :!", function()
after_each(function()
child_session.feed_data("\3") -- Ctrl-C
- screen:detach()
end)
it("displays output without LF/EOF. #4646 #4569 #3772", function()
@@ -51,8 +52,7 @@ describe("shell command :!", function()
it("throttles shell-command output greater than ~10KB", function()
if 'openbsd' == helpers.uname() then
- pending('FIXME #10804', function() end)
- return
+ pending('FIXME #10804')
end
child_session.feed_data(":!"..nvim_dir.."/shell-test REP 30001 foo\n")
@@ -96,8 +96,7 @@ describe("shell command :!", function()
it('handles control codes', function()
if iswin() then
- pending('missing printf', function() end)
- return
+ pending('missing printf')
end
local screen = Screen.new(50, 4)
screen:attach()
@@ -230,4 +229,23 @@ describe("shell command :!", function()
]])
end)
end)
+ if has_powershell() then
+ it('powershell supports literal strings', function()
+ set_shell_powershell()
+ local screen = Screen.new(30, 4)
+ screen:attach()
+ feed_command([[!'Write-Output $a']])
+ screen:expect{any='\nWrite%-Output %$a', timeout=10000}
+ feed_command([[!$a = 1; Write-Output '$a']])
+ screen:expect{any='\n%$a', timeout=10000}
+ feed_command([[!"Write-Output $a"]])
+ screen:expect{any='\nWrite%-Output', timeout=10000}
+ feed_command([[!$a = 1; Write-Output "$a"]])
+ screen:expect{any='\n1', timeout=10000}
+ feed_command(iswin()
+ and [[!& 'C:\\Windows\\system32\\cmd.exe' /c 'echo $a']]
+ or [[!& '/bin/sh' -c 'echo ''$a''']])
+ screen:expect{any='\n%$a', timeout=10000}
+ end)
+ end
end)
diff --git a/test/functional/ui/popupmenu_spec.lua b/test/functional/ui/popupmenu_spec.lua
index ae2136f451..c1c5d1ce2e 100644
--- a/test/functional/ui/popupmenu_spec.lua
+++ b/test/functional/ui/popupmenu_spec.lua
@@ -8,7 +8,7 @@ local command = helpers.command
local funcs = helpers.funcs
local get_pathsep = helpers.get_pathsep
local eq = helpers.eq
-local matches = helpers.matches
+local pcall_err = helpers.pcall_err
describe('ui/ext_popupmenu', function()
local screen
@@ -382,7 +382,7 @@ describe('ui/ext_popupmenu', function()
end
describe('pum_set_height', function()
- it('can be set pum height', function()
+ it('can set pum height', function()
source_complete_month()
local month_expected = {
{'January', '', '', ''},
@@ -421,22 +421,79 @@ describe('ui/ext_popupmenu', function()
end)
it('an error occurs if set 0 or less', function()
- local ok, err, _
- ok, _ = pcall(meths.ui_pum_set_height, 1)
- eq(ok, true)
- ok, err = pcall(meths.ui_pum_set_height, 0)
- eq(ok, false)
- matches('.*: Expected pum height > 0', err)
+ meths.ui_pum_set_height(1)
+ eq('Expected pum height > 0',
+ pcall_err(meths.ui_pum_set_height, 0))
end)
it('an error occurs when ext_popupmenu is false', function()
- local ok, err, _
- ok, _ = pcall(meths.ui_pum_set_height, 1)
- eq(ok, true)
+ meths.ui_pum_set_height(1)
screen:set_option('ext_popupmenu', false)
- ok, err = pcall(meths.ui_pum_set_height, 1)
- eq(ok, false)
- matches('.*: It must support the ext_popupmenu option', err)
+ eq('It must support the ext_popupmenu option',
+ pcall_err(meths.ui_pum_set_height, 1))
+ end)
+ end)
+
+ describe('pum_set_bounds', function()
+ it('can set pum bounds', function()
+ source_complete_month()
+ local month_expected = {
+ {'January', '', '', ''},
+ {'February', '', '', ''},
+ {'March', '', '', ''},
+ {'April', '', '', ''},
+ {'May', '', '', ''},
+ {'June', '', '', ''},
+ {'July', '', '', ''},
+ {'August', '', '', ''},
+ {'September', '', '', ''},
+ {'October', '', '', ''},
+ {'November', '', '', ''},
+ {'December', '', '', ''},
+ }
+ local pum_height = 6
+ feed('o<C-r>=TestCompleteMonth()<CR>')
+ meths.ui_pum_set_height(pum_height)
+ -- set bounds w h r c
+ meths.ui_pum_set_bounds(10.5, 5.2, 6.3, 7.4)
+ feed('<PageDown>')
+ -- pos becomes pum_height-2 because it is subtracting 2 to keep some
+ -- context in ins_compl_key2count()
+ screen:expect{grid=[[
+ |
+ January^ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {2:-- INSERT --} |
+ ]], popupmenu={
+ items=month_expected,
+ pos=pum_height-2,
+ anchor={1,1,0},
+ }}
+ end)
+
+ it('no error occurs if row or col set less than 0', function()
+ meths.ui_pum_set_bounds(1.0, 1.0, 0.0, 1.5)
+ meths.ui_pum_set_bounds(1.0, 1.0, -1.0, 0.0)
+ meths.ui_pum_set_bounds(1.0, 1.0, 0.0, -1.0)
+ end)
+
+ it('an error occurs if width or height set 0 or less', function()
+ meths.ui_pum_set_bounds(1.0, 1.0, 0.0, 1.5)
+ eq('Expected width > 0',
+ pcall_err(meths.ui_pum_set_bounds, 0.0, 1.0, 1.0, 0.0))
+ eq('Expected height > 0',
+ pcall_err(meths.ui_pum_set_bounds, 1.0, -1.0, 1.0, 0.0))
+ end)
+
+ it('an error occurs when ext_popupmenu is false', function()
+ meths.ui_pum_set_bounds(1.0, 1.0, 0.0, 1.5)
+ screen:set_option('ext_popupmenu', false)
+ eq('UI must support the ext_popupmenu option',
+ pcall_err(meths.ui_pum_set_bounds, 1.0, 1.0, 0.0, 1.5))
end)
end)
@@ -516,6 +573,7 @@ describe('ui/ext_popupmenu', function()
{1:~ }|
:sign ^ |
]])
+ eq(0, funcs.wildmenumode())
feed('<tab>')
screen:expect{grid=[[
@@ -530,6 +588,7 @@ describe('ui/ext_popupmenu', function()
{1:~ }|
:sign define^ |
]], popupmenu={items=wild_expected, pos=0, anchor={1, 9, 6}}}
+ eq(1, funcs.wildmenumode())
feed('<left>')
screen:expect{grid=[[
@@ -589,6 +648,7 @@ describe('ui/ext_popupmenu', function()
:sign unplace^ |
]], popupmenu={items=wild_expected, pos=5, anchor={1, 9, 6}}}
feed('<esc>')
+ eq(0, funcs.wildmenumode())
-- check positioning with multibyte char in pattern
command("e långfile1")
@@ -637,7 +697,7 @@ describe('builtin popupmenu', function()
})
end)
- it('works with preview-window above', function()
+ 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>')
@@ -665,7 +725,7 @@ describe('builtin popupmenu', function()
]])
end)
- it('works with preview-window below', function()
+ 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>')
@@ -693,7 +753,7 @@ describe('builtin popupmenu', function()
]])
end)
- it('works with preview-window above and tall and inverted', function()
+ 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>')
@@ -723,7 +783,7 @@ describe('builtin popupmenu', function()
]])
end)
- it('works with preview-window above and short and inverted', function()
+ 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>')
@@ -736,23 +796,23 @@ describe('builtin popupmenu', function()
ee |
ff |
gg |
- {s:aa } |
- {n:bb }{3:iew][+] }|
- {n:cc } |
- {n:dd } |
- {n:ee } |
- {n:ff } |
- {n:gg } |
- {n:hh } |
- {n:ii } |
- {n:jj } |
+ 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('works with preview-window below and inverted', function()
+ 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>')
@@ -781,7 +841,7 @@ describe('builtin popupmenu', function()
]])
end)
- it('works with vsplits', function()
+ it('with vsplits', function()
insert('aaa aab aac\n')
feed(':vsplit<cr>')
screen:expect([[
@@ -856,7 +916,7 @@ describe('builtin popupmenu', function()
]])
end)
- it('works with split and scroll', function()
+ it('with split and scroll', function()
screen:try_resize(60,14)
command("split")
command("set completeopt+=noinsert")
@@ -1153,10 +1213,10 @@ describe('builtin popupmenu', function()
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 }|
+ {n:word }{1: }|
+ {n:choice }{1: }|
+ {n:text }{1: }|
+ {n:thing }{1: }|
{1:~ }|
{1:~ }|
{1:~ }|
@@ -1201,10 +1261,10 @@ describe('builtin popupmenu', function()
feed('<c-p>')
screen:expect([[
some long prefix before the text|
- {1:^~ }{n: word }|
- {1:~ }{n: choice}|
- {1:~ }{s: text }|
- {1:~ }{n: thing }|
+ {n:^word }{1: }|
+ {n:choice }{1: }|
+ {s:text }{1: }|
+ {n:thing }{1: }|
{1:~ }|
{1:~ }|
{1:~ }|
@@ -1290,7 +1350,7 @@ describe('builtin popupmenu', function()
]])
end)
- it('behaves correcty with VimResized autocmd', function()
+ it('with VimResized autocmd', function()
feed('isome long prefix before the ')
command("set completeopt+=noinsert,noselect")
command("autocmd VimResized * redraw!")
@@ -1298,10 +1358,10 @@ describe('builtin popupmenu', function()
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 }|
+ {n:word }{1: }|
+ {n:choice }{1: }|
+ {n:text }{1: }|
+ {n:thing }{1: }|
{1:~ }|
{1:~ }|
{1:~ }|
@@ -1334,8 +1394,8 @@ describe('builtin popupmenu', function()
]])
end)
- it('works with rightleft window', function()
- command("set rl")
+ it('with rightleft window', function()
+ command("set rl wildoptions+=pum")
feed('isome rightleft ')
screen:expect([[
^ tfelthgir emos|
@@ -1432,9 +1492,58 @@ describe('builtin popupmenu', function()
{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('works with multiline messages', function()
+ it('with multiline messages', function()
screen:try_resize(40,8)
feed('ixx<cr>')
command('imap <f2> <cmd>echoerr "very"\\|echoerr "much"\\|echoerr "error"<cr>')
@@ -1488,20 +1597,20 @@ describe('builtin popupmenu', function()
command("split")
screen:expect([[
+ xx |
choice^ |
- {1:~ }|
{n:word }{1: }|
{s:choice }{4: }|
{n:text } |
- {n:thing }{1: }|
+ {n:thing } |
{3:[No Name] [+] }|
{2:-- INSERT --} |
]])
meths.input_mouse('wheel', 'down', '', 0, 6, 15)
screen:expect{grid=[[
+ xx |
choice^ |
- {1:~ }|
{n:word }{1: }|
{s:choice }{4: }|
{n:text } |
@@ -1511,7 +1620,7 @@ describe('builtin popupmenu', function()
]], unchanged=true}
end)
- it('works with kind, menu and abbr attributes', function()
+ 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'}})
@@ -1563,7 +1672,7 @@ describe('builtin popupmenu', function()
]])
end)
- it('works with wildoptions=pum', function()
+ it('wildoptions=pum', function()
screen:try_resize(32,10)
command('set wildmenu')
command('set wildoptions=pum')
@@ -1735,7 +1844,7 @@ describe('builtin popupmenu', function()
]])
end)
- it('works with wildoptions=pum with scrolled mesages ', function()
+ it('wildoptions=pum with scrolled mesages ', function()
screen:try_resize(40,10)
command('set wildmenu')
command('set wildoptions=pum')
@@ -1783,6 +1892,39 @@ describe('builtin popupmenu', function()
]]}
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')
+
+ feed(':sign u<tab>')
+ screen:expect{grid=[[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ :sign un^ |
+ ]]}
+ eq(0, funcs.wildmenumode())
+
+ feed('<tab>')
+ screen:expect{grid=[[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }{s: undefine }{1: }|
+ {1:~ }{n: unplace }{1: }|
+ :sign undefine^ |
+ ]]}
+ eq(1, funcs.wildmenumode())
+ end)
+
it("'pumblend' RGB-color", function()
screen:try_resize(60,14)
screen:set_default_attr_ids({
@@ -2016,4 +2158,42 @@ describe('builtin popupmenu', function()
{9:-- Keyword Local completion (^N^P) }{10:match 1 of 3} |
]])
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 ^ |
+ {n:word }{c: }{1: }|
+ {n:choice }{s: }{1: }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {2:-- INSERT --} |
+ ]])
+ 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 ^ |
+ {n:word }{1: }|
+ {n:choice }{1: }|
+ {n:text }{1: }|
+ {n:thing }{1: }|
+ {1:~ }|
+ {1:~ }|
+ {2:-- INSERT --} |
+ ]])
+ end)
end)
diff --git a/test/functional/ui/screen.lua b/test/functional/ui/screen.lua
index 06a2ac3ca2..bf979e89f4 100644
--- a/test/functional/ui/screen.lua
+++ b/test/functional/ui/screen.lua
@@ -66,12 +66,12 @@
-- [1] = {reverse = true, bold = true},
-- [2] = {reverse = true}
-- })
--- screen:set_default_attr_ignore( {{}, {bold=true, foreground=NonText}} )
--
-- To help write screen tests, see Screen:snapshot_util().
-- To debug screen tests, see Screen:redraw_debug().
local helpers = require('test.functional.helpers')(nil)
+local busted = require('busted')
local deepcopy = helpers.deepcopy
local shallowcopy = helpers.shallowcopy
local concat_tables = helpers.concat_tables
@@ -158,6 +158,7 @@ function Screen.new(width, height)
wildmenu_items = nil,
wildmenu_selected = nil,
win_position = {},
+ win_viewport = {},
float_pos = {},
msg_grid = nil,
msg_grid_pos = nil,
@@ -169,12 +170,11 @@ function Screen.new(width, height)
ruler = {},
hl_groups = {},
_default_attr_ids = nil,
- _default_attr_ignore = nil,
_mouse_enabled = true,
_attrs = {},
- _hl_info = {},
+ _hl_info = {[0]={}},
_attr_table = {[0]={{},{}}},
- _clear_attrs = {},
+ _clear_attrs = nil,
_new_attrs = false,
_width = width,
_height = height,
@@ -202,12 +202,8 @@ function Screen:get_default_attr_ids()
return deepcopy(self._default_attr_ids)
end
-function Screen:set_default_attr_ignore(attr_ignore)
- self._default_attr_ignore = attr_ignore
-end
-
-function Screen:set_hlstate_cterm(val)
- self._hlstate_cterm = val
+function Screen:set_rgb_cterm(val)
+ self._rgb_cterm = val
end
function Screen:attach(options, session)
@@ -223,7 +219,7 @@ function Screen:attach(options, session)
self._session = session
self._options = options
- self._clear_attrs = (options.ext_linegrid and {{},{}}) or {}
+ self._clear_attrs = (not options.ext_linegrid) and {} or nil
self:_handle_resize(self._width, self._height)
self.uimeths.attach(self._width, self._height, options)
if self._options.rgb == nil then
@@ -259,13 +255,13 @@ end
-- canonical order of ext keys, used to generate asserts
local ext_keys = {
'popupmenu', 'cmdline', 'cmdline_block', 'wildmenu_items', 'wildmenu_pos',
- 'messages', 'showmode', 'showcmd', 'ruler', 'float_pos',
+ 'messages', 'showmode', 'showcmd', 'ruler', 'float_pos', 'win_viewport'
}
-- Asserts that the screen state eventually matches an expected state.
--
-- Can be called with positional args:
--- screen:expect(grid, [attr_ids, attr_ignore])
+-- screen:expect(grid, [attr_ids])
-- screen:expect(condition)
-- or keyword args (supports more options):
-- screen:expect{grid=[[...]], cmdline={...}, condition=function() ... end}
@@ -274,7 +270,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.
--- Lines containing only "{IGNORE}|" are skipped.
+-- "{MATCH:x}|" lines are 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
@@ -282,8 +278,6 @@ local ext_keys = {
-- attributes in the final state are an error.
-- Use screen:set_default_attr_ids() to define attributes for many
-- expect() calls.
--- attr_ignore: Ignored text attributes, or `true` to ignore all. By default
--- nothing is ignored.
-- condition: Function asserting some arbitrary condition. Return value is
-- ignored, throw an error (use eq() or similar) to signal failure.
-- any: Lua pattern string expected to match a screen line. NB: the
@@ -318,13 +312,13 @@ local ext_keys = {
-- cmdline_block: Expected ext_cmdline block (for function definitions)
-- wildmenu_items: Expected items for ext_wildmenu
-- wildmenu_pos: Expected position for ext_wildmenu
-function Screen:expect(expected, attr_ids, attr_ignore, ...)
+function Screen:expect(expected, attr_ids, ...)
local grid, condition = nil, nil
local expected_rows = {}
assert(next({...}) == nil, "invalid args to expect()")
if type(expected) == "table" then
- assert(not (attr_ids ~= nil or attr_ignore ~= nil))
- local is_key = {grid=true, attr_ids=true, attr_ignore=true, condition=true,
+ assert(not (attr_ids ~= nil))
+ local is_key = {grid=true, attr_ids=true, condition=true,
any=true, mode=true, unchanged=true, intermediate=true,
reset=true, timeout=true, request_cb=true, hl_groups=true}
for _, v in ipairs(ext_keys) do
@@ -337,14 +331,13 @@ function Screen:expect(expected, attr_ids, attr_ignore, ...)
end
grid = expected.grid
attr_ids = expected.attr_ids
- attr_ignore = expected.attr_ignore
condition = expected.condition
assert(not (expected.any ~= nil and grid ~= nil))
elseif type(expected) == "string" then
grid = expected
expected = {}
elseif type(expected) == "function" then
- assert(not (attr_ids ~= nil or attr_ignore ~= nil))
+ assert(not (attr_ids ~= nil))
condition = expected
expected = {}
else
@@ -361,10 +354,9 @@ function Screen:expect(expected, attr_ids, attr_ignore, ...)
end
local attr_state = {
ids = attr_ids or self._default_attr_ids,
- ignore = attr_ignore or self._default_attr_ignore,
}
- if self._options.ext_hlstate then
- attr_state.id_to_index = self:hlstate_check_attrs(attr_state.ids or {})
+ if self._options.ext_linegrid then
+ attr_state.id_to_index = self:linegrid_check_attrs(attr_state.ids or {})
end
self._new_attrs = false
self:_wait(function()
@@ -375,8 +367,8 @@ function Screen:expect(expected, attr_ids, attr_ignore, ...)
end
end
- if self._options.ext_hlstate and self._new_attrs then
- attr_state.id_to_index = self:hlstate_check_attrs(attr_state.ids or {})
+ if self._options.ext_linegrid and self._new_attrs then
+ attr_state.id_to_index = self:linegrid_check_attrs(attr_state.ids or {})
end
local actual_rows = self:render(not expected.any, attr_state)
@@ -399,9 +391,10 @@ function Screen:expect(expected, attr_ids, attr_ignore, ...)
err_msg = "Expected screen height " .. #expected_rows
.. ' differs from actual height ' .. #actual_rows .. '.'
end
- for i = 1, #expected_rows do
- msg_expected_rows[i] = expected_rows[i]
- if expected_rows[i] ~= actual_rows[i] and expected_rows[i] ~= "{IGNORE}|" then
+ 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]:match(m)) then
msg_expected_rows[i] = '*' .. msg_expected_rows[i]
if i <= #actual_rows then
actual_rows[i] = '*' .. actual_rows[i]
@@ -429,6 +422,9 @@ screen:redraw_debug() to show all intermediate screen states. ]])
if expected.mode ~= nil then
extstate.mode = self.mode
end
+ if expected.win_viewport == nil then
+ extstate.win_viewport = nil
+ end
-- Convert assertion errors into invalid screen state descriptions.
for _, k in ipairs(concat_tables(ext_keys, {'mode'})) do
@@ -584,7 +580,7 @@ asynchronous (feed(), nvim_input()) and synchronous API calls.
if err then
- assert(false, err)
+ busted.fail(err, 3)
elseif did_warn then
local tb = debug.traceback()
local index = string.find(tb, '\n%s*%[C]')
@@ -614,17 +610,12 @@ function Screen:_redraw(updates)
for i = 2, #update do
local handler_name = '_handle_'..method
local handler = self[handler_name]
- if handler ~= nil then
- local status, res = pcall(handler, self, unpack(update[i]))
- if not status then
- error(handler_name..' failed'
- ..'\n payload: '..inspect(update)
- ..'\n error: '..tostring(res))
- end
- else
- assert(self._on_event,
- "Add Screen:"..handler_name.." or call Screen:set_on_event_handler")
- self._on_event(method, update[i])
+ assert(handler ~= nil, "missing handler: Screen:"..handler_name)
+ local status, res = pcall(handler, self, unpack(update[i]))
+ if not status then
+ error(handler_name..' failed'
+ ..'\n payload: '..inspect(update)
+ ..'\n error: '..tostring(res))
end
end
if k == #updates and method == "flush" then
@@ -634,10 +625,6 @@ function Screen:_redraw(updates)
return did_flush
end
-function Screen:set_on_event_handler(callback)
- self._on_event = callback
-end
-
function Screen:_handle_resize(width, height)
self:_handle_grid_resize(1, width, height)
self._scroll_region = {
@@ -743,6 +730,7 @@ function Screen:_handle_grid_destroy(grid)
self._grids[grid] = nil
if self._options.ext_multigrid then
self.win_position[grid] = nil
+ self.win_viewport[grid] = nil
end
end
@@ -763,14 +751,24 @@ function Screen:_handle_grid_cursor_goto(grid, row, col)
end
function Screen:_handle_win_pos(grid, win, startrow, startcol, width, height)
- self.win_position[grid] = {
- win = win,
- startrow = startrow,
- startcol = startcol,
- width = width,
- height = height
- }
- self.float_pos[grid] = nil
+ self.win_position[grid] = {
+ win = win,
+ startrow = startrow,
+ startcol = startcol,
+ width = width,
+ height = height
+ }
+ self.float_pos[grid] = nil
+end
+
+function Screen:_handle_win_viewport(grid, win, topline, botline, curline, curcol)
+ self.win_viewport[grid] = {
+ win = win,
+ topline = topline,
+ botline = botline,
+ curline = curline,
+ curcol = curcol
+ }
end
function Screen:_handle_win_float_pos(grid, ...)
@@ -898,19 +896,16 @@ function Screen:_handle_grid_line(grid, row, col, items)
assert(self._options.ext_linegrid)
local line = self._grids[grid].rows[row+1]
local colpos = col+1
- local hl = self._clear_attrs
local hl_id = 0
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
- hl = self._attr_table[hl_id]
end
for _ = 1, (count or 1) do
local cell = line[colpos]
cell.text = text
cell.hl_id = hl_id
- cell.attrs = hl
colpos = colpos+1
end
end
@@ -1070,6 +1065,7 @@ function Screen:_clear_row_section(grid, rownum, startcol, stopcol, invalid)
for i = startcol, stopcol do
row[i].text = (invalid and '�' or ' ')
row[i].attrs = self._clear_attrs
+ row[i].hl_id = 0
end
end
@@ -1100,11 +1096,7 @@ function Screen:_row_repr(gridnr, rownr, attr_state, cursor)
end
if not did_window then
- local attrs = row[i].attrs
- if self._options.ext_linegrid then
- attrs = attrs[(self._options.rgb and 1) or 2]
- end
- local attr_id = self:_get_attr_id(attr_state, attrs, row[i].hl_id)
+ local attr_id = self:_get_attr_id(attr_state, row[i].attrs, row[i].hl_id)
if current_attr_id and attr_id ~= current_attr_id then
-- close current attribute bracket
table.insert(rv, '}')
@@ -1153,6 +1145,8 @@ function Screen:_extstate_repr(attr_state)
messages[i] = {kind=entry[1], content=self:_chunks_repr(entry[2], attr_state)}
end
+ local win_viewport = (next(self.win_viewport) and self.win_viewport) or nil
+
return {
popupmenu=self.popupmenu,
cmdline=cmdline,
@@ -1164,7 +1158,8 @@ function Screen:_extstate_repr(attr_state)
showcmd=self:_chunks_repr(self.showcmd, attr_state),
ruler=self:_chunks_repr(self.ruler, attr_state),
msg_history=msg_history,
- float_pos=self.float_pos
+ float_pos=self.float_pos,
+ win_viewport=win_viewport,
}
end
@@ -1239,10 +1234,6 @@ function Screen:render(headers, attr_state, preview)
return rv
end
-local remove_all_metatables = function(item, path)
- if path[#path] ~= inspect.METATABLE then return item end
-end
-
-- Returns the current screen state in the form of a screen:expect()
-- keyword-args map.
function Screen:get_snapshot(attrs, ignore)
@@ -1261,8 +1252,8 @@ function Screen:get_snapshot(attrs, ignore)
attr_state.ids[i] = a
end
end
- if self._options.ext_hlstate then
- attr_state.id_to_index = self:hlstate_check_attrs(attr_state.ids)
+ if self._options.ext_linegrid then
+ attr_state.id_to_index = self:linegrid_check_attrs(attr_state.ids)
end
local lines = self:render(true, attr_state, true)
@@ -1292,6 +1283,26 @@ function Screen:get_snapshot(attrs, ignore)
return kwargs, ext_state, attr_state
end
+local function fmt_ext_state(name, state)
+ if name == "win_viewport" then
+ local str = "{\n"
+ 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.."},\n")
+ end
+ return str .. "}"
+ else
+ -- TODO(bfredl): improve formatting of more states
+ local function remove_all_metatables(item, path)
+ if path[#path] ~= inspect.METATABLE then
+ return item
+ end
+ end
+ return inspect(state,{process=remove_all_metatables})
+ end
+end
+
function Screen:print_snapshot(attrs, ignore)
local kwargs, ext_state, attr_state = self:get_snapshot(attrs, ignore)
local attrstr = ""
@@ -1299,8 +1310,8 @@ function Screen:print_snapshot(attrs, ignore)
local attrstrs = {}
for i, a in pairs(attr_state.ids) do
local dict
- if self._options.ext_hlstate then
- dict = self:_pprint_hlstate(a)
+ if self._options.ext_linegrid then
+ dict = self:_pprint_hlitem(a)
else
dict = "{"..self:_pprint_attrs(a).."}"
end
@@ -1314,9 +1325,8 @@ function Screen:print_snapshot(attrs, ignore)
print(kwargs.grid)
io.stdout:write( "]]"..attrstr)
for _, k in ipairs(ext_keys) do
- if ext_state[k] ~= nil then
- -- TODO(bfredl): improve formatting
- io.stdout:write(", "..k.."="..inspect(ext_state[k],{process=remove_all_metatables}))
+ if ext_state[k] ~= nil and not (k == "win_viewport" and not self.options.ext_multigrid) then
+ io.stdout:write(", "..k.."="..fmt_ext_state(k, ext_state[k]))
end
end
print("}\n")
@@ -1328,37 +1338,41 @@ function Screen:_insert_hl_id(attr_state, hl_id)
return attr_state.id_to_index[hl_id]
end
local raw_info = self._hl_info[hl_id]
- local info = {}
- if #raw_info > 1 then
- for i, item in ipairs(raw_info) do
- info[i] = self:_insert_hl_id(attr_state, item.id)
- end
- else
- info[1] = {}
- for k, v in pairs(raw_info[1]) do
- if k ~= "id" then
- info[1][k] = v
+ local info = nil
+ if self._options.ext_hlstate then
+ info = {}
+ if #raw_info > 1 then
+ for i, item in ipairs(raw_info) do
+ info[i] = self:_insert_hl_id(attr_state, item.id)
+ end
+ else
+ info[1] = {}
+ for k, v in pairs(raw_info[1]) do
+ if k ~= "id" then
+ info[1][k] = v
+ end
end
end
end
local entry = self._attr_table[hl_id]
local attrval
- if self._hlstate_cterm then
+ if self._rgb_cterm then
attrval = {entry[1], entry[2], info} -- unpack() doesn't work
- else
+ elseif self._options.ext_hlstate then
attrval = {entry[1], info}
+ else
+ attrval = self._options.rgb and entry[1] or entry[2]
end
-
table.insert(attr_state.ids, attrval)
attr_state.id_to_index[hl_id] = #attr_state.ids
return #attr_state.ids
end
-function Screen:hlstate_check_attrs(attrs)
+function Screen:linegrid_check_attrs(attrs)
local id_to_index = {}
- for i = 1,#self._attr_table do
+ for i, def_attr in pairs(self._attr_table) do
local iinfo = self._hl_info[i]
local matchinfo = {}
if #iinfo > 1 then
@@ -1370,13 +1384,17 @@ function Screen:hlstate_check_attrs(attrs)
end
for k,v in pairs(attrs) do
local attr, info, attr_rgb, attr_cterm
- if self._hlstate_cterm then
+ if self._rgb_cterm then
attr_rgb, attr_cterm, info = unpack(v)
attr = {attr_rgb, attr_cterm}
- else
+ info = info or {}
+ elseif self._options.ext_hlstate then
attr, info = unpack(v)
+ else
+ attr = v
+ info = {}
end
- if self:_equal_attr_def(attr, self._attr_table[i]) then
+ if self:_equal_attr_def(attr, def_attr) then
if #info == #matchinfo then
local match = false
if #info == 1 then
@@ -1397,24 +1415,32 @@ function Screen:hlstate_check_attrs(attrs)
end
end
end
+ if self:_equal_attr_def(self._rgb_cterm and {{}, {}} or {}, def_attr) and #self._hl_info[i] == 0 then
+ id_to_index[i] = ""
+ end
end
return id_to_index
end
-function Screen:_pprint_hlstate(item)
+function Screen:_pprint_hlitem(item)
-- print(inspect(item))
- local attrdict = "{"..self:_pprint_attrs(item[1]).."}, "
+ local multi = self._rgb_cterm or self._options.ext_hlstate
+ local cterm = (not self._rgb_cterm and not self._options.rgb)
+ local attrdict = "{"..self:_pprint_attrs(multi and item[1] or item, cterm).."}"
local attrdict2, hlinfo
- if self._hlstate_cterm then
- attrdict2 = "{"..self:_pprint_attrs(item[2]).."}, "
+ local descdict = ""
+ if self._rgb_cterm then
+ attrdict2 = ", {"..self:_pprint_attrs(item[2], true).."}"
hlinfo = item[3]
else
attrdict2 = ""
hlinfo = item[2]
end
- local descdict = "{"..self:_pprint_hlinfo(hlinfo).."}"
- return "{"..attrdict..attrdict2..descdict.."}"
+ if self._options.ext_hlstate then
+ descdict = ", {"..self:_pprint_hlinfo(hlinfo).."}"
+ end
+ return (multi and "{" or "")..attrdict..attrdict2..descdict..(multi and "}" or "")
end
function Screen:_pprint_hlinfo(states)
@@ -1434,13 +1460,15 @@ function Screen:_pprint_hlinfo(states)
end
-function Screen:_pprint_attrs(attrs)
+function Screen:_pprint_attrs(attrs, cterm)
local items = {}
for f, v in pairs(attrs) do
local desc = tostring(v)
if f == "foreground" or f == "background" or f == "special" then
if Screen.colornames[v] ~= nil then
desc = "Screen.colors."..Screen.colornames[v]
+ elseif cterm then
+ desc = tostring(v)
else
desc = string.format("tonumber('0x%06x')",v)
end
@@ -1464,9 +1492,11 @@ function Screen:_get_attr_id(attr_state, attrs, hl_id)
return
end
- if self._options.ext_hlstate then
+ if self._options.ext_linegrid then
local id = attr_state.id_to_index[hl_id]
- if id ~= nil or hl_id == 0 then
+ if id == "" then -- sentinel for empty it
+ return nil
+ elseif id ~= nil then
return id
end
if attr_state.mutable then
@@ -1476,9 +1506,7 @@ function Screen:_get_attr_id(attr_state, attrs, hl_id)
end
return "UNEXPECTED "..self:_pprint_attrs(self._attr_table[hl_id][1])
else
- if self:_equal_attrs(attrs, {}) or
- attr_state.ignore == true or
- self:_attr_index(attr_state.ignore, attrs) ~= nil then
+ if self:_equal_attrs(attrs, {}) then
-- ignore this attrs
return nil
end
@@ -1497,10 +1525,12 @@ function Screen:_get_attr_id(attr_state, attrs, hl_id)
end
function Screen:_equal_attr_def(a, b)
- if self._hlstate_cterm then
+ if self._rgb_cterm then
return self:_equal_attrs(a[1],b[1]) and self:_equal_attrs(a[2],b[2])
- else
+ elseif self._options.rgb then
return self:_equal_attrs(a,b[1])
+ else
+ return self:_equal_attrs(a,b[2])
end
end
@@ -1510,7 +1540,8 @@ function Screen:_equal_attrs(a, b)
a.italic == b.italic and a.reverse == b.reverse and
a.foreground == b.foreground and a.background == b.background and
a.special == b.special and a.blend == b.blend and
- a.strikethrough == b.strikethrough
+ a.strikethrough == b.strikethrough and
+ a.fg_indexed == b.fg_indexed and a.bg_indexed == b.bg_indexed
end
function Screen:_equal_info(a, b)
diff --git a/test/functional/ui/screen_basic_spec.lua b/test/functional/ui/screen_basic_spec.lua
index 46f0b5060c..ff9f30d0a1 100644
--- a/test/functional/ui/screen_basic_spec.lua
+++ b/test/functional/ui/screen_basic_spec.lua
@@ -915,6 +915,7 @@ local function screen_tests(linegrid)
-- Regression test for #8357
it('does not have artifacts after temporary chars in insert mode', function()
+ command('set timeoutlen=10000')
command('inoremap jk <esc>')
feed('ifooj')
screen:expect([[
@@ -986,7 +987,7 @@ describe('Screen default colors', function()
it('can be set to light', function()
startup(true, false)
screen:expect{condition=function()
- eq({rgb_bg=Screen.colors.White, rgb_fg=0, rgb_sp=Screen.colors.Red,
+ eq({rgb_fg=Screen.colors.White, rgb_bg=0, rgb_sp=Screen.colors.Red,
cterm_bg=0, cterm_fg=0}, screen.default_colors)
end}
end)
diff --git a/test/functional/ui/searchhl_spec.lua b/test/functional/ui/searchhl_spec.lua
index 486de02a09..635ce7392b 100644
--- a/test/functional/ui/searchhl_spec.lua
+++ b/test/functional/ui/searchhl_spec.lua
@@ -442,7 +442,7 @@ describe('search highlighting', function()
feed_command("call matchadd('MyGroup', 'special')")
feed_command("call matchadd('MyGroup2', 'text', 0)")
- -- searchhl and matchadd matches are exclusive, only the higest priority
+ -- searchhl and matchadd matches are exclusive, only the highest priority
-- is used (and matches with lower priorities are not combined)
feed_command("/ial te")
screen:expect([[
diff --git a/test/functional/ui/sign_spec.lua b/test/functional/ui/sign_spec.lua
index 68e675b8e5..0ed62b21b2 100644
--- a/test/functional/ui/sign_spec.lua
+++ b/test/functional/ui/sign_spec.lua
@@ -26,10 +26,6 @@ describe('Signs', function()
} )
end)
- after_each(function()
- screen:detach()
- end)
-
describe(':sign place', function()
it('allows signs with combining characters', function()
feed('ia<cr>b<cr><esc>')
diff --git a/test/functional/ui/spell_spec.lua b/test/functional/ui/spell_spec.lua
index 913f1b9bed..2c6e586665 100644
--- a/test/functional/ui/spell_spec.lua
+++ b/test/functional/ui/spell_spec.lua
@@ -4,8 +4,9 @@ local helpers = require('test.functional.helpers')(after_each)
local Screen = require('test.functional.ui.screen')
local clear = helpers.clear
local feed = helpers.feed
-local feed_command = helpers.feed_command
local insert = helpers.insert
+local uname = helpers.uname
+local command = helpers.command
describe("'spell'", function()
local screen
@@ -16,16 +17,14 @@ describe("'spell'", function()
screen:attach()
screen:set_default_attr_ids( {
[0] = {bold=true, foreground=Screen.colors.Blue},
- [1] = {special = Screen.colors.Red, undercurl = true}
+ [1] = {special = Screen.colors.Red, undercurl = true},
+ [2] = {special = Screen.colors.Blue1, undercurl = true},
})
end)
- after_each(function()
- screen:detach()
- end)
-
it('joins long lines #7937', function()
- feed_command('set spell')
+ if uname() == 'openbsd' then pending('FIXME #12104', function() end) return end
+ command('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,
@@ -46,4 +45,26 @@ describe("'spell'", function()
|
]])
end)
+
+ 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.", |
+ ^ |
+ |
+ ]])
+ end)
end)
diff --git a/test/functional/ui/syntax_conceal_spec.lua b/test/functional/ui/syntax_conceal_spec.lua
index 00e94ef94b..d1af0e955c 100644
--- a/test/functional/ui/syntax_conceal_spec.lua
+++ b/test/functional/ui/syntax_conceal_spec.lua
@@ -1,6 +1,7 @@
local helpers = require('test.functional.helpers')(after_each)
local Screen = require('test.functional.ui.screen')
local clear, feed, command = helpers.clear, helpers.feed, helpers.command
+local eq = helpers.eq
local insert = helpers.insert
describe('Screen', function()
@@ -17,13 +18,10 @@ describe('Screen', function()
[3] = {reverse = true},
[4] = {bold = true},
[5] = {background = Screen.colors.Yellow},
+ [6] = {background = Screen.colors.LightGrey},
} )
end)
- after_each(function()
- screen:detach()
- end)
-
describe("match and conceal", function()
before_each(function()
@@ -823,5 +821,96 @@ describe('Screen', function()
]])
end)
end)
+
+ it('redraws properly with concealcursor in visual mode', function()
+ command('set concealcursor=v conceallevel=2')
+
+ feed('10Ofoo barf bar barf eggs<esc>')
+ feed(':3<cr>o a<Esc>ggV')
+ screen:expect{grid=[[
+ ^f{6:oo }{1:b}{6: bar }{1:b}{6: eggs} |
+ foo {1:b} bar {1:b} eggs |
+ foo {1:b} bar {1:b} eggs |
+ a |
+ foo {1:b} bar {1:b} eggs |
+ foo {1:b} bar {1:b} eggs |
+ foo {1:b} bar {1:b} eggs |
+ foo {1:b} bar {1:b} eggs |
+ foo {1:b} bar {1:b} eggs |
+ {4:-- VISUAL LINE --} |
+ ]]}
+ feed(string.rep('j', 15))
+ screen:expect{grid=[[
+ {6:foo }{1:b}{6: bar }{1:b}{6: eggs} |
+ {6:foo }{1:b}{6: bar }{1:b}{6: eggs} |
+ {6:foo }{1:b}{6: bar }{1:b}{6: eggs} |
+ {6:foo }{1:b}{6: bar }{1:b}{6: eggs} |
+ {6:foo }{1:b}{6: bar }{1:b}{6: eggs} |
+ {6:foo }{1:b}{6: bar }{1:b}{6: eggs} |
+ {6:foo }{1:b}{6: bar }{1:b}{6: eggs} |
+ {6:foo }{1:b}{6: bar }{1:b}{6: eggs} |
+ ^f{6:oo }{1:b}{6: bar }{1:b}{6: eggs} |
+ {4:-- VISUAL LINE --} |
+ ]]}
+ feed(string.rep('k', 15))
+ screen:expect{grid=[[
+ ^f{6:oo }{1:b}{6: bar }{1:b}{6: eggs} |
+ foo {1:b} bar {1:b} eggs |
+ foo {1:b} bar {1:b} eggs |
+ a |
+ foo {1:b} bar {1:b} eggs |
+ foo {1:b} bar {1:b} eggs |
+ foo {1:b} bar {1:b} eggs |
+ foo {1:b} bar {1:b} eggs |
+ foo {1:b} bar {1:b} eggs |
+ {4:-- VISUAL LINE --} |
+ ]]}
+ end)
+ end)
+
+ it('redraws not too much with conceallevel=1', function()
+ command('set conceallevel=1')
+ command('set redrawdebug+=nodelta')
+
+ insert([[
+ aaa
+ bbb
+ ccc
+ ]])
+ screen:expect{grid=[[
+ aaa |
+ bbb |
+ ccc |
+ ^ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ |
+ ]]}
+
+ -- XXX: hack to get notifications, and check only a single line is
+ -- updated. Could use next_msg() also.
+ local orig_handle_grid_line = screen._handle_grid_line
+ local grid_lines = {}
+ function screen._handle_grid_line(self, grid, row, col, items)
+ table.insert(grid_lines, {row, col, items})
+ orig_handle_grid_line(self, grid, row, col, items)
+ end
+ feed('k')
+ screen:expect{grid=[[
+ aaa |
+ bbb |
+ ^ccc |
+ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ |
+ ]]}
+ eq(grid_lines, {{2, 0, {{'c', 0, 3}}}})
end)
end)
diff --git a/test/functional/ui/tabline_spec.lua b/test/functional/ui/tabline_spec.lua
index dcab9f7ef4..23aae81745 100644
--- a/test/functional/ui/tabline_spec.lua
+++ b/test/functional/ui/tabline_spec.lua
@@ -10,15 +10,9 @@ describe('ui/ext_tabline', function()
clear()
screen = Screen.new(25, 5)
screen:attach({rgb=true, ext_tabline=true})
- screen:set_on_event_handler(function(name, data)
- if name == "tabline_update" then
- event_curtab, event_tabs = unpack(data)
- end
- end)
- end)
-
- after_each(function()
- screen:detach()
+ function screen:_handle_tabline_update(curtab, tabs)
+ event_curtab, event_tabs = curtab, tabs
+ end
end)
it('publishes UI events', function()
diff --git a/test/functional/ui/wildmode_spec.lua b/test/functional/ui/wildmode_spec.lua
index f3fa711fb1..99ebc4971e 100644
--- a/test/functional/ui/wildmode_spec.lua
+++ b/test/functional/ui/wildmode_spec.lua
@@ -16,6 +16,44 @@ describe("'wildmenu'", function()
screen:attach()
end)
+ it('C-E to cancel wildmenu completion restore original input', function()
+ feed(':sign <tab>')
+ screen:expect([[
+ |
+ ~ |
+ ~ |
+ define jump list > |
+ :sign define^ |
+ ]])
+ feed('<C-E>')
+ screen:expect([[
+ |
+ ~ |
+ ~ |
+ ~ |
+ :sign ^ |
+ ]])
+ end)
+
+ it('C-Y to apply selection and end wildmenu completion', function()
+ feed(':sign <tab>')
+ screen:expect([[
+ |
+ ~ |
+ ~ |
+ define jump list > |
+ :sign define^ |
+ ]])
+ feed('<tab><C-Y>')
+ screen:expect([[
+ |
+ ~ |
+ ~ |
+ ~ |
+ :sign jump^ |
+ ]])
+ end)
+
it(':sign <tab> shows wildmenu completions', function()
command('set wildmenu wildmode=full')
feed(':sign <tab>')
@@ -221,6 +259,106 @@ describe("'wildmenu'", function()
]])
end)
+ it('wildmode=longest,list', function()
+ -- Need more than 5 rows, else tabline is covered and will be redrawn.
+ screen:try_resize(25, 7)
+
+ command('set wildmenu wildmode=longest,list')
+
+ -- give wildmode-longest something to expand to
+ feed(':sign u<tab>')
+ screen:expect([[
+ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ :sign un^ |
+ ]])
+ feed('<tab>') -- trigger wildmode list
+ screen:expect([[
+ |
+ ~ |
+ ~ |
+ |
+ :sign un |
+ undefine unplace |
+ :sign un^ |
+ ]])
+ feed('<Esc>')
+ screen:expect([[
+ ^ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ |
+ ]])
+
+ -- give wildmode-longest something it cannot expand, use list
+ feed(':sign un<tab>')
+ screen:expect([[
+ |
+ ~ |
+ ~ |
+ |
+ :sign un |
+ undefine unplace |
+ :sign un^ |
+ ]])
+ feed('<tab>')
+ screen:expect_unchanged()
+ feed('<Esc>')
+ screen:expect([[
+ ^ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ |
+ ]])
+ end)
+
+ it('wildmode=list,longest', function()
+ -- Need more than 5 rows, else tabline is covered and will be redrawn.
+ screen:try_resize(25, 7)
+
+ command('set wildmenu wildmode=list,longest')
+ feed(':sign u<tab>')
+ screen:expect([[
+ |
+ ~ |
+ ~ |
+ |
+ :sign u |
+ undefine unplace |
+ :sign u^ |
+ ]])
+ feed('<tab>') -- trigger wildmode longest
+ screen:expect([[
+ |
+ ~ |
+ ~ |
+ |
+ :sign u |
+ undefine unplace |
+ :sign un^ |
+ ]])
+ feed('<Esc>')
+ screen:expect([[
+ ^ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ |
+ ]])
+ end)
+
it('multiple <C-D> renders correctly', function()
screen:try_resize(25, 7)
diff --git a/test/helpers.lua b/test/helpers.lua
index ebc0a7d811..40b93d9935 100644
--- a/test/helpers.lua
+++ b/test/helpers.lua
@@ -55,17 +55,32 @@ local check_logs_useless_lines = {
['See README_MISSING_SYSCALL_OR_IOCTL for guidance']=3,
}
-function module.eq(expected, actual, context)
- return assert.are.same(expected, actual, context)
+--- Invokes `fn` and includes the tail of `logfile` in the error message if it
+--- fails.
+---
+--@param logfile Log file, defaults to $NVIM_LOG_FILE or '.nvimlog'
+--@param fn Function to invoke
+--@param ... Function arguments
+local function dumplog(logfile, fn, ...)
+ -- module.validate({
+ -- logfile={logfile,'s',true},
+ -- fn={fn,'f',false},
+ -- })
+ local status, rv = pcall(fn, ...)
+ if status == false then
+ logfile = logfile or os.getenv('NVIM_LOG_FILE') or '.nvimlog'
+ local logtail = module.read_nvim_log(logfile)
+ error(string.format('%s\n%s', rv, logtail))
+ end
end
-function module.neq(expected, actual, context)
- return assert.are_not.same(expected, actual, context)
+function module.eq(expected, actual, context, logfile)
+ return dumplog(logfile, assert.are.same, expected, actual, context)
end
-function module.ok(res, msg)
- return assert.is_true(res, msg)
+function module.neq(expected, actual, context, logfile)
+ return dumplog(logfile, assert.are_not.same, expected, actual, context)
end
-function module.near(actual, expected, tolerance)
- return assert.is.near(actual, expected, tolerance)
+function module.ok(res, msg, logfile)
+ return dumplog(logfile, assert.is_true, res, msg)
end
function module.matches(pat, actual)
if nil ~= string.match(actual, pat) then
@@ -74,7 +89,24 @@ function module.matches(pat, actual)
error(string.format('Pattern does not match.\nPattern:\n%s\nActual:\n%s', pat, actual))
end
--- Invokes `fn` and returns the error string, or raises an error if `fn` succeeds.
+--- Asserts that `pat` matches one or more lines in the tail of $NVIM_LOG_FILE.
+---
+--@param pat (string) Lua pattern to search for in the log file.
+--@param logfile (string, default=$NVIM_LOG_FILE) full path to log file.
+function module.assert_log(pat, logfile)
+ logfile = logfile or os.getenv('NVIM_LOG_FILE') or '.nvimlog'
+ local nrlines = 10
+ local lines = module.read_file_list(logfile, -nrlines) or {}
+ for _,line in ipairs(lines) do
+ if line:match(pat) then return end
+ end
+ local logtail = module.read_nvim_log(logfile)
+ error(string.format('Pattern %q not found in log (last %d lines): %s:\n%s',
+ pat, nrlines, logfile, logtail))
+end
+
+-- Invokes `fn` and returns the error string (may truncate full paths), or
+-- raises an error if `fn` succeeds.
--
-- Usage:
-- -- Match exact string.
@@ -88,7 +120,20 @@ function module.pcall_err(fn, ...)
if status == true then
error('expected failure, but got success')
end
+ -- From this:
+ -- /home/foo/neovim/runtime/lua/vim/shared.lua:186: Expected string, got number
+ -- to this:
+ -- Expected string, got number
local errmsg = tostring(rv):gsub('^[^:]+:%d+: ', '')
+ -- From this:
+ -- Error executing lua: /very/long/foo.lua:186: Expected string, got number
+ -- to this:
+ -- Error executing lua: .../foo.lua:186: Expected string, got number
+ errmsg = errmsg:gsub([[lua: [a-zA-Z]?:?[^:]-[/\]([^:/\]+):%d+: ]], 'lua: .../%1: ')
+ -- Compiled modules will not have a path and will just be a name like
+ -- shared.lua:186, so strip the number.
+ errmsg = errmsg:gsub([[lua: ([^:/\ ]+):%d+: ]], 'lua: .../%1: ')
+ -- ^ Windows drive-letter (C:)
return errmsg
end
@@ -245,24 +290,6 @@ module.tmpname = (function()
end)
end)()
-function module.map(func, tab)
- local rettab = {}
- for k, v in pairs(tab) do
- rettab[k] = func(v)
- end
- return rettab
-end
-
-function module.filter(filter_func, tab)
- local rettab = {}
- for _, entry in pairs(tab) do
- if filter_func(entry) then
- table.insert(rettab, entry)
- end
- end
- return rettab
-end
-
function module.hasenv(name)
local env = os.getenv(name)
if env and env ~= '' then
@@ -715,17 +742,18 @@ end
function module.isCI(name)
local any = (name == nil)
- assert(any or name == 'appveyor' or name == 'quickbuild' or name == 'travis')
+ assert(any or name == 'appveyor' or name == 'travis' or name == 'sourcehut')
local av = ((any or name == 'appveyor') and nil ~= os.getenv('APPVEYOR'))
local tr = ((any or name == 'travis') and nil ~= os.getenv('TRAVIS'))
- local qb = ((any or name == 'quickbuild') and nil ~= lfs.attributes('/usr/home/quickbuild'))
- return tr or av or qb
+ local sh = ((any or name == 'sourcehut') and nil ~= os.getenv('SOURCEHUT'))
+ return tr or av or sh
+
end
--- Gets the contents of $NVIM_LOG_FILE for printing to the build log.
+-- Gets the (tail) contents of `logfile`.
-- Also moves the file to "${NVIM_LOG_FILE}.displayed" on CI environments.
-function module.read_nvim_log()
- local logfile = os.getenv('NVIM_LOG_FILE') or '.nvimlog'
+function module.read_nvim_log(logfile, ci_rename)
+ logfile = logfile or os.getenv('NVIM_LOG_FILE') or '.nvimlog'
local is_ci = module.isCI()
local keep = is_ci and 999 or 10
local lines = module.read_file_list(logfile, -keep) or {}
@@ -736,7 +764,7 @@ function module.read_nvim_log()
log = log..line..'\n'
end
log = log..('-'):rep(78)..'\n'
- if is_ci then
+ if is_ci and ci_rename then
os.rename(logfile, logfile .. '.displayed')
end
return log
diff --git a/test/unit/eval/helpers.lua b/test/unit/eval/helpers.lua
index 3d1c42c3a0..b600f01ab2 100644
--- a/test/unit/eval/helpers.lua
+++ b/test/unit/eval/helpers.lua
@@ -136,11 +136,15 @@ local function typvalt2lua_tab_init()
return
end
typvalt2lua_tab = {
+ [tonumber(eval.VAR_BOOL)] = function(t)
+ return ({
+ [tonumber(eval.kBoolVarFalse)] = false,
+ [tonumber(eval.kBoolVarTrue)] = true,
+ })[tonumber(t.vval.v_bool)]
+ end,
[tonumber(eval.VAR_SPECIAL)] = function(t)
return ({
- [tonumber(eval.kSpecialVarFalse)] = false,
[tonumber(eval.kSpecialVarNull)] = nil_value,
- [tonumber(eval.kSpecialVarTrue)] = true,
})[tonumber(t.vval.v_special)]
end,
[tonumber(eval.VAR_NUMBER)] = function(t)
@@ -349,8 +353,8 @@ lua2typvalt = function(l, processed)
[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}},
- [true] = {'VAR_SPECIAL', {v_special=eval.kSpecialVarTrue}},
- [false] = {'VAR_SPECIAL', {v_special=eval.kSpecialVarFalse}},
+ [true] = {'VAR_BOOL', {v_bool=eval.kBoolVarTrue}},
+ [false] = {'VAR_BOOL', {v_bool=eval.kBoolVarFalse}},
}
for k, v in pairs(special_vals) do
@@ -406,7 +410,7 @@ end
local alloc_logging_helpers = {
list = function(l) return {func='calloc', args={1, ffi.sizeof('list_T')}, ret=void(l)} end,
li = function(li) return {func='malloc', args={ffi.sizeof('listitem_T')}, ret=void(li)} end,
- dict = function(d) return {func='malloc', args={ffi.sizeof('dict_T')}, ret=void(d)} end,
+ dict = function(d) return {func='calloc', args={1, ffi.sizeof('dict_T')}, ret=void(d)} end,
di = function(di, size)
size = alloc_len(size, function() return di.di_key end)
return {func='malloc', args={ffi.offsetof('dictitem_T', 'di_key') + size + 1}, ret=void(di)}
diff --git a/test/unit/eval/typval_spec.lua b/test/unit/eval/typval_spec.lua
index 4535d6a0b2..7c03005529 100644
--- a/test/unit/eval/typval_spec.lua
+++ b/test/unit/eval/typval_spec.lua
@@ -14,7 +14,7 @@ local cimport = helpers.cimport
local to_cstr = helpers.to_cstr
local alloc_log_new = helpers.alloc_log_new
local concat_tables = helpers.concat_tables
-local map = helpers.map
+local map = helpers.tbl_map
local a = eval_helpers.alloc_logging_helpers
local int = eval_helpers.int
@@ -48,8 +48,7 @@ local lib = cimport('./src/nvim/eval/typval.h', './src/nvim/memory.h',
local function vimconv_alloc()
return ffi.gc(
- ffi.cast('vimconv_T*', lib.xcalloc(1, ffi.sizeof('vimconv_T'))),
- function(vc)
+ ffi.cast('vimconv_T*', lib.xcalloc(1, ffi.sizeof('vimconv_T'))), function(vc)
lib.convert_setup(vc, nil, nil)
lib.xfree(vc)
end)
@@ -1235,13 +1234,13 @@ describe('typval.c', function()
local l = list()
local l2 = list()
- -- NULL lists are not equal to empty lists
- eq(false, lib.tv_list_equal(l, nil, true, false))
- eq(false, lib.tv_list_equal(nil, l, false, false))
- eq(false, lib.tv_list_equal(nil, l, false, true))
- eq(false, lib.tv_list_equal(l, nil, true, true))
+ -- NULL lists are equal to empty lists
+ eq(true, lib.tv_list_equal(l, nil, true, false))
+ eq(true, lib.tv_list_equal(nil, l, false, false))
+ eq(true, lib.tv_list_equal(nil, l, false, true))
+ eq(true, lib.tv_list_equal(l, nil, true, true))
- -- Yet NULL lists are equal themselves
+ -- NULL lists are equal themselves
eq(true, lib.tv_list_equal(nil, nil, true, false))
eq(true, lib.tv_list_equal(nil, nil, false, false))
eq(true, lib.tv_list_equal(nil, nil, false, true))
@@ -2026,6 +2025,26 @@ describe('typval.c', function()
alloc_log:check({})
end)
end)
+ describe('float()', function()
+ itp('works', function()
+ local d = dict({test=10})
+ alloc_log:clear()
+ eq({test=10}, dct2tbl(d))
+ eq(OK, lib.tv_dict_add_float(d, 'testt', 3, 1.5))
+ local dis = dict_items(d)
+ 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()'))
+ 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,
+ nil))
+ lib.emsg_skip = lib.emsg_skip - 1
+ alloc_log:clear_tmp_allocs()
+ alloc_log:check({})
+ end)
+ end)
describe('str()', function()
itp('works', function()
local d = dict({test=10})
@@ -2629,13 +2648,13 @@ describe('typval.c', function()
local l2 = lua2typvalt(empty_list)
local nl = lua2typvalt(null_list)
- -- NULL lists are not equal to empty lists
- eq(false, lib.tv_equal(l, nl, true, false))
- eq(false, lib.tv_equal(nl, l, false, false))
- eq(false, lib.tv_equal(nl, l, false, true))
- eq(false, lib.tv_equal(l, nl, true, true))
+ -- NULL lists are equal to empty lists
+ eq(true, lib.tv_equal(l, nl, true, false))
+ eq(true, lib.tv_equal(nl, l, false, false))
+ eq(true, lib.tv_equal(nl, l, false, true))
+ eq(true, lib.tv_equal(l, nl, true, true))
- -- Yet NULL lists are equal themselves
+ -- NULL lists are equal themselves
eq(true, lib.tv_equal(nl, nl, true, false))
eq(true, lib.tv_equal(nl, nl, false, false))
eq(true, lib.tv_equal(nl, nl, false, true))
@@ -2818,6 +2837,7 @@ describe('typval.c', function()
{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_BOOL, nil},
{lib.VAR_SPECIAL, nil},
{lib.VAR_UNKNOWN, 'E908: using an invalid value as a String'},
}) do
@@ -2848,8 +2868,8 @@ describe('typval.c', function()
{lib.VAR_LIST, {v_list=NULL}, 'E745: Using a List as a Number', 0},
{lib.VAR_DICT, {v_dict=NULL}, 'E728: Using a Dictionary as a Number', 0},
{lib.VAR_SPECIAL, {v_special=lib.kSpecialVarNull}, nil, 0},
- {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarTrue}, nil, 1},
- {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarFalse}, nil, 0},
+ {lib.VAR_BOOL, {v_bool=lib.kBoolVarTrue}, nil, 1},
+ {lib.VAR_BOOL, {v_bool=lib.kBoolVarFalse}, nil, 0},
{lib.VAR_UNKNOWN, nil, 'E685: Internal error: tv_get_number(UNKNOWN)', 0},
}) do
-- Using to_cstr, cannot free with tv_clear
@@ -2877,8 +2897,8 @@ describe('typval.c', function()
{lib.VAR_LIST, {v_list=NULL}, 'E745: Using a List as a Number', 0},
{lib.VAR_DICT, {v_dict=NULL}, 'E728: Using a Dictionary as a Number', 0},
{lib.VAR_SPECIAL, {v_special=lib.kSpecialVarNull}, nil, 0},
- {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarTrue}, nil, 1},
- {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarFalse}, nil, 0},
+ {lib.VAR_BOOL, {v_bool=lib.kBoolVarTrue}, nil, 1},
+ {lib.VAR_BOOL, {v_bool=lib.kBoolVarFalse}, nil, 0},
{lib.VAR_UNKNOWN, nil, 'E685: Internal error: tv_get_number(UNKNOWN)', 0},
}) do
-- Using to_cstr, cannot free with tv_clear
@@ -2911,8 +2931,8 @@ describe('typval.c', function()
{lib.VAR_LIST, {v_list=NULL}, 'E745: Using a List as a Number', -1},
{lib.VAR_DICT, {v_dict=NULL}, 'E728: Using a Dictionary as a Number', -1},
{lib.VAR_SPECIAL, {v_special=lib.kSpecialVarNull}, nil, 0},
- {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarTrue}, nil, 1},
- {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarFalse}, nil, 0},
+ {lib.VAR_BOOL, {v_bool=lib.kBoolVarTrue}, nil, 1},
+ {lib.VAR_BOOL, {v_bool=lib.kBoolVarFalse}, nil, 0},
{lib.VAR_UNKNOWN, nil, 'E685: Internal error: tv_get_number(UNKNOWN)', -1},
}) do
lib.curwin.w_cursor.lnum = 46
@@ -2941,8 +2961,8 @@ describe('typval.c', function()
{lib.VAR_LIST, {v_list=NULL}, 'E893: Using a List as a Float', 0},
{lib.VAR_DICT, {v_dict=NULL}, 'E894: Using a Dictionary as a Float', 0},
{lib.VAR_SPECIAL, {v_special=lib.kSpecialVarNull}, 'E907: Using a special value as a Float', 0},
- {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarTrue}, 'E907: Using a special value as a Float', 0},
- {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarFalse}, 'E907: Using a special value as a Float', 0},
+ {lib.VAR_BOOL, {v_bool=lib.kBoolVarTrue}, 'E362: Using a boolean value as a Float', 0},
+ {lib.VAR_BOOL, {v_bool=lib.kBoolVarFalse}, 'E362: Using a boolean value as a Float', 0},
{lib.VAR_UNKNOWN, nil, 'E685: Internal error: tv_get_float(UNKNOWN)', 0},
}) do
-- Using to_cstr, cannot free with tv_clear
@@ -2973,8 +2993,8 @@ describe('typval.c', function()
{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_SPECIAL, {v_special=lib.kSpecialVarNull}, nil, 'null'},
- {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarTrue}, nil, 'true'},
- {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarFalse}, nil, 'false'},
+ {lib.VAR_BOOL, {v_bool=lib.kBoolVarTrue}, nil, 'true'},
+ {lib.VAR_BOOL, {v_bool=lib.kBoolVarFalse}, nil, 'false'},
{lib.VAR_UNKNOWN, nil, 'E908: using an invalid value as a String', ''},
}) do
-- Using to_cstr in place of Neovim allocated string, cannot
@@ -2985,7 +3005,8 @@ describe('typval.c', function()
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 then
+ 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)
@@ -3016,8 +3037,8 @@ describe('typval.c', function()
{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_SPECIAL, {v_special=lib.kSpecialVarNull}, nil, 'null'},
- {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarTrue}, nil, 'true'},
- {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarFalse}, nil, 'false'},
+ {lib.VAR_BOOL, {v_bool=lib.kBoolVarTrue}, nil, 'true'},
+ {lib.VAR_BOOL, {v_bool=lib.kBoolVarFalse}, nil, 'false'},
{lib.VAR_UNKNOWN, nil, 'E908: using an invalid value as a String', nil},
}) do
-- Using to_cstr, cannot free with tv_clear
@@ -3027,7 +3048,8 @@ describe('typval.c', function()
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 then
+ 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)
@@ -3057,8 +3079,8 @@ describe('typval.c', function()
{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_SPECIAL, {v_special=lib.kSpecialVarNull}, nil, 'null'},
- {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarTrue}, nil, 'true'},
- {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarFalse}, nil, 'false'},
+ {lib.VAR_BOOL, {v_bool=lib.kBoolVarTrue}, nil, 'true'},
+ {lib.VAR_BOOL, {v_bool=lib.kBoolVarFalse}, nil, 'false'},
{lib.VAR_UNKNOWN, nil, 'E908: using an invalid value as a String', ''},
}) do
-- Using to_cstr, cannot free with tv_clear
@@ -3069,7 +3091,8 @@ describe('typval.c', function()
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 then
+ 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)
@@ -3099,8 +3122,8 @@ describe('typval.c', function()
{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_SPECIAL, {v_special=lib.kSpecialVarNull}, nil, 'null'},
- {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarTrue}, nil, 'true'},
- {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarFalse}, nil, 'false'},
+ {lib.VAR_BOOL, {v_bool=lib.kBoolVarTrue}, nil, 'true'},
+ {lib.VAR_BOOL, {v_bool=lib.kBoolVarFalse}, nil, 'false'},
{lib.VAR_UNKNOWN, nil, 'E908: using an invalid value as a String', nil},
}) do
-- Using to_cstr, cannot free with tv_clear
@@ -3111,7 +3134,8 @@ describe('typval.c', function()
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 then
+ 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)
diff --git a/test/unit/helpers.lua b/test/unit/helpers.lua
index 24dbc65bd0..465b553693 100644
--- a/test/unit/helpers.lua
+++ b/test/unit/helpers.lua
@@ -13,7 +13,7 @@ local syscall = nil
local check_cores = global_helpers.check_cores
local dedent = global_helpers.dedent
local neq = global_helpers.neq
-local map = global_helpers.map
+local map = global_helpers.tbl_map
local eq = global_helpers.eq
local trim = global_helpers.trim
@@ -96,8 +96,8 @@ local init = only_separate(function()
c.func(unpack(c.args))
end
libnvim.time_init()
- libnvim.early_init()
libnvim.event_init()
+ libnvim.early_init(nil)
if child_calls_mod then
for _, c in ipairs(child_calls_mod) do
c.func(unpack(c.args))
@@ -545,7 +545,7 @@ 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 = 1
+ trace_level = 0
else
trace_level = tonumber(trace_level)
end
@@ -708,7 +708,7 @@ local function check_child_err(rd)
local eres = sc.read(rd, 2)
if eres ~= '$\n' then
if #trace == 0 then
- err = '\nTest crashed, no trace available\n'
+ err = '\nTest crashed, no trace available (check NVIM_TEST_TRACE_LEVEL)\n'
else
err = '\nTest crashed, trace:\n' .. tracehelp
for i = 1, #trace do
diff --git a/test/unit/marktree_spec.lua b/test/unit/marktree_spec.lua
new file mode 100644
index 0000000000..56acc0f93e
--- /dev/null
+++ b/test/unit/marktree_spec.lua
@@ -0,0 +1,190 @@
+local helpers = require("test.unit.helpers")(after_each)
+local itp = helpers.gen_itp(it)
+
+local ffi = helpers.ffi
+local eq = helpers.eq
+local ok = helpers.ok
+
+local lib = helpers.cimport("./src/nvim/marktree.h")
+
+local function tablelength(t)
+ local count = 0
+ for _ in pairs(t) do count = count + 1 end
+ return count
+end
+
+local function pos_leq(a, b)
+ return a[1] < b[1] or (a[1] == b[1] and a[2] <= b[2])
+end
+
+-- Checks that shadow and tree is consistent, and optionally
+-- return the order
+local function shadoworder(tree, shadow, iter, giveorder)
+ ok(iter ~= nil)
+ local status = lib.marktree_itr_first(tree, iter)
+ local count = 0
+ local pos2id, id2pos = {}, {}
+ local last
+ if not status and next(shadow) == nil then
+ return pos2id, id2pos
+ end
+ repeat
+ local mark = lib.marktree_itr_current(iter)
+ local id = tonumber(mark.id)
+ local spos = shadow[id]
+ if (mark.row ~= spos[1] or mark.col ~= spos[2]) then
+ error("invalid pos for "..id..":("..mark.row..", "..mark.col..") instead of ("..spos[1]..", "..spos[2]..")")
+ end
+ if mark.right_gravity ~= spos[3] then
+ error("invalid gravity for "..id..":("..mark.row..", "..mark.col..")")
+ end
+ if count > 0 then
+ if not pos_leq(last, spos) then
+ error("DISORDER")
+ end
+ end
+ count = count + 1
+ last = spos
+ if giveorder then
+ pos2id[count] = id
+ id2pos[id] = count
+ end
+ until not lib.marktree_itr_next(tree, iter)
+ local shadowlen = tablelength(shadow)
+ if shadowlen ~= count then
+ error("missed some keys? (shadow "..shadowlen..", tree "..count..")")
+ end
+ return id2pos, pos2id
+end
+
+local function shadowsplice(shadow, start, old_extent, new_extent)
+ local old_end = {start[1] + old_extent[1],
+ (old_extent[1] == 0 and start[2] or 0) + old_extent[2]}
+ local new_end = {start[1] + new_extent[1],
+ (new_extent[1] == 0 and start[2] or 0) + new_extent[2]}
+ local delta = {new_end[1] - old_end[1], new_end[2] - old_end[2]}
+ for _, pos in pairs(shadow) do
+ if pos_leq(start, pos) then
+ if pos_leq(pos, old_end) then
+ -- delete region
+ if pos[3] then -- right gravity
+ pos[1], pos[2] = new_end[1], new_end[2]
+ else
+ pos[1], pos[2] = start[1], start[2]
+ end
+ else
+ if pos[1] == old_end[1] then
+ pos[2] = pos[2] + delta[2]
+ end
+ pos[1] = pos[1] + delta[1]
+ end
+ end
+ end
+end
+
+local function dosplice(tree, shadow, start, old_extent, new_extent)
+ lib.marktree_splice(tree, start[1], start[2], old_extent[1], old_extent[2], new_extent[1], new_extent[2])
+ shadowsplice(shadow, start, old_extent, new_extent)
+end
+
+describe('marktree', function()
+ itp('works', function()
+ local tree = ffi.new("MarkTree[1]") -- zero initialized by luajit
+ local shadow = {}
+ local iter = ffi.new("MarkTreeIter[1]")
+ local iter2 = ffi.new("MarkTreeIter[1]")
+
+ for i = 1,100 do
+ for j = 1,100 do
+ local gravitate = (i%2) > 0
+ local id = tonumber(lib.marktree_put(tree, j, i, gravitate))
+ ok(id > 0)
+ eq(nil, shadow[id])
+ shadow[id] = {j,i,gravitate}
+ end
+ -- checking every insert is too slow, but this is ok
+ lib.marktree_check(tree)
+ end
+
+ -- ss = lib.mt_inspect_rec(tree)
+ -- io.stdout:write(ffi.string(ss))
+ -- io.stdout:flush()
+
+ local id2pos, pos2id = shadoworder(tree, shadow, iter)
+ eq({}, pos2id) -- not set if not requested
+ eq({}, id2pos)
+
+ for i,ipos in pairs(shadow) do
+ local pos = lib.marktree_lookup(tree, i, iter)
+ eq(ipos[1], pos.row)
+ eq(ipos[2], pos.col)
+ local k = lib.marktree_itr_current(iter)
+ eq(ipos[1], k.row)
+ eq(ipos[2], k.col, ipos[1])
+ lib.marktree_itr_next(tree, iter)
+ -- TODO(bfredl): use id2pos to check neighbour?
+ -- local k2 = lib.marktree_itr_current(iter)
+ end
+
+ for i,ipos in pairs(shadow) do
+ lib.marktree_itr_get(tree, ipos[1], ipos[2], iter)
+ local k = lib.marktree_itr_current(iter)
+ eq(i, tonumber(k.id))
+ eq(ipos[1], k.row)
+ eq(ipos[2], k.col)
+ end
+
+ ok(lib.marktree_itr_first(tree, iter))
+ local del = lib.marktree_itr_current(iter)
+
+ lib.marktree_del_itr(tree, iter, false)
+ shadow[tonumber(del.id)] = nil
+ shadoworder(tree, shadow, iter)
+
+ for _, ci in ipairs({0,-1,1,-2,2,-10,10}) do
+ for i = 1,100 do
+ lib.marktree_itr_get(tree, i, 50+ci, iter)
+ local k = lib.marktree_itr_current(iter)
+ local id = tonumber(k.id)
+ eq(shadow[id][1], k.row)
+ eq(shadow[id][2], k.col)
+ lib.marktree_del_itr(tree, iter, false)
+ shadow[id] = nil
+ end
+ lib.marktree_check(tree)
+ shadoworder(tree, shadow, iter)
+ end
+
+ -- NB: this is quite rudimentary. We rely on
+ -- functional tests exercising splicing quite a bit
+ lib.marktree_check(tree)
+ dosplice(tree, shadow, {2,2}, {0,5}, {1, 2})
+ lib.marktree_check(tree)
+ shadoworder(tree, shadow, iter)
+ dosplice(tree, shadow, {30,2}, {30,5}, {1, 2})
+ lib.marktree_check(tree)
+ shadoworder(tree, shadow, iter)
+
+ dosplice(tree, shadow, {5,3}, {0,2}, {0, 5})
+ shadoworder(tree, shadow, iter)
+ lib.marktree_check(tree)
+
+ -- build then burn (HOORAY! HOORAY!)
+ while next(shadow) do
+ lib.marktree_itr_first(tree, iter)
+ -- delete every other key for fun and profit
+ while true do
+ local k = lib.marktree_itr_current(iter)
+ lib.marktree_del_itr(tree, iter, false)
+ ok(shadow[tonumber(k.id)] ~= nil)
+ shadow[tonumber(k.id)] = nil
+ local stat = lib.marktree_itr_next(tree, iter)
+ if not stat then
+ break
+ end
+ end
+ lib.marktree_check(tree)
+ shadoworder(tree, shadow, iter2)
+ end
+ end)
+end)
diff --git a/test/unit/mbyte_spec.lua b/test/unit/mbyte_spec.lua
index d27f52923a..fdb1bceab0 100644
--- a/test/unit/mbyte_spec.lua
+++ b/test/unit/mbyte_spec.lua
@@ -8,11 +8,6 @@ local mbyte = helpers.cimport("./src/nvim/mbyte.h")
local charset = helpers.cimport('./src/nvim/charset.h')
describe('mbyte', function()
- if helpers.isCI('quickbuild') then
- pending("crashes on quickbuild", function() end)
- return
- end
-
-- Array for composing characters
local intp = ffi.typeof('int[?]')
local function to_intp()
diff --git a/test/unit/os/env_spec.lua b/test/unit/os/env_spec.lua
index c543551607..ad05b134e0 100644
--- a/test/unit/os/env_spec.lua
+++ b/test/unit/os/env_spec.lua
@@ -121,7 +121,7 @@ describe('env.c', function()
local name = 'NVIM_UNIT_TEST_GETENV_1N'
local value = 'NVIM_UNIT_TEST_GETENV_1V'
eq(NULL, os_getenv(name))
- -- Use os_setenv because Lua dosen't have setenv.
+ -- Use os_setenv because Lua doesn't have setenv.
os_setenv(name, value, 1)
eq(value, os_getenv(name))
@@ -172,7 +172,7 @@ describe('env.c', function()
i = i + 1
name = cimp.os_getenvname_at_index(i)
end
- eq(true, (table.getn(names)) > 0)
+ eq(true, #names > 0)
eq(true, found_name)
end)
diff --git a/test/unit/os/fs_spec.lua b/test/unit/os/fs_spec.lua
index 526a09e845..7fd71cb1ae 100644
--- a/test/unit/os/fs_spec.lua
+++ b/test/unit/os/fs_spec.lua
@@ -110,7 +110,7 @@ describe('fs.c', function()
describe('os_chdir', function()
itp('fails with path="~"', function()
- eq(false, os_isdir('~')) -- sanity check: no literal "~" directory.
+ eq(false, os_isdir('~'), 'sanity check: no literal "~" directory')
local length = 4096
local expected_cwd = cstr(length, '')
local cwd = cstr(length, '')
diff --git a/test/unit/path_spec.lua b/test/unit/path_spec.lua
index e8ce660ce1..356c4997fa 100644
--- a/test/unit/path_spec.lua
+++ b/test/unit/path_spec.lua
@@ -66,10 +66,10 @@ describe('path.c', function()
end)
describe('path_full_compare', function()
- local function path_full_compare(s1, s2, cn)
+ local function path_full_compare(s1, s2, cn, ee)
s1 = to_cstr(s1)
s2 = to_cstr(s2)
- return cimp.path_full_compare(s1, s2, cn or 0)
+ return cimp.path_full_compare(s1, s2, cn or 0, ee or 1)
end
local f1 = 'f1.o'
@@ -456,7 +456,7 @@ describe('path.c', function()
end)
itp('fails and uses filename when the path is relative to HOME', function()
- eq(false, cimp.os_isdir('~')) -- sanity check: no literal "~" directory.
+ eq(false, cimp.os_isdir('~'), 'sanity check: no literal "~" directory')
local absolute_path = '~/home.file'
local buflen = string.len(absolute_path) + 1
local do_expand = 1
diff --git a/test/unit/preprocess.lua b/test/unit/preprocess.lua
index 1073855a7d..3786bc2122 100644
--- a/test/unit/preprocess.lua
+++ b/test/unit/preprocess.lua
@@ -89,6 +89,10 @@ local Gcc = {
get_defines_extra_flags = {'-std=c99', '-dM', '-E'},
get_declarations_extra_flags = {'-std=c99', '-P', '-E'},
}
+if ffi.abi("32bit") then
+ table.insert(Gcc.get_defines_extra_flags, '-m32')
+ table.insert(Gcc.get_declarations_extra_flags, '-m32')
+end
function Gcc:define(name, args, val)
local define = '-D' .. name
diff --git a/test/unit/tui_spec.lua b/test/unit/tui_spec.lua
new file mode 100644
index 0000000000..e6b5c889d7
--- /dev/null
+++ b/test/unit/tui_spec.lua
@@ -0,0 +1,136 @@
+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")
+
+itp('handle_background_color', function()
+ local handle_background_color = cinput.ut_handle_background_color
+ local term_input = ffi.new('TermInput', {})
+ local events = globals.main_loop.thread_events
+
+ -- Short-circuit when not waiting for response.
+ term_input.waiting_for_bg_response = 0
+ eq(false, 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(true, handle_background_color(term_input))
+ eq(0, term_input.waiting_for_bg_response)
+ eq(1, multiqueue.multiqueue_size(events))
+
+ local event = multiqueue.multiqueue_get(events)
+ local bg_event = ffi.cast("Event*", event.argv[1])
+ eq(bg, ffi.string(bg_event.argv[0]))
+
+ -- 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: not necessarily correct behavior, but tests it.
+ 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(false, handle_background_color(term_input))
+ eq(0, term_input.waiting_for_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(false, 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(true, handle_background_color(term_input))
+ eq(0, term_input.waiting_for_bg_response)
+
+ eq(1, multiqueue.multiqueue_size(events))
+ eq(3, rbuf.size)
+ rbuffer.rbuffer_consumed(rbuf, rbuf.size)
+end)
diff --git a/third-party/CMakeLists.txt b/third-party/CMakeLists.txt
index 43cad34aae..477e25a882 100644
--- a/third-party/CMakeLists.txt
+++ b/third-party/CMakeLists.txt
@@ -41,6 +41,7 @@ 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
# 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})
if(USE_BUNDLED AND MSVC)
option(USE_BUNDLED_GETTEXT "Use the bundled version of gettext." ON)
@@ -134,38 +135,38 @@ include(ExternalProject)
if(WIN32)
# "nvim" branch of https://github.com/neovim/libuv
- set(LIBUV_URL https://github.com/neovim/libuv/archive/eeae18d085de25f138c23966f98a179f0fb609e7.tar.gz)
- set(LIBUV_SHA256 c37d0b7cb1defe69ae8dbb4d712c0d7cf838d6539178e8bcf71c72579ab5b666)
+ set(LIBUV_URL https://github.com/neovim/libuv/archive/b899d12b0d56d217f31222da83f8c398355b69ef.tar.gz)
+ set(LIBUV_SHA256 eb7e37b824887e1b31a4e31d1d9bad4c03d8b98532d9cce5f67a3b70495a4b2a)
else()
- set(LIBUV_URL https://github.com/libuv/libuv/archive/v1.30.0.tar.gz)
- set(LIBUV_SHA256 44c8fdadf3b3f393006a4ac4ba144020673a3f9cd72bed1fbb2c366ebcf0d199)
+ set(LIBUV_URL https://github.com/libuv/libuv/archive/v1.34.2.tar.gz)
+ set(LIBUV_SHA256 0d9d38558b45c006c1ea4e8529bae64caf8becda570295ea74e3696362aeb7f2)
endif()
set(MSGPACK_URL https://github.com/msgpack/msgpack-c/releases/download/cpp-3.0.0/msgpack-3.0.0.tar.gz)
set(MSGPACK_SHA256 bfbb71b7c02f806393bc3cbc491b40523b89e64f83860c58e3e54af47de176e4)
-# https://github.com/LuaJIT/LuaJIT/tree/v2.0
-set(LUAJIT_URL https://github.com/LuaJIT/LuaJIT/archive/61464b0a5b685489bee7b6680c0e9663f2143a84.tar.gz)
-set(LUAJIT_SHA256 033fa4ef19f559ef18a9b9fb017d0cb8be58befe05ab604e92814234910f1c68)
+# https://github.com/LuaJIT/LuaJIT/tree/v2.1
+set(LUAJIT_URL https://github.com/LuaJIT/LuaJIT/archive/f0e865dd4861520258299d0f2a56491bd9d602e1.tar.gz)
+set(LUAJIT_SHA256 ad5077bd861241bf5e50ae4bf543d291c5fcffab95ccc3218401131f503e45bd)
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/v2.4.4.tar.gz)
-set(LUAROCKS_SHA256 9eb3d0738fd02ad8bf39bcedccac4e83e9b5fff2bcca247c3584b925b2075d9c)
+set(LUAROCKS_URL https://github.com/luarocks/luarocks/archive/v3.2.1.tar.gz)
+set(LUAROCKS_SHA256 0cab9f79311083f33e4d8f5a76021604f1d3f7141ce9a2ef1d8b717d92058370)
set(UNIBILIUM_URL https://github.com/neovim/unibilium/archive/92d929f.tar.gz)
set(UNIBILIUM_SHA256 29815283c654277ef77a3adcc8840db79ddbb20a0f0b0c8f648bd8cd49a02e4b)
-set(LIBTERMKEY_URL http://www.leonerd.org.uk/code/libtermkey/libtermkey-0.21.1.tar.gz)
-set(LIBTERMKEY_SHA256 cecbf737f35d18f433c8d7864f63c0f878af41f8bd0255a3ebb16010dc044d5f)
+set(LIBTERMKEY_URL http://www.leonerd.org.uk/code/libtermkey/libtermkey-0.22.tar.gz)
+set(LIBTERMKEY_SHA256 6945bd3c4aaa83da83d80a045c5563da4edd7d0374c62c0d35aec09eb3014600)
-set(LIBVTERM_URL https://github.com/neovim/libvterm/archive/4a5fa43e0dbc0db4fe67d40d788d60852864df9e.tar.gz)
-set(LIBVTERM_SHA256 49b3cf2dcb988b887671b1011cfeac98ff81bb5c23fb4ac34b91a59524992935)
+set(LIBVTERM_URL https://github.com/neovim/libvterm/archive/65dbda3ed214f036ee799d18b2e693a833a0e591.tar.gz)
+set(LIBVTERM_SHA256 95d3c7e86336fbd40dfd7a0aa0a795320bb71bc957ea995ea0e549c96d20db3a)
-set(LUV_VERSION 1.30.0-0)
+set(LUV_VERSION 1.30.1-1)
set(LUV_URL https://github.com/luvit/luv/archive/${LUV_VERSION}.tar.gz)
-set(LUV_SHA256 6e468fa17bf222ca8ce0bfffdbdf947fc897da48643a12955db92f80e2c852f5)
+set(LUV_SHA256 2b17921e2e18094df6221e3cd291c82d4569e50d8c081518d3e775dceae267cf)
set(LUA_COMPAT53_URL https://github.com/keplerproject/lua-compat-5.3/archive/v0.7.tar.gz)
set(LUA_COMPAT53_SHA256 bec3a23114a3d9b3218038309657f0f506ad10dfbc03bb54e91da7e5ffdba0a2)
@@ -177,8 +178,8 @@ set(GPERF_SHA256 588546b945bba4b70b6a3a616e80b4ab466e3f33024a352fc2198112cdbb3ae
set(WINTOOLS_URL https://github.com/neovim/deps/raw/2f9acbecf06365c10baa3c0087f34a54c9c6f949/opt/win32tools.zip)
set(WINTOOLS_SHA256 8bfce7e3a365721a027ce842f2ec1cf878f1726233c215c05964aac07300798c)
-set(WINGUI_URL https://github.com/equalsraf/neovim-qt/releases/download/v0.2.14/neovim-qt.zip)
-set(WINGUI_SHA256 dfcb1f7d25d4907dc1d4f20edd71ff9eb4762196225106bec01274dd668fb04c)
+set(WINGUI_URL https://github.com/equalsraf/neovim-qt/releases/download/v0.2.16/neovim-qt.zip)
+set(WINGUI_SHA256 aad95a1f8413a9ebf36fc0298d0dfd7d786abf88cb0f4ae9f7ec895b70c7b312)
set(WIN32YANK_X86_URL https://github.com/equalsraf/win32yank/releases/download/v0.0.4/win32yank-x86.zip)
set(WIN32YANK_X86_SHA256 62f34e5a46c5d4a7b3f3b512e1ff7b77fedd432f42581cbe825233a996eed62c)
@@ -188,12 +189,15 @@ set(WIN32YANK_X86_64_SHA256 33a747a92da60fb65e668edbf7661d3d902411a2d545fe9dc086
set(WINPTY_URL https://github.com/rprichard/winpty/releases/download/0.4.3/winpty-0.4.3-msvc2015.zip)
set(WINPTY_SHA256 35a48ece2ff4acdcbc8299d4920de53eb86b1fb41e64d2fe5ae7898931bcee89)
-set(GETTEXT_URL https://ftp.gnu.org/pub/gnu/gettext/gettext-0.19.8.1.tar.gz)
-set(GETTEXT_SHA256 ff942af0e438ced4a8b0ea4b0b6e0d6d657157c5e2364de57baa279c1c125c43)
+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/6002fcd.tar.gz)
+set(TREESITTER_C_SHA256 46f8d44fa886d9ddb92571bb6fa8b175992c8758eca749cb1217464e512b6e97)
+
if(USE_BUNDLED_UNIBILIUM)
include(BuildUnibilium)
endif()
@@ -245,6 +249,10 @@ if(USE_BUNDLED_LIBICONV)
include(BuildLibiconv)
endif()
+if(USE_BUNDLED_TS_PARSERS)
+ include(BuildTreesitterParsers)
+endif()
+
if(WIN32)
include(GetBinaryDeps)
@@ -278,13 +286,21 @@ if(WIN32)
-P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/CopyFilesGlob.cmake)
endif()
-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}
-)
+# 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)
+endif()
add_custom_target(third-party ALL
COMMAND ${CMAKE_COMMAND} -E touch .third-party
- DEPENDS clean-shared-libraries)
+ DEPENDS ${ALL_DEPS}
+)
diff --git a/third-party/cmake/BuildGettext.cmake b/third-party/cmake/BuildGettext.cmake
index 45264167a5..9357456343 100644
--- a/third-party/cmake/BuildGettext.cmake
+++ b/third-party/cmake/BuildGettext.cmake
@@ -12,10 +12,6 @@ if(MSVC)
-DTARGET=gettext
-DUSE_EXISTING_SRC_DIR=${USE_EXISTING_SRC_DIR}
-P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/DownloadAndExtractFile.cmake
- PATCH_COMMAND ${GIT_EXECUTABLE} -C ${DEPS_BUILD_DIR}/src/gettext init
- COMMAND ${GIT_EXECUTABLE} -C ${DEPS_BUILD_DIR}/src/gettext apply --ignore-whitespace
- ${CMAKE_CURRENT_SOURCE_DIR}/patches/gettext-Fix-compilation-on-a-system-without-alloca.patch
- ${CMAKE_CURRENT_SOURCE_DIR}/patches/gettext-Fix-building-with-MSVC.patch
CONFIGURE_COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_CURRENT_SOURCE_DIR}/cmake/GettextCMakeLists.txt
${DEPS_BUILD_DIR}/src/gettext/CMakeLists.txt
diff --git a/third-party/cmake/BuildLibtermkey.cmake b/third-party/cmake/BuildLibtermkey.cmake
index b2332ed65a..10e98fbab3 100644
--- a/third-party/cmake/BuildLibtermkey.cmake
+++ b/third-party/cmake/BuildLibtermkey.cmake
@@ -48,6 +48,7 @@ ExternalProject_Add(libtermkey
PREFIX=${DEPS_INSTALL_DIR}
PKG_CONFIG_PATH=${DEPS_LIB_DIR}/pkgconfig
CFLAGS=-fPIC
+ LDFLAGS+=-static
${DEFAULT_MAKE_CFLAGS}
install)
endif()
diff --git a/third-party/cmake/BuildLibvterm.cmake b/third-party/cmake/BuildLibvterm.cmake
index e4649986af..c3485dac25 100644
--- a/third-party/cmake/BuildLibvterm.cmake
+++ b/third-party/cmake/BuildLibvterm.cmake
@@ -27,7 +27,6 @@ function(BuildLibvterm)
-DUSE_EXISTING_SRC_DIR=${USE_EXISTING_SRC_DIR}
-P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/DownloadAndExtractFile.cmake
PATCH_COMMAND "${_libvterm_PATCH_COMMAND}"
- CONFIGURE_COMMAND ""
BUILD_IN_SOURCE 1
CONFIGURE_COMMAND "${_libvterm_CONFIGURE_COMMAND}"
BUILD_COMMAND "${_libvterm_BUILD_COMMAND}"
@@ -58,6 +57,7 @@ else()
set(LIBVTERM_INSTALL_COMMAND ${MAKE_PRG} CC=${DEPS_C_COMPILER}
PREFIX=${DEPS_INSTALL_DIR}
CFLAGS=-fPIC
+ LDFLAGS+=-static
${DEFAULT_MAKE_CFLAGS}
install)
endif()
diff --git a/third-party/cmake/BuildLuajit.cmake b/third-party/cmake/BuildLuajit.cmake
index 791da4b7df..c0b24fb2a5 100644
--- a/third-party/cmake/BuildLuajit.cmake
+++ b/third-party/cmake/BuildLuajit.cmake
@@ -33,8 +33,21 @@ function(BuildLuajit)
BUILD_IN_SOURCE 1
BUILD_COMMAND "${_luajit_BUILD_COMMAND}"
INSTALL_COMMAND "${_luajit_INSTALL_COMMAND}")
+
+ # 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)
+ endif()
endfunction()
+check_c_compiler_flag(-fno-stack-check HAS_NO_STACK_CHECK)
+if(CMAKE_SYSTEM_NAME MATCHES "Darwin" AND HAS_NO_STACK_CHECK)
+ set(NO_STACK_CHECK "CFLAGS+=-fno-stack-check")
+else()
+ set(NO_STACK_CHECK "")
+endif()
if(CMAKE_SYSTEM_NAME MATCHES "OpenBSD")
set(AMD64_ABI "LDFLAGS=-lpthread -lc++abi")
else()
@@ -43,6 +56,7 @@ endif()
set(INSTALLCMD_UNIX ${MAKE_PRG} CFLAGS=-fPIC
CFLAGS+=-DLUA_USE_APICHECK
CFLAGS+=-DLUA_USE_ASSERT
+ ${NO_STACK_CHECK}
${AMD64_ABI}
CCDEBUG+=-g
Q=
@@ -107,8 +121,8 @@ elseif(MINGW)
# 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 make_directory ${DEPS_INSTALL_DIR}/include/luajit-2.0
- COMMAND ${CMAKE_COMMAND} -DFROM_GLOB=${DEPS_BUILD_DIR}/src/luajit/src/*.h -DTO=${DEPS_INSTALL_DIR}/include/luajit-2.0 -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/CopyFilesGlob.cmake
+ 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
)
elseif(MSVC)
@@ -122,8 +136,8 @@ elseif(MSVC)
COMMAND ${CMAKE_COMMAND} -E copy ${DEPS_BUILD_DIR}/src/luajit/src/lua51.lib ${DEPS_INSTALL_DIR}/lib/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 make_directory ${DEPS_INSTALL_DIR}/include/luajit-2.0
- COMMAND ${CMAKE_COMMAND} -DFROM_GLOB=${DEPS_BUILD_DIR}/src/luajit/src/*.h -DTO=${DEPS_INSTALL_DIR}/include/luajit-2.0 -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/CopyFilesGlob.cmake)
+ 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)
else()
message(FATAL_ERROR "Trying to build luajit in an unsupported system ${CMAKE_SYSTEM_NAME}/${CMAKE_C_COMPILER_ID}")
diff --git a/third-party/cmake/BuildLuarocks.cmake b/third-party/cmake/BuildLuarocks.cmake
index 87e2946c96..c5595bf840 100644
--- a/third-party/cmake/BuildLuarocks.cmake
+++ b/third-party/cmake/BuildLuarocks.cmake
@@ -52,13 +52,17 @@ if(NOT MSVC)
set(LUAROCKS_BUILDARGS CC=${HOSTDEPS_C_COMPILER} LD=${HOSTDEPS_C_COMPILER})
endif()
+# Lua version, used with rocks directories.
+# Defaults to 5.1 for bundled LuaJIT/Lua.
+set(LUA_VERSION "5.1")
+
if(UNIX OR (MINGW AND CMAKE_CROSSCOMPILING))
if(USE_BUNDLED_LUAJIT)
list(APPEND LUAROCKS_OPTS
--with-lua=${HOSTDEPS_INSTALL_DIR}
- --with-lua-include=${HOSTDEPS_INSTALL_DIR}/include/luajit-2.0
- --lua-suffix=jit)
+ --with-lua-include=${HOSTDEPS_INSTALL_DIR}/include/luajit-2.1
+ --with-lua-interpreter=luajit)
elseif(USE_BUNDLED_LUA)
list(APPEND LUAROCKS_OPTS
--with-lua=${HOSTDEPS_INSTALL_DIR})
@@ -66,9 +70,23 @@ if(UNIX OR (MINGW AND CMAKE_CROSSCOMPILING))
find_package(LuaJit)
if(LUAJIT_FOUND)
list(APPEND LUAROCKS_OPTS
- --lua-version=5.1
--with-lua-include=${LUAJIT_INCLUDE_DIRS}
- --lua-suffix=jit)
+ --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()
@@ -89,7 +107,7 @@ elseif(MSVC OR MINGW)
/LUA ${DEPS_INSTALL_DIR}
/LIB ${DEPS_LIB_DIR}
/BIN ${DEPS_BIN_DIR}
- /INC ${DEPS_INSTALL_DIR}/include/luajit-2.0
+ /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}
@@ -111,7 +129,7 @@ if(USE_BUNDLED_LUAJIT)
elseif(USE_BUNDLED_LUA)
add_dependencies(luarocks lua)
endif()
-set(ROCKS_DIR ${HOSTDEPS_LIB_DIR}/luarocks/rocks)
+set(ROCKS_DIR ${HOSTDEPS_LIB_DIR}/luarocks/rocks-${LUA_VERSION})
# mpack
add_custom_command(OUTPUT ${ROCKS_DIR}/mpack
@@ -199,10 +217,10 @@ if(USE_BUNDLED_BUSTED)
endif()
add_custom_target(luv DEPENDS ${ROCKS_DIR}/luv)
- # nvim-client
+ # nvim-client: https://github.com/neovim/lua-client
add_custom_command(OUTPUT ${ROCKS_DIR}/nvim-client
COMMAND ${LUAROCKS_BINARY}
- ARGS build nvim-client 0.2.0-1 ${LUAROCKS_BUILDARGS}
+ ARGS build nvim-client 0.2.2-1 ${LUAROCKS_BUILDARGS}
DEPENDS luv)
add_custom_target(nvim-client DEPENDS ${ROCKS_DIR}/nvim-client)
diff --git a/third-party/cmake/BuildLuv.cmake b/third-party/cmake/BuildLuv.cmake
index 967e0a1711..ab3e2190ab 100644
--- a/third-party/cmake/BuildLuv.cmake
+++ b/third-party/cmake/BuildLuv.cmake
@@ -55,7 +55,7 @@ endfunction()
set(LUV_SRC_DIR ${DEPS_BUILD_DIR}/src/luv)
set(LUV_INCLUDE_FLAGS
- "-I${DEPS_INSTALL_DIR}/include -I${DEPS_INSTALL_DIR}/include/luajit-2.0")
+ "-I${DEPS_INSTALL_DIR}/include -I${DEPS_INSTALL_DIR}/include/luajit-2.1")
# Replace luv default rockspec with the alternate one under the "rockspecs"
# directory
@@ -65,6 +65,7 @@ set(LUV_PATCH_COMMAND
set(LUV_CONFIGURE_COMMAND_COMMON
${CMAKE_COMMAND} ${LUV_SRC_DIR}
-DCMAKE_GENERATOR=${CMAKE_GENERATOR}
+ -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}
-DCMAKE_INSTALL_PREFIX=${DEPS_INSTALL_DIR}
-DLUA_BUILD_TYPE=System
-DWITH_SHARED_LIBUV=ON
@@ -87,7 +88,8 @@ endif()
if(USE_BUNDLED_LIBUV)
set(LUV_CONFIGURE_COMMAND_COMMON
${LUV_CONFIGURE_COMMAND_COMMON}
- -DCMAKE_PREFIX_PATH=${DEPS_INSTALL_DIR})
+ -DCMAKE_PREFIX_PATH=${DEPS_INSTALL_DIR}
+ -DLUA_COMPAT53_DIR=${DEPS_BUILD_DIR}/src/lua-compat-5.3)
endif()
if(MINGW AND CMAKE_CROSSCOMPILING)
@@ -106,28 +108,23 @@ elseif(MSVC)
# Same as Unix without fPIC
"-DCMAKE_C_FLAGS:STRING=${CMAKE_C_COMPILER_ARG1} ${LUV_INCLUDE_FLAGS}"
# Make sure we use the same generator, otherwise we may
- # accidentaly end up using different MSVC runtimes
- -DCMAKE_GENERATOR=${CMAKE_GENERATOR}
- # Use static runtime
- -DCMAKE_C_FLAGS_DEBUG="-MTd"
- -DCMAKE_C_FLAGS_RELEASE="-MT")
+ # accidentally end up using different MSVC runtimes
+ -DCMAKE_GENERATOR=${CMAKE_GENERATOR})
else()
set(LUV_CONFIGURE_COMMAND
${LUV_CONFIGURE_COMMAND_COMMON}
-DCMAKE_C_COMPILER=${CMAKE_C_COMPILER}
"-DCMAKE_C_FLAGS:STRING=${CMAKE_C_COMPILER_ARG1} ${LUV_INCLUDE_FLAGS} -fPIC")
+ if(CMAKE_GENERATOR MATCHES "Unix Makefiles" AND
+ (CMAKE_SYSTEM_NAME MATCHES ".*BSD" OR CMAKE_SYSTEM_NAME MATCHES "DragonFly"))
+ set(LUV_CONFIGURE_COMMAND ${LUV_CONFIGURE_COMMAND} -DCMAKE_MAKE_PROGRAM=gmake)
+ endif()
endif()
-if(CMAKE_GENERATOR MATCHES "Unix Makefiles" AND
- (CMAKE_SYSTEM_NAME MATCHES ".*BSD" OR CMAKE_SYSTEM_NAME MATCHES "DragonFly"))
- set(LUV_BUILD_COMMAND ${CMAKE_COMMAND}
- "-DLUA_COMPAT53_DIR=${DEPS_BUILD_DIR}/src/lua-compat-5.3"
- "-DCMAKE_MAKE_PROGRAM=gmake" --build .)
-else()
- set(LUV_BUILD_COMMAND ${CMAKE_COMMAND}
- "-DLUA_COMPAT53_DIR=${DEPS_BUILD_DIR}/src/lua-compat-5.3" --build .)
-endif()
-set(LUV_INSTALL_COMMAND ${CMAKE_COMMAND} --build . --target install)
+set(LUV_BUILD_COMMAND
+ ${CMAKE_COMMAND} --build . --config ${CMAKE_BUILD_TYPE})
+set(LUV_INSTALL_COMMAND
+ ${CMAKE_COMMAND} --build . --target install --config ${CMAKE_BUILD_TYPE})
BuildLuv(PATCH_COMMAND ${LUV_PATCH_COMMAND}
CONFIGURE_COMMAND ${LUV_CONFIGURE_COMMAND}
diff --git a/third-party/cmake/BuildMsgpack.cmake b/third-party/cmake/BuildMsgpack.cmake
index 616b6e5f83..30af5f060b 100644
--- a/third-party/cmake/BuildMsgpack.cmake
+++ b/third-party/cmake/BuildMsgpack.cmake
@@ -68,7 +68,7 @@ elseif(MSVC)
"-DCMAKE_C_FLAGS:STRING=${CMAKE_C_COMPILER_ARG1}"
-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}
# Make sure we use the same generator, otherwise we may
- # accidentaly end up using different MSVC runtimes
+ # accidentally end up using different MSVC runtimes
-DCMAKE_GENERATOR=${CMAKE_GENERATOR})
endif()
diff --git a/third-party/cmake/BuildTreesitterParsers.cmake b/third-party/cmake/BuildTreesitterParsers.cmake
new file mode 100644
index 0000000000..5284a7fd62
--- /dev/null
+++ b/third-party/cmake/BuildTreesitterParsers.cmake
@@ -0,0 +1,27 @@
+ExternalProject_Add(treesitter-c
+PREFIX ${DEPS_BUILD_DIR}
+URL ${TREESITTER_C_URL}
+DOWNLOAD_DIR ${DEPS_DOWNLOAD_DIR}/treesitter-c
+DOWNLOAD_COMMAND ${CMAKE_COMMAND}
+ -DPREFIX=${DEPS_BUILD_DIR}
+ -DDOWNLOAD_DIR=${DEPS_DOWNLOAD_DIR}/treesitter-c
+ -DURL=${TREESITTER_C_URL}
+ -DEXPECTED_SHA256=${TREESITTER_C_SHA256}
+ -DTARGET=treesitter-c
+ -DUSE_EXISTING_SRC_DIR=${USE_EXISTING_SRC_DIR}
+ -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/DownloadAndExtractFile.cmake
+PATCH_COMMAND ${CMAKE_COMMAND} -E copy
+ ${CMAKE_CURRENT_SOURCE_DIR}/cmake/TreesitterParserCMakeLists.txt
+ ${DEPS_BUILD_DIR}/src/treesitter-c/CMakeLists.txt
+CMAKE_ARGS
+ -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER}
+ -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}
+ -DCMAKE_GENERATOR=${CMAKE_GENERATOR}
+ -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}
+ -DCMAKE_INSTALL_PREFIX=${DEPS_INSTALL_DIR}
+ # Pass toolchain
+ -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}
+ -DPARSERLANG=c
+
+BUILD_COMMAND ${CMAKE_COMMAND} --build . --config ${CMAKE_BUILD_TYPE}
+INSTALL_COMMAND ${CMAKE_COMMAND} --build . --target install --config ${CMAKE_BUILD_TYPE})
diff --git a/third-party/cmake/BuildUnibilium.cmake b/third-party/cmake/BuildUnibilium.cmake
index e9deeb4987..74c1cbddb0 100644
--- a/third-party/cmake/BuildUnibilium.cmake
+++ b/third-party/cmake/BuildUnibilium.cmake
@@ -40,6 +40,7 @@ else()
BUILD_COMMAND ${MAKE_PRG} CC=${DEPS_C_COMPILER}
PREFIX=${DEPS_INSTALL_DIR}
CFLAGS=-fPIC
+ LDFLAGS+=-static
INSTALL_COMMAND ${MAKE_PRG} PREFIX=${DEPS_INSTALL_DIR} install)
endif()
diff --git a/third-party/cmake/DownloadAndExtractFile.cmake b/third-party/cmake/DownloadAndExtractFile.cmake
index bb8cf1b674..e008fa8a8a 100644
--- a/third-party/cmake/DownloadAndExtractFile.cmake
+++ b/third-party/cmake/DownloadAndExtractFile.cmake
@@ -76,7 +76,8 @@ list(GET status 1 status_string)
if(NOT status_code EQUAL 0)
# Retry on certain errors, e.g. CURLE_COULDNT_RESOLVE_HOST, which is often
# seen with libtermkey (www.leonerd.org.uk).
- if(status_code EQUAL 6) # "Couldn't resolve host name"
+ if((status_code EQUAL 6) # "Couldn't resolve host name"
+ OR (status_code EQUAL 7)) # "Couldn't connect to server"
message(STATUS "warning: retrying '${URL}' (${status_string}, status ${status_code})")
execute_process(COMMAND ${CMAKE_COMMAND} -E sleep 10)
file(DOWNLOAD ${URL} ${file}
diff --git a/third-party/cmake/GettextCMakeLists.txt b/third-party/cmake/GettextCMakeLists.txt
index 5a6253df3b..c3f78716d0 100644
--- a/third-party/cmake/GettextCMakeLists.txt
+++ b/third-party/cmake/GettextCMakeLists.txt
@@ -8,6 +8,10 @@ endmacro()
file(READ gettext-runtime/config.h.in CONFIG_CONTENT)
string(REPLACE "#undef HAVE_GETCWD" "#define HAVE_GETCWD 1" CONFIG_CONTENT ${CONFIG_CONTENT})
+string(REPLACE "#undef HAVE_LONG_LONG_INT" "#define HAVE_LONG_LONG_INT 1" CONFIG_CONTENT ${CONFIG_CONTENT})
+string(REPLACE "#undef HAVE_ICONV_H" "#define HAVE_ICONV_H 1" CONFIG_CONTENT ${CONFIG_CONTENT})
+string(REPLACE "#undef HAVE_ICONV" "#define HAVE_ICONV 1" CONFIG_CONTENT ${CONFIG_CONTENT})
+string(REPLACE "#undef ICONV_CONST" "#define ICONV_CONST const" CONFIG_CONTENT ${CONFIG_CONTENT})
string(REPLACE "#undef uintmax_t" "
#if _WIN64
# define intmax_t long long
@@ -24,6 +28,8 @@ set(HAVE_POSIX_PRINTF 0)
set(HAVE_SNPRINTF 0)
set(HAVE_ASPRINTF 0)
set(HAVE_WPRINTF 0)
+set(HAVE_NAMELESS_LOCALES 0)
+set(HAVE_LONG_LONG_INT 1)
configure_file(gettext-runtime/intl/libgnuintl.in.h
${CMAKE_CURRENT_BINARY_DIR}/gettext-runtime/intl/libgnuintl.h)
@@ -45,16 +51,20 @@ add_definitions(-DLOCALEDIR=\"${LOCALDIR}\"
set(libintl_SOURCES
bindtextdom.c dcgettext.c dcigettext.c dcngettext.c dgettext.c dngettext.c
- explodename.c finddomain.c gettext.c hash-string.c l10nflist.c langprefs.c
- loadmsgcat.c localcharset.c localealias.c localename.c lock.c log.c ngettext.c
+ explodename.c finddomain.c gettext.c hash-string.c intl-compat.c l10nflist.c
+ langprefs.c loadmsgcat.c localcharset.c localealias.c localename-table.c
+ localename.c lock.c log.c ngettext.c osdep.c
plural-exp.c plural.c printf.c relocatable.c setlocale.c textdomain.c
- threadlib.c version.c)
+ threadlib.c version.c xsize.c)
+
PREFIX_LIST_ITEMS(libintl_SOURCES "gettext-runtime/intl/")
add_library(libintl ${libintl_SOURCES})
+target_link_libraries(libintl ${LIBICONV_LIBRARIES})
set_property(TARGET libintl APPEND PROPERTY INCLUDE_DIRECTORIES
${CMAKE_CURRENT_BINARY_DIR}/gettext-runtime
- ${CMAKE_CURRENT_BINARY_DIR}/gettext-runtime/intl)
+ ${CMAKE_CURRENT_BINARY_DIR}/gettext-runtime/intl
+ ${LIBICONV_INCLUDE_DIRS})
set_property(TARGET libintl APPEND PROPERTY COMPILE_DEFINITIONS
BUILDING_LIBINTL
IN_LIBINTL
@@ -68,16 +78,22 @@ set_property(TARGET libintl APPEND PROPERTY COMPILE_DEFINITIONS
file(READ gettext-tools/config.h.in CONFIG_CONTENT)
+string(REPLACE "#undef FLEXIBLE_ARRAY_MEMBER" "#define FLEXIBLE_ARRAY_MEMBER 1" CONFIG_CONTENT ${CONFIG_CONTENT})
+string(REPLACE "__declspec (dllimport)" "" CONFIG_CONTENT ${CONFIG_CONTENT})
string(REPLACE "#undef ENDIANNESS" "#define ENDIANNESS 0" CONFIG_CONTENT ${CONFIG_CONTENT})
string(REPLACE "#undef GNULIB_FWRITEERROR" "#define GNULIB_FWRITEERROR 1" CONFIG_CONTENT ${CONFIG_CONTENT})
string(REPLACE "#undef HAVE_DECL_STRERROR_R" "#define HAVE_DECL_STRERROR_R 0" CONFIG_CONTENT ${CONFIG_CONTENT})
string(REPLACE "#undef HAVE_DUP2" "#define HAVE_DUP2 1" CONFIG_CONTENT ${CONFIG_CONTENT})
+string(REPLACE "#undef HAVE_ICONV_H" "#define HAVE_ICONV_H 1" CONFIG_CONTENT ${CONFIG_CONTENT})
+string(REPLACE "#undef HAVE_ICONV" "#define HAVE_ICONV 1" CONFIG_CONTENT ${CONFIG_CONTENT})
string(REPLACE "#undef HAVE_LIBUNISTRING" "#define HAVE_LIBUNISTRING 1" CONFIG_CONTENT ${CONFIG_CONTENT})
string(REPLACE "#undef HAVE_STDINT_H_WITH_UINTMAX" "#define HAVE_STDINT_H_WITH_UINTMAX 1" CONFIG_CONTENT ${CONFIG_CONTENT})
string(REPLACE "#undef HAVE_STDINT_H" "#define HAVE_STDINT_H 1" CONFIG_CONTENT ${CONFIG_CONTENT})
+string(REPLACE "#undef HAVE_LONG_LONG_INT" "#define HAVE_LONG_LONG_INT 1" CONFIG_CONTENT ${CONFIG_CONTENT})
string(REPLACE "#undef HAVE_STRING_H" "#define HAVE_STRING_H 1" CONFIG_CONTENT ${CONFIG_CONTENT})
string(REPLACE "#undef HAVE_SYS_TIMEB_H" "#define HAVE_SYS_TIMEB_H 1" CONFIG_CONTENT ${CONFIG_CONTENT})
string(REPLACE "#undef HAVE__FTIME" "#define HAVE__FTIME 1" CONFIG_CONTENT ${CONFIG_CONTENT})
+string(REPLACE "#undef HAVE_FLOAT_H" "#define HAVE_FLOAT_H 1" CONFIG_CONTENT ${CONFIG_CONTENT})
string(REPLACE "#undef ICONV_CONST" "#define ICONV_CONST const" CONFIG_CONTENT ${CONFIG_CONTENT})
string(REPLACE "#undef PACKAGE" "#define PACKAGE \"gettext\"\n#define gettext_VERSION" CONFIG_CONTENT ${CONFIG_CONTENT})
string(REPLACE "#undef VERSION" "#define VERSION \"\"" CONFIG_CONTENT ${CONFIG_CONTENT})
@@ -86,13 +102,14 @@ string(REPLACE "#undef pid_t" "#define pid_t int" CONFIG_CONTENT ${CONFIG_CONTEN
string(REPLACE "#undef restrict" "#define restrict __restrict" CONFIG_CONTENT ${CONFIG_CONTENT})
string(REPLACE "#undef ssize_t" "#include <BaseTsd.h>\n#define ssize_t SSIZE_T" CONFIG_CONTENT ${CONFIG_CONTENT})
string(REPLACE "#undef uid_t" "#define uid_t int" CONFIG_CONTENT ${CONFIG_CONTENT})
+string(REPLACE "#undef HAVE_DECL___ARGV" "#define HAVE_DECL___ARGV 1" CONFIG_CONTENT ${CONFIG_CONTENT})
+set(CONFIG_CONTENT "${CONFIG_CONTENT}\n#define isatty libtextstyle_isatty")
file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/gettext-tools/config.h ${CONFIG_CONTENT})
-set(libgettextsrc_COMMON_SOURCE
+set(libgettextsrc_COMMON_SOURCE
message.c po-error.c po-xerror.c read-catalog-abstract.c po-lex.c
po-gram-gen.c po-charset.c read-po.c read-properties.c read-stringtable.c
- open-catalog.c dir-list.c
- str-list.c)
+ open-catalog.c dir-list.c str-list.c)
set(libgettextsrc_FORMAT_SOURCE
format.c format-invalid.h format-c.c format-c-parse.h format-sh.c
@@ -104,11 +121,12 @@ set(libgettextsrc_FORMAT_SOURCE
format-javascript.c)
set(libgettextsrc_SOURCES
- ${libgettextsrc_COMMON_SOURCE} read-catalog.c color.c write-catalog.c
- write-properties.c write-stringtable.c write-po.c msgl-ascii.c msgl-iconv.c
- msgl-equal.c msgl-cat.c msgl-header.c msgl-english.c msgl-check.c file-list.c
- msgl-charset.c po-time.c plural-exp.c plural-eval.c plural-count.c
- plural-table.c quote.h sentence.h sentence.c ${libgettextsrc_FORMAT_SOURCE}
+ ${libgettextsrc_COMMON_SOURCE} read-catalog.c
+ write-catalog.c write-properties.c write-stringtable.c write-po.c
+ msgl-ascii.c msgl-iconv.c msgl-equal.c msgl-cat.c msgl-header.c msgl-english.c
+ msgl-check.c file-list.c msgl-charset.c po-time.c plural-exp.c plural-eval.c
+ plural-table.c quote.h sentence.h sentence.c
+ ${libgettextsrc_FORMAT_SOURCE}
read-desktop.c locating-rule.c its.c search-path.c)
PREFIX_LIST_ITEMS(libgettextsrc_SOURCES "gettext-tools/src/")
@@ -118,101 +136,141 @@ set(GLIBC_SOURCE
gettimeofday.c getdtablesize.c fcntl.c dup-safer-flag.c cloexec.c
fd-safer-flag.c fd-safer.c pipe2.c pipe2-safer.c spawn-pipe.c xmemdup0.c
secure_getenv.c tmpdir.c tempname.c mkdtemp.c fnmatch.c clean-temp.c
- gl_array_list.c tputs.c wait-process.c waitpid.c getdelim.c getline.c
- sigprocmask.c sigaction.c addext.c argmatch.c backupfile.c basename.c
- c-strcasecmp.c c-strncasecmp.c c-strstr.c closeout.c concat-filename.c
- error-progname.c error.c exitfail.c file-ostream.c fstrcmp.c full-write.c
- fwriteerror.c getopt.c getopt1.c hash.c libxml/buf.c localcharset.c malloca.c
- mbchar.c mbslen.c mbsstr.c mbswidth.c obstack.c ostream.c html-ostream.c
- fd-ostream.c styled-ostream.c progname.c html-styled-ostream.c printf-args.c
- printf-parse.c propername.c quotearg.c rawmemchr.c safe-read.c safe-write.c
- stpcpy.c stpncpy.c strchrnul.c striconveh.c striconveha.c strnlen1.c
- term-ostream.c term-styled-ostream.c tparm.c trim.c gcd.c gl_linkedhash_list.c
+ wait-process.c waitpid.c getdelim.c getline.c sigprocmask.c sigaction.c
+ addext.c argmatch.c backupfile.c basename.c c-strcasecmp.c c-strncasecmp.c
+ c-strstr.c closeout.c concat-filename.c error-progname.c error.c exitfail.c
+ fstrcmp.c full-write.c fwriteerror.c getopt.c getopt1.c hash.c libxml/buf.c
+ localcharset.c malloca.c mbchar.c mbslen.c mbsstr.c mbswidth.c obstack.c
+ progname.c printf-args.c printf-parse.c propername.c quotearg.c rawmemchr.c
+ safe-read.c safe-write.c stpcpy.c stpncpy.c strchrnul.c striconv.c
+ striconveh.c striconveha.c strnlen1.c trim.c gcd.c gl_linkedhash_list.c
uniconv/u8-conv-from-enc.c unictype/ctype_space.c unilbrk/lbrktables.c
unilbrk/u8-possible-linebreaks.c unilbrk/u8-width-linebreaks.c
unilbrk/ulc-common.c unilbrk/ulc-width-linebreaks.c unistr/u16-mbtouc-aux.c
- unistr/u16-mbtouc.c unistr/u8-check.c unistr/u8-mblen.c unistr/u8-mbtouc-aux.c
- unistr/u8-mbtouc-unsafe-aux.c unistr/u8-mbtouc-unsafe.c unistr/u8-mbtouc.c
- unistr/u8-mbtoucr.c unistr/u8-prev.c unistr/u8-uctomb-aux.c unistr/u8-uctomb.c
- uniwidth/width.c vasnprintf.c vasprintf.c wcwidth.c xasprintf.c
- xconcat-filename.c xerror.c xmalloc.c xstrdup.c xvasprintf.c glib/ghash.c
- glib/glist.c glib/gmessages.c glib/gprimes.c glib/gstrfuncs.c glib/gstring.c
- libcroco/cr-additional-sel.c libcroco/cr-attr-sel.c libcroco/cr-cascade.c
- libcroco/cr-declaration.c libcroco/cr-doc-handler.c libcroco/cr-enc-handler.c
- libcroco/cr-fonts.c libcroco/cr-input.c libcroco/cr-num.c
- libcroco/cr-om-parser.c libcroco/cr-parser.c libcroco/cr-parsing-location.c
- libcroco/cr-prop-list.c libcroco/cr-pseudo.c libcroco/cr-rgb.c
- libcroco/cr-sel-eng.c libcroco/cr-selector.c libcroco/cr-simple-sel.c
- libcroco/cr-statement.c libcroco/cr-string.c libcroco/cr-style.c
- libcroco/cr-stylesheet.c libcroco/cr-term.c libcroco/cr-tknzr.c
- libcroco/cr-token.c libcroco/cr-utils.c libxml/DOCBparser.c
- libxml/HTMLparser.c libxml/HTMLtree.c libxml/SAX.c libxml/SAX2.c libxml/c14n.c
- libxml/catalog.c libxml/chvalid.c libxml/debugXML.c libxml/dict.c
- libxml/encoding.c libxml/entities.c libxml/error.c libxml/globals.c
- libxml/hash.c libxml/legacy.c libxml/list.c libxml/nanoftp.c libxml/nanohttp.c
- libxml/parser.c libxml/parserInternals.c libxml/pattern.c libxml/relaxng.c
- libxml/schematron.c libxml/threads.c libxml/tree.c libxml/trionan.c
- libxml/uri.c libxml/valid.c libxml/xinclude.c libxml/xlink.c libxml/xmlIO.c
- libxml/xmlmemory.c libxml/xmlmodule.c libxml/xmlreader.c libxml/xmlregexp.c
- libxml/xmlsave.c libxml/xmlschemas.c libxml/xmlschemastypes.c
- libxml/xmlstring.c libxml/xmlunicode.c libxml/xmlwriter.c libxml/xpath.c
- libxml/xpointer.c fatal-signal.c copy-file.c)
+ unistr/u16-mbtouc.c unistr/u8-check.c unistr/u8-mblen.c
+ unistr/u8-mbtouc-aux.c unistr/u8-mbtouc-unsafe-aux.c
+ unistr/u8-mbtouc-unsafe.c unistr/u8-mbtouc.c unistr/u8-mbtoucr.c
+ unistr/u8-prev.c unistr/u8-uctomb-aux.c unistr/u8-uctomb.c uniwidth/width.c
+ vasnprintf.c vasprintf.c wcwidth.c xasprintf.c xconcat-filename.c xerror.c
+ xmalloc.c xstrdup.c xstriconv.c xstriconveh.c xvasprintf.c
+ libxml/DOCBparser.c libxml/HTMLparser.c libxml/HTMLtree.c libxml/SAX.c
+ libxml/SAX2.c libxml/c14n.c libxml/catalog.c libxml/chvalid.c
+ libxml/debugXML.c libxml/dict.c libxml/encoding.c libxml/entities.c
+ libxml/error.c libxml/globals.c libxml/hash.c libxml/legacy.c libxml/list.c
+ libxml/nanoftp.c libxml/nanohttp.c libxml/parser.c libxml/parserInternals.c
+ libxml/pattern.c libxml/relaxng.c libxml/schematron.c libxml/threads.c
+ libxml/tree.c libxml/trionan.c libxml/uri.c libxml/valid.c libxml/xinclude.c
+ libxml/xlink.c libxml/xmlIO.c libxml/xmlmemory.c libxml/xmlmodule.c
+ libxml/xmlreader.c libxml/xmlregexp.c libxml/xmlsave.c libxml/xmlschemas.c
+ libxml/xmlschemastypes.c libxml/xmlstring.c libxml/xmlunicode.c
+ libxml/xmlwriter.c libxml/xpath.c libxml/xpointer.c fatal-signal.c
+ copy-file.c read-file.c ftello.c utime.c gettime.c utimens.c)
PREFIX_LIST_ITEMS(GLIBC_SOURCE "gettext-tools/gnulib-lib/")
-set(libgettextsrc_SOURCES ${libgettextsrc_SOURCES} ${GLIBC_SOURCE})
-
-set(HEADER_TEMPLATES_PATH "gettext-tools/gnulib-lib")
-set(HEADER_TEMPLATES_ABS_PATH "${CMAKE_CURRENT_SOURCE_DIR}/${HEADER_TEMPLATES_PATH}")
-file(GLOB_RECURSE HEADER_TEMPLATES "${HEADER_TEMPLATES_ABS_PATH}/*.in.h")
-list(REMOVE_ITEM HEADER_TEMPLATES "${HEADER_TEMPLATES_ABS_PATH}/stdint.in.h")
-list(REMOVE_ITEM HEADER_TEMPLATES "${HEADER_TEMPLATES_ABS_PATH}/wchar.in.h")
-foreach(HEADER_TEMPLATE ${HEADER_TEMPLATES})
- file(READ ${HEADER_TEMPLATE} HEADER_CONTENT)
- string(REPLACE "/* The definition of _GL_ARG_NONNULL is copied here. */" "#include \"arg-nonnull.h\"" HEADER_CONTENT "${HEADER_CONTENT}")
- string(REPLACE "/* The definition of _GL_WARN_ON_USE is copied here. */" "#include \"warn-on-use.h\"" HEADER_CONTENT "${HEADER_CONTENT}")
- string(REPLACE "/* The definitions of _GL_FUNCDECL_RPL etc. are copied here. */" "#include \"c++defs.h\"" HEADER_CONTENT "${HEADER_CONTENT}")
- string(REPLACE "@GNULIB_LSTAT@" "1" HEADER_CONTENT "${HEADER_CONTENT}")
- string(REPLACE "@GNULIB_MBSINIT@" "1" HEADER_CONTENT "${HEADER_CONTENT}")
- string(REPLACE "@GNULIB_SIGACTION@" "1" HEADER_CONTENT "${HEADER_CONTENT}")
- string(REPLACE "@GNULIB_SIGPROCMASK@" "1" HEADER_CONTENT "${HEADER_CONTENT}")
- string(REPLACE "@GNULIB_STPCPY@" "1" HEADER_CONTENT "${HEADER_CONTENT}")
- string(REPLACE "@GNULIB_STPNCPY@" "1" HEADER_CONTENT "${HEADER_CONTENT}")
- string(REPLACE "@GNULIB_STRCHRNUL@" "1" HEADER_CONTENT "${HEADER_CONTENT}")
- string(REPLACE "@HAVE_ISWCNTRL@" "1" HEADER_CONTENT "${HEADER_CONTENT}")
- string(REPLACE "@HAVE_WCTYPE_T@" "1" HEADER_CONTENT "${HEADER_CONTENT}")
- string(REPLACE "@PRAGMA_COLUMNS@" "" HEADER_CONTENT "${HEADER_CONTENT}")
-
- string(REGEX REPLACE "^${HEADER_TEMPLATES_ABS_PATH}/" "" HEADER_PATH "${HEADER_TEMPLATE}")
- string(REPLACE ".in" "" HEADER_PATH ${HEADER_PATH})
- string(REPLACE "_" "/" HEADER_PATH ${HEADER_PATH})
- # find_file will create a cache entry for the variable
- # SYSTEM_HEADER, so reset it before each call
- set(SYSTEM_HEADER "SYSTEM_HEADER-NOTFOUND")
- find_file(SYSTEM_HEADER ${HEADER_PATH} PATHS "${LIBICONV_INCLUDE_DIRS}")
- if(SYSTEM_HEADER)
- # Gnulib uses #include_next to extend system header files,
- # but MSVC doesn't support it, so a regular include directive
- # with a relative path is used instead
- string(REGEX REPLACE ".*/(.*/${HEADER_PATH})" "../\\1"
- INCLUDE_PATH "${SYSTEM_HEADER}")
- string(REGEX REPLACE "@INCLUDE_NEXT[^@]*@ @NEXT_[^@\n]+@"
- "include <${INCLUDE_PATH}>" HEADER_CONTENT "${HEADER_CONTENT}")
- endif()
-
- # Default any remaining template variables to 0
- string(REGEX REPLACE "@[^@\n]+@" "0" HEADER_CONTENT "${HEADER_CONTENT}")
-
- file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/${HEADER_TEMPLATES_PATH}/${HEADER_PATH}" "${HEADER_CONTENT}")
-endforeach()
+file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/gettext-tools/gnulib-lib/configmake.h "#define PKGDATADIR \"gettext\"")
+
+set(LIBGLIB_SOURCES
+ ghash.c glist.c gmessages.c gprimes.c gstrfuncs.c gstring.c)
+PREFIX_LIST_ITEMS(LIBGLIB_SOURCES "libtextstyle/lib/glib/")
+
+set(LIBTEXTSTYLE_SOURCE
+ gl_array_list.h gl_array_list.c binary-io.h
+ binary-io.c c-ctype.h c-ctype.c c-strcase.h c-strcasecmp.c
+ c-strncasecmp.c concat-filename.c dirname-lgpl.c
+ basename-lgpl.c stripslash.c exitfail.c fatal-signal.h
+ fatal-signal.c fd-hook.c fd-ostream.c file-ostream.c
+ full-write.h full-write.c getprogname.h getprogname.c
+ gettext.h hash.h hash.c html-ostream.c html-styled-ostream.c
+ iconv-ostream.c gl_list.h gl_list.c math.c memory-ostream.c
+ minmax.h noop-styled-ostream.c ostream.c safe-read.c
+ safe-write.c sig-handler.c size_max.h styled-ostream.c
+ term-ostream.c term-style-control.c term-styled-ostream.c
+ unistd.c xalloc.h xmalloc.c xstrdup.c
+ xconcat-filename.c gl_xlist.h gl_xlist.c xsize.h xsize.c
+ xvasprintf.h xvasprintf.c xasprintf.c color.h color.c misc.h
+ misc.c version.c isatty.c fsync.c tparm.c tputs.c)
+PREFIX_LIST_ITEMS(LIBTEXTSTYLE_SOURCE "libtextstyle/lib/")
+
+configure_file(
+ libtextstyle/lib/stdbool.mini.h
+ ${CMAKE_CURRENT_BINARY_DIR}/libtextstyle/lib/textstyle/stdbool.h
+ COPYONLY)
+
+set(LIBCROCO_SOURCES
+ cr-additional-sel.c cr-attr-sel.c cr-cascade.c cr-declaration.c
+ cr-doc-handler.c cr-enc-handler.c cr-fonts.c cr-input.c cr-num.c
+ cr-om-parser.c cr-parser.c cr-parsing-location.c cr-prop-list.c cr-pseudo.c
+ cr-rgb.c cr-sel-eng.c cr-selector.c cr-simple-sel.c cr-statement.c
+ cr-string.c cr-style.c cr-stylesheet.c cr-term.c cr-tknzr.c cr-token.c
+ cr-utils.c)
+PREFIX_LIST_ITEMS(LIBCROCO_SOURCES "libtextstyle/lib/libcroco/")
+
+set(libgettextsrc_SOURCES
+ ${libgettextsrc_SOURCES} ${GLIBC_SOURCE} ${LIBGLIB_SOURCES}
+ ${LIBTEXTSTYLE_SOURCE} ${LIBCROCO_SOURCES})
+
+macro(CONFIGURE_HEADER_FILES HEADER_TEMPLATES_PATH)
+ set(HEADER_TEMPLATES_ABS_PATH "${CMAKE_CURRENT_SOURCE_DIR}/${HEADER_TEMPLATES_PATH}")
+ file(GLOB_RECURSE HEADER_TEMPLATES "${HEADER_TEMPLATES_ABS_PATH}/*.in.h")
+ list(REMOVE_ITEM HEADER_TEMPLATES "${HEADER_TEMPLATES_ABS_PATH}/stdint.in.h")
+ list(REMOVE_ITEM HEADER_TEMPLATES "${HEADER_TEMPLATES_ABS_PATH}/wchar.in.h")
+ foreach(HEADER_TEMPLATE ${HEADER_TEMPLATES})
+ file(READ ${HEADER_TEMPLATE} HEADER_CONTENT)
+ string(REPLACE "/* The definition of _GL_ARG_NONNULL is copied here. */" "#include \"arg-nonnull.h\"" HEADER_CONTENT "${HEADER_CONTENT}")
+ string(REPLACE "/* The definition of _GL_WARN_ON_USE is copied here. */" "#include \"warn-on-use.h\"" HEADER_CONTENT "${HEADER_CONTENT}")
+ string(REPLACE "/* The definitions of _GL_FUNCDECL_RPL etc. are copied here. */" "#include \"c++defs.h\"" HEADER_CONTENT "${HEADER_CONTENT}")
+ string(REPLACE "@GNULIB_LSTAT@" "1" HEADER_CONTENT "${HEADER_CONTENT}")
+ string(REPLACE "@GNULIB_MBSINIT@" "1" HEADER_CONTENT "${HEADER_CONTENT}")
+ string(REPLACE "@GNULIB_SIGACTION@" "1" HEADER_CONTENT "${HEADER_CONTENT}")
+ string(REPLACE "@GNULIB_SIGPROCMASK@" "1" HEADER_CONTENT "${HEADER_CONTENT}")
+ string(REPLACE "@GNULIB_STPCPY@" "1" HEADER_CONTENT "${HEADER_CONTENT}")
+ string(REPLACE "@GNULIB_STPNCPY@" "1" HEADER_CONTENT "${HEADER_CONTENT}")
+ string(REPLACE "@GNULIB_STRCHRNUL@" "1" HEADER_CONTENT "${HEADER_CONTENT}")
+ string(REPLACE "@HAVE_ISWCNTRL@" "1" HEADER_CONTENT "${HEADER_CONTENT}")
+ string(REPLACE "@HAVE_WCTYPE_T@" "1" HEADER_CONTENT "${HEADER_CONTENT}")
+ string(REPLACE "@HAVE_STRUCT_TIMEVAL@" "1" HEADER_CONTENT "${HEADER_CONTENT}")
+ string(REPLACE "@HAVE_WINSOCK2_H@" "1" HEADER_CONTENT "${HEADER_CONTENT}")
+ string(REPLACE "@DLL_VARIABLE@" "" HEADER_CONTENT "${HEADER_CONTENT}")
+ string(REPLACE "@HAVE_NEWLOCALE@" "0" HEADER_CONTENT "${HEADER_CONTENT}")
+ string(REPLACE "@PRAGMA_COLUMNS@" "" HEADER_CONTENT "${HEADER_CONTENT}")
+ string(REPLACE "#if @GNULIB_UTIME@" "#if 1\n#define utime gl_utime" HEADER_CONTENT "${HEADER_CONTENT}")
+ string(REPLACE "@HAVE_UTIME@" "1" HEADER_CONTENT "${HEADER_CONTENT}")
+ string(REPLACE "@HAVE_LONG_LONG_INT@" "1" HEADER_CONTENT "${HEADER_CONTENT}")
+
+ string(REGEX REPLACE "^${HEADER_TEMPLATES_ABS_PATH}/" "" HEADER_PATH "${HEADER_TEMPLATE}")
+ string(REPLACE ".in" "" HEADER_PATH ${HEADER_PATH})
+ string(REPLACE "_" "/" HEADER_PATH ${HEADER_PATH})
+ # find_file will create a cache entry for the variable
+ # SYSTEM_HEADER, so reset it before each call
+ set(SYSTEM_HEADER "SYSTEM_HEADER-NOTFOUND")
+ find_file(SYSTEM_HEADER ${HEADER_PATH} PATHS "${LIBICONV_INCLUDE_DIRS}")
+ if(SYSTEM_HEADER)
+ # Gnulib uses #include_next to extend system header files,
+ # but MSVC doesn't support it, so a regular include directive
+ # with a relative path is used instead
+ string(REGEX REPLACE ".*/(.*/${HEADER_PATH})" "../\\1"
+ INCLUDE_PATH "${SYSTEM_HEADER}")
+ string(REGEX REPLACE "@INCLUDE_NEXT[^@]*@ @NEXT_[^@\n]+@"
+ "include <${INCLUDE_PATH}>" HEADER_CONTENT "${HEADER_CONTENT}")
+ endif()
+
+ # Default any remaining template variables to 0
+ string(REGEX REPLACE "@[^@\n]+@" "0" HEADER_CONTENT "${HEADER_CONTENT}")
+
+ file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/${HEADER_TEMPLATES_PATH}/${HEADER_PATH}" "${HEADER_CONTENT}")
+ endforeach()
+endmacro()
+
+CONFIGURE_HEADER_FILES("gettext-tools/gnulib-lib")
+CONFIGURE_HEADER_FILES("libtextstyle/lib")
add_library(libgettextsrc ${libgettextsrc_SOURCES})
target_link_libraries(libgettextsrc ${LIBICONV_LIBRARIES})
+set_property(TARGET libgettextsrc APPEND PROPERTY COMPILE_DEFINITIONS
+ LIBTEXTSTYLE_DLL_VARIABLE=)
set(msgmerge_SOURCES
- msgmerge.c
- msgl-fsearch.c
- lang-table.c
- )
+ msgmerge.c msgl-fsearch.c lang-table.c plural-count.c)
PREFIX_LIST_ITEMS(msgmerge_SOURCES "gettext-tools/src/")
add_executable(msgmerge ${msgmerge_SOURCES})
@@ -220,25 +278,21 @@ target_link_libraries(msgmerge libgettextsrc)
add_dependencies(msgmerge libgettextsrc libintl)
set(msgfmt_SOURCES
- msgfmt.c
- write-mo.c
- write-java.c
- write-csharp.c
- write-resources.c
- write-tcl.c
- write-qt.c
- write-desktop.c
- write-xml.c)
+ msgfmt.c write-mo.c write-java.c write-csharp.c write-resources.c write-tcl.c
+ write-qt.c write-desktop.c write-xml.c
+ ../../gettext-runtime/intl/hash-string.c)
PREFIX_LIST_ITEMS(msgfmt_SOURCES "gettext-tools/src/")
-add_executable(msgfmt ${msgfmt_SOURCES} gettext-runtime/intl/hash-string.c)
+add_executable(msgfmt ${msgfmt_SOURCES})
target_link_libraries(msgfmt libgettextsrc)
add_dependencies(msgfmt libgettextsrc libintl)
set(xgettext_SOURCES
- xgettext.c x-c.c x-po.c x-sh.c x-python.c x-lisp.c x-elisp.c x-librep.c
- x-scheme.c x-smalltalk.c x-java.c x-csharp.c x-awk.c x-ycp.c x-tcl.c x-perl.c
- x-php.c x-rst.c x-lua.c x-javascript.c x-vala.c x-desktop.c)
+ xgettext.c xg-pos.c xg-encoding.c xg-mixed-string.c xg-arglist-context.c
+ xg-arglist-callshape.c xg-arglist-parser.c xg-message.c x-c.c x-po.c x-sh.c
+ x-python.c x-lisp.c x-elisp.c x-librep.c x-scheme.c x-smalltalk.c x-java.c
+ x-csharp.c x-awk.c x-ycp.c x-tcl.c x-perl.c x-php.c x-rst.c x-lua.c
+ x-javascript.c x-vala.c x-desktop.c)
PREFIX_LIST_ITEMS(xgettext_SOURCES "gettext-tools/src/")
add_executable(xgettext ${xgettext_SOURCES})
@@ -252,9 +306,13 @@ set_property(TARGET msgmerge msgfmt xgettext libgettextsrc APPEND PROPERTY
${CMAKE_CURRENT_SOURCE_DIR}/gettext-tools/gnulib-lib
${CMAKE_CURRENT_SOURCE_DIR}/gettext-tools/gnulib-lib/libcroco
${CMAKE_CURRENT_SOURCE_DIR}/build-aux/snippet
+ ${CMAKE_CURRENT_SOURCE_DIR}/libtextstyle/lib
+ ${CMAKE_CURRENT_SOURCE_DIR}/libtextstyle/lib/libcroco
${CMAKE_CURRENT_BINARY_DIR}/gettext-runtime/intl
${CMAKE_CURRENT_BINARY_DIR}/gettext-tools
${CMAKE_CURRENT_BINARY_DIR}/gettext-tools/gnulib-lib
+ ${CMAKE_CURRENT_BINARY_DIR}/libtextstyle/lib
+ ${CMAKE_CURRENT_BINARY_DIR}/libtextstyle/lib/textstyle
${LIBICONV_INCLUDE_DIRS})
include(GNUInstallDirs)
diff --git a/third-party/cmake/TreesitterParserCMakeLists.txt b/third-party/cmake/TreesitterParserCMakeLists.txt
new file mode 100644
index 0000000000..2808a9ee14
--- /dev/null
+++ b/third-party/cmake/TreesitterParserCMakeLists.txt
@@ -0,0 +1,19 @@
+cmake_minimum_required(VERSION 2.8.12)
+# some parsers have c++ scanner, problem?
+project(parser C) # CXX
+
+add_library(parser
+ MODULE
+ src/parser.c
+)
+set_target_properties(
+ parser
+ PROPERTIES
+ POSITION_INDEPENDENT_CODE ON
+ OUTPUT_NAME ${PARSERLANG}
+ PREFIX ""
+)
+
+include_directories(src)
+
+install(TARGETS parser LIBRARY DESTINATION lib/nvim/parser)
diff --git a/third-party/patches/gettext-Fix-building-with-MSVC.patch b/third-party/patches/gettext-Fix-building-with-MSVC.patch
deleted file mode 100644
index d15901bce3..0000000000
--- a/third-party/patches/gettext-Fix-building-with-MSVC.patch
+++ /dev/null
@@ -1,50 +0,0 @@
-diff --git a/gettext-tools/config.h.in b/gettext-tools/config.h.in
-index 6818a4d..9842a71 100644
---- a/gettext-tools/config.h.in
-+++ b/gettext-tools/config.h.in
-@@ -3147,7 +3147,7 @@
- #define PAGE_WIDTH 79
-
- /* On Windows, variables that may be in a DLL must be marked specially. */
--#if ((defined _MSC_VER && defined _DLL) || defined WOE32DLL) && !defined IN_RELOCWRAPPER
-+#if ((defined _MSC_VER && defined DLL_IMPORT) || defined WOE32DLL) && !defined IN_RELOCWRAPPER
- # define DLL_VARIABLE __declspec (dllimport)
- #else
- # define DLL_VARIABLE
-diff --git a/gettext-tools/gnulib-lib/javaversion.c b/gettext-tools/gnulib-lib/javaversion.c
-index d760c32..4867fda 100644
---- a/gettext-tools/gnulib-lib/javaversion.c
-+++ b/gettext-tools/gnulib-lib/javaversion.c
-@@ -39,7 +39,7 @@
- #define _(str) gettext (str)
-
- /* Get PKGDATADIR. */
--#include "configmake.h"
-+#define PKGDATADIR ""
-
-
- struct locals
-diff --git a/gettext-tools/libgettextpo/xalloc.h b/gettext-tools/libgettextpo/xalloc.h
-index f4a329e..a38dcf1 100644
---- a/gettext-tools/libgettextpo/xalloc.h
-+++ b/gettext-tools/libgettextpo/xalloc.h
-@@ -60,7 +60,7 @@ extern "C" {
- in charge of honoring the three previous items. This is the
- function to call when one wants the program to die because of a
- memory allocation failure. */
--extern void xalloc_die (void)
-+extern _Noreturn void xalloc_die (void)
- #if (__GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 5)) && !__STRICT_ANSI__
- __attribute__ ((__noreturn__))
- #endif
-diff --git a/gettext-tools/src/plural-exp.c b/gettext-tools/src/plural-exp.c
-index d5b9deb..e2c6bc4 100644
---- a/gettext-tools/src/plural-exp.c
-+++ b/gettext-tools/src/plural-exp.c
-@@ -17,5 +17,5 @@
-
- /* Include the expression parsing code from libintl, with different function
- names. */
--#include "../intl/pluralx.c"
-+#include "../intl/plural.c"
- #include "../../gettext-runtime/intl/plural-exp.c"
diff --git a/third-party/patches/gettext-Fix-compilation-on-a-system-without-alloca.patch b/third-party/patches/gettext-Fix-compilation-on-a-system-without-alloca.patch
deleted file mode 100644
index 5c472c470f..0000000000
--- a/third-party/patches/gettext-Fix-compilation-on-a-system-without-alloca.patch
+++ /dev/null
@@ -1,28 +0,0 @@
-From 1d12aeb7334104f77070361492ff7cc8225503f5 Mon Sep 17 00:00:00 2001
-From: Daiki Ueno <ueno@gnu.org>
-Date: Mon, 14 Nov 2016 13:27:58 +0100
-Subject: [PATCH] intl: Fix compilation on a system without alloca
-
-* gettext-runtime/intl/dcigettext.c (DCIGETTEXT): Fix typo 'tmp_dirname'
--> 'resolved_dirname'. Reported by Egor Pugin in:
-http://lists.gnu.org/archive/html/bug-gettext/2016-09/msg00008.html
----
- gettext-runtime/intl/dcigettext.c | 2 +-
- 1 file changed, 1 insertion(+), 1 deletion(-)
-
-diff --git a/gettext-runtime/intl/dcigettext.c b/gettext-runtime/intl/dcigettext.c
-index 83bd77574..92f6fd685 100644
---- a/gettext-runtime/intl/dcigettext.c
-+++ b/gettext-runtime/intl/dcigettext.c
-@@ -634,7 +634,7 @@ DCIGETTEXT (const char *domainname, const char *msgid1, const char *msgid2,
- for (;;)
- {
- resolved_dirname = (char *) alloca (path_max + dirname_len);
-- ADD_BLOCK (block_list, tmp_dirname);
-+ ADD_BLOCK (block_list, resolved_dirname);
-
- __set_errno (0);
- ret = getcwd (resolved_dirname, path_max);
---
-2.16.1.windows.4
-